create-quadrokit 0.2.3 → 0.2.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 +7 -2
- package/dist/index.mjs +160 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,14 +14,19 @@ bun create-quadrokit/src/index.ts --template dashboard --dir /path/to/my-app
|
|
|
14
14
|
| Flag | Description |
|
|
15
15
|
|------|-------------|
|
|
16
16
|
| `--template <name>` | `dashboard` \| `website` \| `ecommerce` \| `admin-shell` |
|
|
17
|
-
| `--dir <path>` | Target directory (created; must be empty) |
|
|
17
|
+
| `--dir <path>` | Target directory relative to cwd (created; must be empty if it already exists) |
|
|
18
|
+
| `--name <name>` | Same as `--dir` (project folder name); ignored if `--dir` is set |
|
|
19
|
+
| `-y` / `--yes` | Non-interactive: requires `--template`; uses `quadro-<template>` as the directory unless `--dir` / `--name` is set; runs git init and install without prompting (override with `--no-git` / `--no-install`) |
|
|
20
|
+
| `-h` / `--help` | Print usage and exit |
|
|
18
21
|
| `--keep-workspace` | Keep `workspace:*` in `package.json` (for development inside the QuadroKit monorepo only) |
|
|
19
22
|
| `--git` / `--no-git` | Initialize a git repo after scaffolding (skip the prompt) |
|
|
20
23
|
| `--install` / `--no-install` | Run `bun install` or `npm install` after scaffolding (skip the prompt) |
|
|
21
24
|
|
|
25
|
+
The banner shows **create-quadrokit**’s package version (e.g. `v0.2.2`).
|
|
26
|
+
|
|
22
27
|
On start, the CLI prints a short **prerequisites** check (Node.js, Bun, Git). Node 18+ is required; Bun is optional (falls back to npm for installs). Git is only needed if you choose to init a repository.
|
|
23
28
|
|
|
24
|
-
Interactive mode: run without `--template` / `--dir` to be prompted
|
|
29
|
+
Interactive mode: run without `--template` / `--dir` / `--name` to be prompted. If the project directory already exists and is **not empty**, you are asked again for a different name. You are then asked about **git** and **install** unless `-y` or the explicit flags above apply.
|
|
25
30
|
|
|
26
31
|
## What it does
|
|
27
32
|
|
package/dist/index.mjs
CHANGED
|
@@ -32,12 +32,49 @@ function checkPrereqs() {
|
|
|
32
32
|
gitVersion,
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
-
function
|
|
35
|
+
function readPackageVersion() {
|
|
36
|
+
try {
|
|
37
|
+
const raw = readFileSync(path.join(packageRoot(), 'package.json'), 'utf8');
|
|
38
|
+
const v = JSON.parse(raw).version;
|
|
39
|
+
return typeof v === 'string' ? v : '0.0.0';
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return '0.0.0';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function printWelcome(version) {
|
|
36
46
|
console.log(`
|
|
37
|
-
${'✨'.repeat(2)} ${'\u001b[1mcreate-quadrokit\u001b[0m'} ${'✨'.repeat(2)}
|
|
47
|
+
${'✨'.repeat(2)} ${'\u001b[1mcreate-quadrokit\u001b[0m'} ${'\u001b[90m'}v${version}${'\u001b[0m'} ${'✨'.repeat(2)}
|
|
38
48
|
${'🚀'.repeat(2)} Scaffold a QuadroKit Vite + React app for 4D REST
|
|
39
49
|
`);
|
|
40
50
|
}
|
|
51
|
+
function printHelp(version) {
|
|
52
|
+
const t = TEMPLATES.join(' | ');
|
|
53
|
+
console.log(`create-quadrokit v${version}
|
|
54
|
+
|
|
55
|
+
Scaffold a QuadroKit Vite + React app for 4D REST.
|
|
56
|
+
|
|
57
|
+
Usage:
|
|
58
|
+
create-quadrokit [options]
|
|
59
|
+
bunx create-quadrokit@latest [options]
|
|
60
|
+
|
|
61
|
+
Options:
|
|
62
|
+
--template <name> Template: ${t}
|
|
63
|
+
--dir <path> Project directory (relative to cwd; must not exist or be empty)
|
|
64
|
+
--name <name> Same as --dir when --dir is omitted
|
|
65
|
+
-y, --yes Non-interactive: requires --template; uses quadro-<template> as directory
|
|
66
|
+
unless --dir / --name; runs git init + install (override with --no-git / --no-install)
|
|
67
|
+
--git Initialize a git repository (skips prompt)
|
|
68
|
+
--no-git Do not run git init
|
|
69
|
+
--install Install dependencies after scaffold (skips prompt)
|
|
70
|
+
--no-install Do not install dependencies
|
|
71
|
+
--keep-workspace Keep workspace:* @quadrokit deps (monorepo development only)
|
|
72
|
+
-h, --help Show this help
|
|
73
|
+
|
|
74
|
+
If the chosen directory already exists and is not empty, you are prompted for a different name
|
|
75
|
+
(unless --yes is set; then the CLI exits with an error).
|
|
76
|
+
`);
|
|
77
|
+
}
|
|
41
78
|
function printPrereqReport(p) {
|
|
42
79
|
console.log(' Prerequisites');
|
|
43
80
|
const line = (ok, emoji, label, detail) => {
|
|
@@ -86,8 +123,11 @@ async function resolveBiomeJsonPath() {
|
|
|
86
123
|
function parseArgs(argv) {
|
|
87
124
|
let template;
|
|
88
125
|
let dir;
|
|
126
|
+
let name;
|
|
89
127
|
let keepWorkspace = false;
|
|
90
|
-
/**
|
|
128
|
+
/** Non-interactive: default project dir, git init, and install (no prompts). */
|
|
129
|
+
let yes = false;
|
|
130
|
+
/** `undefined` = prompt later (unless yes) */
|
|
91
131
|
let initGit;
|
|
92
132
|
let installDeps;
|
|
93
133
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -98,9 +138,15 @@ function parseArgs(argv) {
|
|
|
98
138
|
else if (a === '--dir' && argv[i + 1]) {
|
|
99
139
|
dir = argv[++i];
|
|
100
140
|
}
|
|
141
|
+
else if (a === '--name' && argv[i + 1]) {
|
|
142
|
+
name = argv[++i];
|
|
143
|
+
}
|
|
101
144
|
else if (a === '--keep-workspace') {
|
|
102
145
|
keepWorkspace = true;
|
|
103
146
|
}
|
|
147
|
+
else if (a === '-y' || a === '--yes') {
|
|
148
|
+
yes = true;
|
|
149
|
+
}
|
|
104
150
|
else if (a === '--git') {
|
|
105
151
|
initGit = true;
|
|
106
152
|
}
|
|
@@ -114,7 +160,10 @@ function parseArgs(argv) {
|
|
|
114
160
|
installDeps = false;
|
|
115
161
|
}
|
|
116
162
|
}
|
|
117
|
-
|
|
163
|
+
if (!dir && name) {
|
|
164
|
+
dir = name;
|
|
165
|
+
}
|
|
166
|
+
return { template, dir, keepWorkspace, yes, initGit, installDeps };
|
|
118
167
|
}
|
|
119
168
|
async function pathExists(p) {
|
|
120
169
|
try {
|
|
@@ -125,6 +174,68 @@ async function pathExists(p) {
|
|
|
125
174
|
return false;
|
|
126
175
|
}
|
|
127
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Resolves an absolute path for the project root. Re-prompts (interactive) when the path exists
|
|
179
|
+
* as a non-empty directory or is not a directory. With --yes, exits if the path is unusable.
|
|
180
|
+
*/
|
|
181
|
+
async function resolveProjectDest(dirArg, template, yesFlag) {
|
|
182
|
+
let dirInput = dirArg?.trim() || undefined;
|
|
183
|
+
let retryAfterConflict = false;
|
|
184
|
+
while (true) {
|
|
185
|
+
if (!dirInput) {
|
|
186
|
+
if (yesFlag && !retryAfterConflict) {
|
|
187
|
+
dirInput = `quadro-${template}`;
|
|
188
|
+
}
|
|
189
|
+
else if (yesFlag) {
|
|
190
|
+
console.error(' ❌ With --yes, pass --dir or --name pointing at a missing or empty directory.');
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
const res = await prompts({
|
|
195
|
+
type: 'text',
|
|
196
|
+
name: 'dir',
|
|
197
|
+
message: retryAfterConflict
|
|
198
|
+
? 'That path exists and is not empty — enter a different project directory'
|
|
199
|
+
: 'Project directory',
|
|
200
|
+
initial: retryAfterConflict ? `quadro-${template}-2` : `quadro-${template}`,
|
|
201
|
+
});
|
|
202
|
+
if (typeof res.dir !== 'string') {
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
dirInput = res.dir.trim();
|
|
206
|
+
if (!dirInput) {
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const dest = path.resolve(process.cwd(), dirInput);
|
|
212
|
+
const st = await stat(dest).catch(() => null);
|
|
213
|
+
if (!st) {
|
|
214
|
+
return dest;
|
|
215
|
+
}
|
|
216
|
+
if (!st.isDirectory()) {
|
|
217
|
+
if (yesFlag) {
|
|
218
|
+
console.error(` ❌ Not a directory: ${dest}`);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
console.log(` ⚠️ Not a directory: ${dest}`);
|
|
222
|
+
dirInput = undefined;
|
|
223
|
+
retryAfterConflict = true;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const files = await readdir(dest);
|
|
227
|
+
if (files.length === 0) {
|
|
228
|
+
return dest;
|
|
229
|
+
}
|
|
230
|
+
if (yesFlag) {
|
|
231
|
+
console.error(` ❌ Directory not empty: ${dest}\n Use --dir or --name with an empty path, or remove files from that folder.`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
console.log(` ⚠️ Directory not empty: ${dest}`);
|
|
235
|
+
dirInput = undefined;
|
|
236
|
+
retryAfterConflict = true;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
128
239
|
async function copyTemplate(src, dest) {
|
|
129
240
|
await copyTree(src, dest, { skipNames: TEMPLATE_COPY_SKIP });
|
|
130
241
|
}
|
|
@@ -269,43 +380,36 @@ dist
|
|
|
269
380
|
}
|
|
270
381
|
async function main() {
|
|
271
382
|
const argv = process.argv.slice(2);
|
|
272
|
-
|
|
273
|
-
|
|
383
|
+
if (argv.includes('-h') || argv.includes('--help')) {
|
|
384
|
+
printHelp(readPackageVersion());
|
|
385
|
+
process.exit(0);
|
|
386
|
+
}
|
|
387
|
+
const { template: tArg, dir: dirArg, keepWorkspace, yes: yesFlag, initGit: gitFlag, installDeps: installFlag, } = parseArgs(argv);
|
|
388
|
+
printWelcome(readPackageVersion());
|
|
274
389
|
const prereqs = checkPrereqs();
|
|
275
390
|
printPrereqReport(prereqs);
|
|
276
391
|
if (!prereqs.nodeOk) {
|
|
277
392
|
console.error(` ❌ Node.js ${MIN_NODE_MAJOR}+ is required.`);
|
|
278
393
|
process.exit(1);
|
|
279
394
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
395
|
+
let template = tArg && TEMPLATES.includes(tArg) ? tArg : undefined;
|
|
396
|
+
if (!template) {
|
|
397
|
+
if (yesFlag) {
|
|
398
|
+
console.error(' ❌ --yes requires --template <name>');
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
const picked = await prompts({
|
|
283
402
|
type: 'select',
|
|
284
403
|
name: 'template',
|
|
285
404
|
message: 'Template',
|
|
286
405
|
choices: TEMPLATES.map((value) => ({ title: value, value })),
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
process.exit(1);
|
|
406
|
+
});
|
|
407
|
+
template = picked.template;
|
|
290
408
|
}
|
|
291
|
-
|
|
292
|
-
(await prompts({
|
|
293
|
-
type: 'text',
|
|
294
|
-
name: 'dir',
|
|
295
|
-
message: 'Project directory',
|
|
296
|
-
initial: `quadro-${template}`,
|
|
297
|
-
})).dir;
|
|
298
|
-
if (!dirAns || typeof dirAns !== 'string') {
|
|
409
|
+
if (!template) {
|
|
299
410
|
process.exit(1);
|
|
300
411
|
}
|
|
301
|
-
const dest =
|
|
302
|
-
if (await pathExists(dest)) {
|
|
303
|
-
const files = await readdir(dest);
|
|
304
|
-
if (files.length > 0) {
|
|
305
|
-
console.error(`Directory not empty: ${dest}`);
|
|
306
|
-
process.exit(1);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
412
|
+
const dest = await resolveProjectDest(dirArg, template, yesFlag);
|
|
309
413
|
const src = await resolveTemplateDir(template);
|
|
310
414
|
if (!src) {
|
|
311
415
|
console.error(`Template not found: ${template} (expected under create-quadrokit/templates or packages/templates)`);
|
|
@@ -324,16 +428,21 @@ async function main() {
|
|
|
324
428
|
console.log(`\n 🎉 Project files are ready at ${'\u001b[1m'}${dest}${'\u001b[0m'}\n`);
|
|
325
429
|
let initGit = gitFlag;
|
|
326
430
|
if (initGit === undefined) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
431
|
+
if (yesFlag) {
|
|
432
|
+
initGit = true;
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
const g = await prompts({
|
|
436
|
+
type: 'confirm',
|
|
437
|
+
name: 'v',
|
|
438
|
+
message: 'Initialize a git repository here?',
|
|
439
|
+
initial: true,
|
|
440
|
+
});
|
|
441
|
+
if (typeof g.v !== 'boolean') {
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
initGit = g.v;
|
|
335
445
|
}
|
|
336
|
-
initGit = g.v;
|
|
337
446
|
}
|
|
338
447
|
if (initGit) {
|
|
339
448
|
if (!prereqs.gitOk) {
|
|
@@ -351,16 +460,21 @@ async function main() {
|
|
|
351
460
|
}
|
|
352
461
|
let installDeps = installFlag;
|
|
353
462
|
if (installDeps === undefined) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
463
|
+
if (yesFlag) {
|
|
464
|
+
installDeps = true;
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
const ins = await prompts({
|
|
468
|
+
type: 'confirm',
|
|
469
|
+
name: 'v',
|
|
470
|
+
message: 'Install dependencies now?',
|
|
471
|
+
initial: true,
|
|
472
|
+
});
|
|
473
|
+
if (typeof ins.v !== 'boolean') {
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
installDeps = ins.v;
|
|
362
477
|
}
|
|
363
|
-
installDeps = ins.v;
|
|
364
478
|
}
|
|
365
479
|
if (installDeps) {
|
|
366
480
|
const useBun = prereqs.bunOk;
|