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.
Files changed (3) hide show
  1. package/README.md +7 -2
  2. package/dist/index.mjs +160 -46
  3. 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 for those; you are then asked about **git** and **install** unless overridden with the flags above.
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 printWelcome() {
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
- /** `undefined` = prompt later */
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
- return { template, dir, keepWorkspace, initGit, installDeps };
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
- const { template: tArg, dir: dirArg, keepWorkspace, initGit: gitFlag, installDeps: installFlag, } = parseArgs(argv);
273
- printWelcome();
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
- const template = tArg && TEMPLATES.includes(tArg)
281
- ? tArg
282
- : (await prompts({
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
- })).template;
288
- if (!template) {
289
- process.exit(1);
406
+ });
407
+ template = picked.template;
290
408
  }
291
- const dirAns = dirArg ??
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 = path.resolve(process.cwd(), dirAns);
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
- const g = await prompts({
328
- type: 'confirm',
329
- name: 'v',
330
- message: 'Initialize a git repository here?',
331
- initial: true,
332
- });
333
- if (typeof g.v !== 'boolean') {
334
- process.exit(1);
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
- const ins = await prompts({
355
- type: 'confirm',
356
- name: 'v',
357
- message: 'Install dependencies now?',
358
- initial: true,
359
- });
360
- if (typeof ins.v !== 'boolean') {
361
- process.exit(1);
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-quadrokit",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Scaffold a QuadroKit Vite + React app from a template",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.mjs",