create-bcp-app 0.1.12 → 0.1.13

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.
@@ -2,8 +2,8 @@
2
2
 
3
3
  import path from "node:path";
4
4
  import {
5
- createInterface,
6
- } from "node:readline/promises";
5
+ emitKeypressEvents,
6
+ } from "node:readline";
7
7
  import {
8
8
  stdin as input,
9
9
  stdout as output,
@@ -19,11 +19,16 @@ import {
19
19
  normalizeDatabase,
20
20
  } from "../src/project-options.mjs";
21
21
 
22
+ const ANSI = {
23
+ reset: "\u001B[0m",
24
+ selected: "\u001B[46m\u001B[30m\u001B[1m",
25
+ muted: "\u001B[2m",
26
+ };
27
+
22
28
  await main();
23
29
 
24
30
  async function main() {
25
- const args =
26
- process.argv.slice(2);
31
+ const args = process.argv.slice(2);
27
32
 
28
33
  if (
29
34
  args.includes("--help") ||
@@ -36,35 +41,20 @@ async function main() {
36
41
  let parsed;
37
42
 
38
43
  try {
39
- parsed =
40
- parseArguments(
41
- args
42
- );
44
+ parsed = parseArguments(args);
43
45
  } catch (error) {
44
- reportError(
45
- error
46
- );
46
+ reportError(error);
47
47
  return;
48
48
  }
49
49
 
50
50
  if (!parsed.projectDirectory) {
51
51
  printHelp();
52
- process.exitCode =
53
- 1;
52
+ process.exitCode = 1;
54
53
  return;
55
54
  }
56
55
 
57
- let prompt =
58
- null;
59
-
60
56
  try {
61
- const resolved =
62
- await resolveOptions(
63
- parsed
64
- );
65
-
66
- prompt =
67
- resolved.prompt;
57
+ const resolved = await resolveOptions(parsed);
68
58
 
69
59
  console.log("");
70
60
  console.log(
@@ -74,68 +64,40 @@ async function main() {
74
64
  );
75
65
  console.log("");
76
66
 
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
- });
94
-
95
- printSuccess(
96
- result,
97
- parsed.install
98
- );
67
+ const result = await createBcpApp({
68
+ projectDirectory: parsed.projectDirectory,
69
+ install: parsed.install,
70
+ bcpPackage: parsed.bcpPackage,
71
+ tailwind: resolved.tailwind,
72
+ database: resolved.database,
73
+ auth: resolved.auth,
74
+ packageManager: "npm",
75
+ });
76
+
77
+ printSuccess(result, parsed.install);
99
78
  } catch (error) {
100
- reportError(
101
- error
102
- );
103
- } finally {
104
- prompt?.close();
79
+ reportError(error);
105
80
  }
106
81
  }
107
82
 
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;
83
+ function parseArguments(args) {
84
+ let projectDirectory = null;
85
+ let install = true;
86
+ let bcpPackage = undefined;
87
+ let tailwind = undefined;
88
+ let database = undefined;
89
+ let auth = undefined;
90
+ let acceptDefaults = false;
125
91
 
126
92
  for (
127
93
  let index = 0;
128
94
  index < args.length;
129
95
  index++
130
96
  ) {
131
- const value =
132
- args[index];
97
+ const value = args[index];
133
98
 
134
- if (
135
- value === "--no-install"
136
- ) {
137
- install =
138
- false;
99
+ if (value === "--no-install") {
100
+ install = false;
139
101
  continue;
140
102
  }
141
103
 
@@ -143,34 +105,22 @@ function parseArguments(
143
105
  value === "--yes" ||
144
106
  value === "-y"
145
107
  ) {
146
- acceptDefaults =
147
- true;
108
+ acceptDefaults = true;
148
109
  continue;
149
110
  }
150
111
 
151
- if (
152
- value === "--tailwind"
153
- ) {
154
- tailwind =
155
- true;
112
+ if (value === "--tailwind") {
113
+ tailwind = true;
156
114
  continue;
157
115
  }
158
116
 
159
- if (
160
- value === "--no-tailwind"
161
- ) {
162
- tailwind =
163
- false;
117
+ if (value === "--no-tailwind") {
118
+ tailwind = false;
164
119
  continue;
165
120
  }
166
121
 
167
- if (
168
- value === "--database"
169
- ) {
170
- const nextValue =
171
- args[
172
- index + 1
173
- ];
122
+ if (value === "--database") {
123
+ const nextValue = args[index + 1];
174
124
 
175
125
  if (!nextValue) {
176
126
  throw new Error(
@@ -178,21 +128,13 @@ function parseArguments(
178
128
  );
179
129
  }
180
130
 
181
- database =
182
- normalizeDatabase(
183
- nextValue
184
- );
131
+ database = normalizeDatabase(nextValue);
185
132
  index++;
186
133
  continue;
187
134
  }
188
135
 
189
- if (
190
- value === "--auth"
191
- ) {
192
- const nextValue =
193
- args[
194
- index + 1
195
- ];
136
+ if (value === "--auth") {
137
+ const nextValue = args[index + 1];
196
138
 
197
139
  if (!nextValue) {
198
140
  throw new Error(
@@ -200,21 +142,13 @@ function parseArguments(
200
142
  );
201
143
  }
202
144
 
203
- auth =
204
- normalizeAuth(
205
- nextValue
206
- );
145
+ auth = normalizeAuth(nextValue);
207
146
  index++;
208
147
  continue;
209
148
  }
210
149
 
211
- if (
212
- value === "--bcp"
213
- ) {
214
- const nextValue =
215
- args[
216
- index + 1
217
- ];
150
+ if (value === "--bcp") {
151
+ const nextValue = args[index + 1];
218
152
 
219
153
  if (!nextValue) {
220
154
  throw new Error(
@@ -222,31 +156,24 @@ function parseArguments(
222
156
  );
223
157
  }
224
158
 
225
- bcpPackage =
226
- nextValue;
159
+ bcpPackage = nextValue;
227
160
  index++;
228
161
  continue;
229
162
  }
230
163
 
231
- if (
232
- value.startsWith("-")
233
- ) {
164
+ if (value.startsWith("-")) {
234
165
  throw new Error(
235
166
  `Unknown option: ${value}`
236
167
  );
237
168
  }
238
169
 
239
- if (
240
- projectDirectory !==
241
- null
242
- ) {
170
+ if (projectDirectory !== null) {
243
171
  throw new Error(
244
172
  "Only one project directory may be provided."
245
173
  );
246
174
  }
247
175
 
248
- projectDirectory =
249
- value;
176
+ projectDirectory = value;
250
177
  }
251
178
 
252
179
  return {
@@ -266,196 +193,79 @@ async function resolveOptions({
266
193
  auth: selectedAuth,
267
194
  acceptDefaults,
268
195
  }) {
269
- let tailwind =
270
- selectedTailwind;
271
- let database =
272
- selectedDatabase;
273
- let auth =
274
- selectedAuth;
196
+ let tailwind = selectedTailwind;
197
+ let database = selectedDatabase;
198
+ let auth = selectedAuth;
275
199
 
276
200
  if (acceptDefaults) {
277
201
  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,
202
+ tailwind: tailwind ?? true,
203
+ database: normalizeDatabase(
204
+ database ?? "none"
205
+ ),
206
+ auth: normalizeAuth(
207
+ auth ?? "none"
208
+ ),
293
209
  };
294
210
  }
295
211
 
296
- const interactive =
297
- Boolean(
298
- input.isTTY &&
299
- output.isTTY
300
- );
212
+ const interactive = Boolean(
213
+ input.isTTY &&
214
+ output.isTTY
215
+ );
301
216
 
302
217
  if (!interactive) {
303
218
  return {
304
- tailwind:
305
- tailwind ??
306
- false,
307
- database:
308
- normalizeDatabase(
309
- database ??
310
- "none"
311
- ),
312
- auth:
313
- normalizeAuth(
314
- auth ??
315
- "none"
316
- ),
317
- prompt:
318
- null,
219
+ tailwind: tailwind ?? false,
220
+ database: normalizeDatabase(
221
+ database ?? "none"
222
+ ),
223
+ auth: normalizeAuth(
224
+ auth ?? "none"
225
+ ),
319
226
  };
320
227
  }
321
228
 
322
- const needsPrompt =
323
- tailwind === undefined ||
324
- database === undefined ||
325
- auth === undefined;
229
+ console.log("");
230
+ console.log("Configure your BCP app");
231
+ console.log("");
326
232
 
327
- if (!needsPrompt) {
328
- return {
329
- tailwind:
330
- Boolean(
331
- tailwind
332
- ),
333
- database:
334
- normalizeDatabase(
335
- database
336
- ),
337
- auth:
338
- normalizeAuth(
339
- auth
340
- ),
341
- prompt:
342
- null,
343
- };
233
+ if (tailwind === undefined) {
234
+ tailwind = await askTailwind();
344
235
  }
345
236
 
346
- const prompt =
347
- createInterface({
348
- input,
349
- output,
350
- });
351
-
352
- try {
353
- console.log("");
354
- console.log(
355
- "Configure your BCP app"
356
- );
357
- console.log("");
358
-
359
- if (
360
- tailwind ===
361
- undefined
362
- ) {
363
- tailwind =
364
- await askYesNo(
365
- prompt,
366
- "Use Tailwind CSS?",
367
- true
368
- );
369
- }
370
-
371
- if (
372
- database ===
373
- undefined
374
- ) {
375
- database =
376
- await askDatabase(
377
- prompt
378
- );
379
- }
237
+ if (database === undefined) {
238
+ database = await askDatabase();
239
+ }
380
240
 
381
- if (
382
- auth ===
383
- undefined
384
- ) {
385
- auth =
386
- await askAuth(
387
- prompt
388
- );
389
- }
390
- } catch (error) {
391
- prompt.close();
392
- throw error;
241
+ if (auth === undefined) {
242
+ auth = await askAuth();
393
243
  }
394
244
 
395
245
  return {
396
- tailwind:
397
- Boolean(
398
- tailwind
399
- ),
400
- database:
401
- normalizeDatabase(
402
- database
403
- ),
404
- auth:
405
- normalizeAuth(
406
- auth
407
- ),
408
- prompt,
246
+ tailwind: Boolean(tailwind),
247
+ database: normalizeDatabase(database),
248
+ auth: normalizeAuth(auth),
409
249
  };
410
250
  }
411
251
 
412
- async function askYesNo(
413
- prompt,
414
- label,
415
- defaultValue
416
- ) {
417
- const suffix =
418
- defaultValue
419
- ? " (Y/n) "
420
- : " (y/N) ";
421
-
422
- while (true) {
423
- const answer =
424
- (
425
- await prompt.question(
426
- `${label}${suffix}`
427
- )
428
- )
429
- .trim()
430
- .toLowerCase();
431
-
432
- if (!answer) {
433
- return defaultValue;
434
- }
435
-
436
- if (
437
- answer === "y" ||
438
- answer === "yes"
439
- ) {
440
- return true;
441
- }
442
-
443
- if (
444
- answer === "n" ||
445
- answer === "no"
446
- ) {
447
- return false;
448
- }
449
-
450
- console.log(
451
- "Please answer yes or no."
452
- );
453
- }
252
+ function askTailwind() {
253
+ return askTerminalSelect(
254
+ "Use Tailwind CSS?",
255
+ [
256
+ {
257
+ value: true,
258
+ label: "Yes",
259
+ },
260
+ {
261
+ value: false,
262
+ label: "No",
263
+ },
264
+ ]
265
+ );
454
266
  }
455
267
 
456
- async function askDatabase(
457
- prompt
458
- ) {
268
+ function askDatabase() {
459
269
  const labels = {
460
270
  none: "None",
461
271
  mysql: "MySQL",
@@ -464,130 +274,201 @@ async function askDatabase(
464
274
  mongodb: "MongoDB",
465
275
  };
466
276
 
467
- console.log(
468
- "Select a database:"
469
- );
470
-
471
- DATABASE_CHOICES.forEach(
472
- (
473
- value,
474
- index
475
- ) => {
476
- console.log(
477
- ` ${index + 1}) ${labels[value]}`
478
- );
479
- }
277
+ return askTerminalSelect(
278
+ "Select a database:",
279
+ DATABASE_CHOICES.map(
280
+ (value) => ({
281
+ value,
282
+ label: labels[value],
283
+ })
284
+ )
480
285
  );
481
-
482
- while (true) {
483
- const answer =
484
- (
485
- await prompt.question(
486
- "Database (1): "
487
- )
488
- ).trim();
489
-
490
- if (!answer) {
491
- return "none";
492
- }
493
-
494
- const choice =
495
- DATABASE_CHOICES[
496
- Number(answer) - 1
497
- ];
498
-
499
- if (choice) {
500
- return choice;
501
- }
502
-
503
- const normalized =
504
- answer.toLowerCase();
505
-
506
- if (
507
- DATABASE_CHOICES.includes(
508
- normalized
509
- )
510
- ) {
511
- return normalized;
512
- }
513
-
514
- console.log(
515
- "Choose a number from the list or enter a database name."
516
- );
517
- }
518
286
  }
519
287
 
520
- async function askAuth(
521
- prompt
522
- ) {
288
+ function askAuth() {
523
289
  const labels = {
524
290
  none: "None",
525
- "jwt-cookie":
526
- "JWT Cookie",
291
+ "jwt-cookie": "JWT Cookie",
527
292
  };
528
293
 
529
- console.log(
530
- "Select authentication:"
294
+ return askTerminalSelect(
295
+ "Select authentication:",
296
+ AUTH_CHOICES.map(
297
+ (value) => ({
298
+ value,
299
+ label: labels[value],
300
+ })
301
+ )
531
302
  );
303
+ }
532
304
 
533
- AUTH_CHOICES.forEach(
305
+ function askTerminalSelect(
306
+ label,
307
+ choices
308
+ ) {
309
+ if (
310
+ !Array.isArray(choices) ||
311
+ choices.length === 0
312
+ ) {
313
+ return Promise.reject(
314
+ new Error(
315
+ "Interactive selection requires at least one choice."
316
+ )
317
+ );
318
+ }
319
+
320
+ if (
321
+ typeof input.setRawMode !== "function"
322
+ ) {
323
+ return Promise.resolve(
324
+ choices[0].value
325
+ );
326
+ }
327
+
328
+ return new Promise(
534
329
  (
535
- value,
536
- index
330
+ resolve,
331
+ reject
537
332
  ) => {
333
+ let selectedIndex = 0;
334
+ const previousRawMode =
335
+ input.isRaw === true;
336
+
337
+ const render = (
338
+ redraw = false
339
+ ) => {
340
+ if (redraw) {
341
+ output.write(
342
+ `\u001B[${choices.length}A`
343
+ );
344
+ }
345
+
346
+ for (
347
+ let index = 0;
348
+ index < choices.length;
349
+ index++
350
+ ) {
351
+ const choice = choices[index];
352
+ const selected =
353
+ index === selectedIndex;
354
+
355
+ output.write(
356
+ "\u001B[2K\r"
357
+ );
358
+
359
+ if (selected) {
360
+ output.write(
361
+ `${ANSI.selected} > ${choice.label} ${ANSI.reset}\n`
362
+ );
363
+ } else {
364
+ output.write(
365
+ ` ${choice.label}\n`
366
+ );
367
+ }
368
+ }
369
+ };
370
+
371
+ const cleanup = () => {
372
+ input.removeListener(
373
+ "keypress",
374
+ onKeypress
375
+ );
376
+ input.setRawMode(
377
+ previousRawMode
378
+ );
379
+
380
+ if (!previousRawMode) {
381
+ input.pause();
382
+ }
383
+ };
384
+
385
+ const onKeypress = (
386
+ _character,
387
+ key = {}
388
+ ) => {
389
+ if (
390
+ key.ctrl &&
391
+ key.name === "c"
392
+ ) {
393
+ cleanup();
394
+ output.write("\n");
395
+ reject(
396
+ new Error(
397
+ "Setup cancelled."
398
+ )
399
+ );
400
+ return;
401
+ }
402
+
403
+ if (
404
+ key.name === "escape"
405
+ ) {
406
+ cleanup();
407
+ output.write("\n");
408
+ reject(
409
+ new Error(
410
+ "Setup cancelled."
411
+ )
412
+ );
413
+ return;
414
+ }
415
+
416
+ if (key.name === "up") {
417
+ selectedIndex = (
418
+ selectedIndex - 1 +
419
+ choices.length
420
+ ) % choices.length;
421
+ render(true);
422
+ return;
423
+ }
424
+
425
+ if (key.name === "down") {
426
+ selectedIndex = (
427
+ selectedIndex + 1
428
+ ) % choices.length;
429
+ render(true);
430
+ return;
431
+ }
432
+
433
+ if (
434
+ key.name === "return" ||
435
+ key.name === "enter"
436
+ ) {
437
+ const selected =
438
+ choices[selectedIndex];
439
+
440
+ cleanup();
441
+ output.write("\n");
442
+ resolve(selected.value);
443
+ }
444
+ };
445
+
446
+ console.log(label);
538
447
  console.log(
539
- ` ${index + 1}) ${labels[value]}`
448
+ `${ANSI.muted}Use ↑/↓ to move, Enter to select.${ANSI.reset}`
540
449
  );
541
- }
542
- );
543
-
544
- while (true) {
545
- const answer =
546
- (
547
- await prompt.question(
548
- "Authentication (1): "
549
- )
550
- ).trim();
551
-
552
- if (!answer) {
553
- return "none";
554
- }
555
450
 
556
- const choice =
557
- AUTH_CHOICES[
558
- Number(answer) - 1
559
- ];
451
+ emitKeypressEvents(input);
452
+ input.on(
453
+ "keypress",
454
+ onKeypress
455
+ );
456
+ input.setRawMode(true);
457
+ input.resume();
560
458
 
561
- if (choice) {
562
- return choice;
459
+ render();
563
460
  }
564
-
565
- const normalized =
566
- answer.toLowerCase();
567
-
568
- if (
569
- AUTH_CHOICES.includes(
570
- normalized
571
- )
572
- ) {
573
- return normalized;
574
- }
575
-
576
- console.log(
577
- "Choose a number from the list or enter an authentication preset name."
578
- );
579
- }
461
+ );
580
462
  }
581
463
 
582
464
  function printSuccess(
583
465
  result,
584
466
  installed
585
467
  ) {
586
- const relative =
587
- path.relative(
588
- process.cwd(),
589
- result.targetDirectory
590
- ) || ".";
468
+ const relative = path.relative(
469
+ process.cwd(),
470
+ result.targetDirectory
471
+ ) || ".";
591
472
 
592
473
  console.log("");
593
474
  console.log(
@@ -606,28 +487,20 @@ function printSuccess(
606
487
  `Authentication: ${formatAuthName(result.auth)}`
607
488
  );
608
489
  console.log("");
609
- console.log(
610
- "Next steps:"
611
- );
490
+ console.log("Next steps:");
612
491
  console.log(
613
492
  ` cd ${quoteIfNeeded(relative)}`
614
493
  );
615
494
 
616
495
  if (!installed) {
617
- console.log(
618
- " npm install"
619
- );
496
+ console.log(" npm install");
620
497
  }
621
498
 
622
- console.log(
623
- " npm run dev"
624
- );
499
+ console.log(" npm run dev");
625
500
  console.log("");
626
501
  }
627
502
 
628
- function formatDatabaseName(
629
- value
630
- ) {
503
+ function formatDatabaseName(value) {
631
504
  const names = {
632
505
  none: "None",
633
506
  mysql: "MySQL",
@@ -636,21 +509,16 @@ function formatDatabaseName(
636
509
  mongodb: "MongoDB",
637
510
  };
638
511
 
639
- return names[value] ??
640
- value;
512
+ return names[value] ?? value;
641
513
  }
642
514
 
643
- function formatAuthName(
644
- value
645
- ) {
515
+ function formatAuthName(value) {
646
516
  const names = {
647
517
  none: "None",
648
- "jwt-cookie":
649
- "JWT Cookie",
518
+ "jwt-cookie": "JWT Cookie",
650
519
  };
651
520
 
652
- return names[value] ??
653
- value;
521
+ return names[value] ?? value;
654
522
  }
655
523
 
656
524
  function printHelp() {
@@ -661,7 +529,8 @@ Usage:
661
529
  npx create-bcp-app <project-name> [options]
662
530
 
663
531
  Interactive setup:
664
- - Choose whether to use Tailwind CSS
532
+ - Use ↑/↓ and Enter for every setup choice
533
+ - Choose Tailwind CSS: Yes or No
665
534
  - Choose a database: None, MySQL, PostgreSQL, SQLite or MongoDB
666
535
  - Choose authentication: None or JWT Cookie
667
536
 
@@ -684,31 +553,20 @@ Examples:
684
553
  `);
685
554
  }
686
555
 
687
- function quoteIfNeeded(
688
- value
689
- ) {
690
- return /\s/.test(
691
- value
692
- )
693
- ? JSON.stringify(
694
- value
695
- )
556
+ function quoteIfNeeded(value) {
557
+ return /\s/.test(value)
558
+ ? JSON.stringify(value)
696
559
  : value;
697
560
  }
698
561
 
699
- function reportError(
700
- error
701
- ) {
562
+ function reportError(error) {
702
563
  const message =
703
564
  error instanceof Error
704
565
  ? error.message
705
- : String(
706
- error
707
- );
566
+ : String(error);
708
567
 
709
568
  console.error(
710
569
  `create-bcp-app: ${message}`
711
570
  );
712
- process.exitCode =
713
- 1;
571
+ process.exitCode = 1;
714
572
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bcp-app",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "Create a new BCP Framework application.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.mjs CHANGED
@@ -250,12 +250,9 @@ function copyTemplate(
250
250
  sourceDirectory,
251
251
  targetDirectory
252
252
  ) {
253
- fs.cpSync(
253
+ copyDirectoryContents(
254
254
  sourceDirectory,
255
- targetDirectory,
256
- {
257
- recursive: true,
258
- }
255
+ targetDirectory
259
256
  );
260
257
 
261
258
  const gitignoreTemplate =
@@ -279,6 +276,66 @@ function copyTemplate(
279
276
  }
280
277
  }
281
278
 
279
+ function copyDirectoryContents(
280
+ sourceDirectory,
281
+ targetDirectory
282
+ ) {
283
+ fs.mkdirSync(
284
+ targetDirectory,
285
+ {
286
+ recursive: true,
287
+ }
288
+ );
289
+
290
+ const entries =
291
+ fs.readdirSync(
292
+ sourceDirectory,
293
+ {
294
+ withFileTypes: true,
295
+ }
296
+ );
297
+
298
+ for (
299
+ const entry
300
+ of entries
301
+ ) {
302
+ const sourcePath =
303
+ path.join(
304
+ sourceDirectory,
305
+ entry.name
306
+ );
307
+ const targetPath =
308
+ path.join(
309
+ targetDirectory,
310
+ entry.name
311
+ );
312
+
313
+ if (
314
+ entry.isDirectory()
315
+ ) {
316
+ copyDirectoryContents(
317
+ sourcePath,
318
+ targetPath
319
+ );
320
+ continue;
321
+ }
322
+
323
+ if (
324
+ entry.isFile()
325
+ ) {
326
+ fs.copyFileSync(
327
+ sourcePath,
328
+ targetPath
329
+ );
330
+ continue;
331
+ }
332
+
333
+ throw new Error(
334
+ `Unsupported template entry: ${sourcePath}`
335
+ );
336
+ }
337
+ }
338
+
282
339
  function readPackageMetadata() {
283
340
  const packageJson =
284
341
  JSON.parse(