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