create-quadrokit 0.2.3 → 0.2.5

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 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
  }
@@ -237,8 +348,9 @@ Created with **create-quadrokit** (template: \`${template}\`).
237
348
 
238
349
  - \`QUADROKIT_ACCESS_KEY\` — multipart \`accessKey\` to \`/api/login\`; then \`4DAdminSID\` for the catalog request.
239
350
  - \`QUADROKIT_LOGIN_URL\` — full login URL if not \`\${catalog origin}/api/login\`.
351
+ - \`QUADROKIT_GENERATE_VERBOSE\` / \`QUADROKIT_INSECURE_TLS\` — \`1\` / \`true\` / \`yes\` for chatty logs or self-signed HTTPS (dev only).
240
352
 
241
- Flags: \`--url\`, \`--access-key\`, \`--login-url\`, \`--token\` / \`QUADROKIT_CATALOG_TOKEN\` (Bearer).
353
+ Flags: \`--url\`, \`--access-key\`, \`--login-url\`, \`--token\` / \`QUADROKIT_CATALOG_TOKEN\` (Bearer), \`-v\` / \`--verbose\`, \`--insecure-tls\`.
242
354
 
243
355
  4. \`bun run dev\` — the dev server proxies \`/rest\` to \`VITE_4D_ORIGIN\` so **4DSID_** cookies stay same-origin.
244
356
 
@@ -269,43 +381,36 @@ dist
269
381
  }
270
382
  async function main() {
271
383
  const argv = process.argv.slice(2);
272
- const { template: tArg, dir: dirArg, keepWorkspace, initGit: gitFlag, installDeps: installFlag, } = parseArgs(argv);
273
- printWelcome();
384
+ if (argv.includes('-h') || argv.includes('--help')) {
385
+ printHelp(readPackageVersion());
386
+ process.exit(0);
387
+ }
388
+ const { template: tArg, dir: dirArg, keepWorkspace, yes: yesFlag, initGit: gitFlag, installDeps: installFlag, } = parseArgs(argv);
389
+ printWelcome(readPackageVersion());
274
390
  const prereqs = checkPrereqs();
275
391
  printPrereqReport(prereqs);
276
392
  if (!prereqs.nodeOk) {
277
393
  console.error(` ❌ Node.js ${MIN_NODE_MAJOR}+ is required.`);
278
394
  process.exit(1);
279
395
  }
280
- const template = tArg && TEMPLATES.includes(tArg)
281
- ? tArg
282
- : (await prompts({
396
+ let template = tArg && TEMPLATES.includes(tArg) ? tArg : undefined;
397
+ if (!template) {
398
+ if (yesFlag) {
399
+ console.error(' ❌ --yes requires --template <name>');
400
+ process.exit(1);
401
+ }
402
+ const picked = await prompts({
283
403
  type: 'select',
284
404
  name: 'template',
285
405
  message: 'Template',
286
406
  choices: TEMPLATES.map((value) => ({ title: value, value })),
287
- })).template;
288
- if (!template) {
289
- process.exit(1);
407
+ });
408
+ template = picked.template;
290
409
  }
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') {
410
+ if (!template) {
299
411
  process.exit(1);
300
412
  }
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
- }
413
+ const dest = await resolveProjectDest(dirArg, template, yesFlag);
309
414
  const src = await resolveTemplateDir(template);
310
415
  if (!src) {
311
416
  console.error(`Template not found: ${template} (expected under create-quadrokit/templates or packages/templates)`);
@@ -324,16 +429,21 @@ async function main() {
324
429
  console.log(`\n 🎉 Project files are ready at ${'\u001b[1m'}${dest}${'\u001b[0m'}\n`);
325
430
  let initGit = gitFlag;
326
431
  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);
432
+ if (yesFlag) {
433
+ initGit = true;
434
+ }
435
+ else {
436
+ const g = await prompts({
437
+ type: 'confirm',
438
+ name: 'v',
439
+ message: 'Initialize a git repository here?',
440
+ initial: true,
441
+ });
442
+ if (typeof g.v !== 'boolean') {
443
+ process.exit(1);
444
+ }
445
+ initGit = g.v;
335
446
  }
336
- initGit = g.v;
337
447
  }
338
448
  if (initGit) {
339
449
  if (!prereqs.gitOk) {
@@ -351,16 +461,21 @@ async function main() {
351
461
  }
352
462
  let installDeps = installFlag;
353
463
  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);
464
+ if (yesFlag) {
465
+ installDeps = true;
466
+ }
467
+ else {
468
+ const ins = await prompts({
469
+ type: 'confirm',
470
+ name: 'v',
471
+ message: 'Install dependencies now?',
472
+ initial: true,
473
+ });
474
+ if (typeof ins.v !== 'boolean') {
475
+ process.exit(1);
476
+ }
477
+ installDeps = ins.v;
362
478
  }
363
- installDeps = ins.v;
364
479
  }
365
480
  if (installDeps) {
366
481
  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.5",
4
4
  "description": "Scaffold a QuadroKit Vite + React app from a template",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.mjs",
@@ -5,3 +5,5 @@ VITE_4D_ORIGIN=http://127.0.0.1:7080
5
5
  # Not used by the browser runtime.
6
6
  # QUADROKIT_ACCESS_KEY=
7
7
  # QUADROKIT_LOGIN_URL=https://127.0.0.1:7443/api/login
8
+ # QUADROKIT_GENERATE_VERBOSE=1
9
+ # QUADROKIT_INSECURE_TLS=1
@@ -5,3 +5,5 @@ VITE_4D_ORIGIN=http://127.0.0.1:7080
5
5
  # Not used by the browser runtime.
6
6
  # QUADROKIT_ACCESS_KEY=
7
7
  # QUADROKIT_LOGIN_URL=https://127.0.0.1:7443/api/login
8
+ # QUADROKIT_GENERATE_VERBOSE=1
9
+ # QUADROKIT_INSECURE_TLS=1
@@ -5,3 +5,5 @@ VITE_4D_ORIGIN=http://127.0.0.1:7080
5
5
  # Not used by the browser runtime.
6
6
  # QUADROKIT_ACCESS_KEY=
7
7
  # QUADROKIT_LOGIN_URL=https://127.0.0.1:7443/api/login
8
+ # QUADROKIT_GENERATE_VERBOSE=1
9
+ # QUADROKIT_INSECURE_TLS=1
@@ -5,3 +5,5 @@ VITE_4D_ORIGIN=http://127.0.0.1:7080
5
5
  # Not used by the browser runtime.
6
6
  # QUADROKIT_ACCESS_KEY=
7
7
  # QUADROKIT_LOGIN_URL=https://127.0.0.1:7443/api/login
8
+ # QUADROKIT_GENERATE_VERBOSE=1
9
+ # QUADROKIT_INSECURE_TLS=1
@@ -5,3 +5,5 @@ VITE_4D_ORIGIN=http://127.0.0.1:7080
5
5
  # Not used by the browser runtime.
6
6
  # QUADROKIT_ACCESS_KEY=
7
7
  # QUADROKIT_LOGIN_URL=https://127.0.0.1:7443/api/login
8
+ # QUADROKIT_GENERATE_VERBOSE=1
9
+ # QUADROKIT_INSECURE_TLS=1