create-quadrokit 0.2.0 → 0.2.1
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 +10 -6
- package/dist/{index.js → index.mjs} +159 -37
- package/package.json +3 -4
- package/templates/README.md +3 -1
- package/templates/admin-shell/package.json +1 -0
- package/templates/dashboard/package.json +1 -0
- package/templates/ecommerce/package.json +1 -0
- package/templates/website/package.json +1 -0
- package/vendor/generated/client.gen.ts +0 -2725
- package/vendor/generated/meta.json +0 -5
- package/vendor/generated/types.gen.ts +0 -991
package/README.md
CHANGED
|
@@ -16,16 +16,20 @@ bun create-quadrokit/src/index.ts --template dashboard --dir /path/to/my-app
|
|
|
16
16
|
| `--template <name>` | `dashboard` \| `website` \| `ecommerce` \| `admin-shell` |
|
|
17
17
|
| `--dir <path>` | Target directory (created; must be empty) |
|
|
18
18
|
| `--keep-workspace` | Keep `workspace:*` in `package.json` (for development inside the QuadroKit monorepo only) |
|
|
19
|
+
| `--git` / `--no-git` | Initialize a git repo after scaffolding (skip the prompt) |
|
|
20
|
+
| `--install` / `--no-install` | Run `bun install` or `npm install` after scaffolding (skip the prompt) |
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
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
|
+
|
|
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.
|
|
21
25
|
|
|
22
26
|
## What it does
|
|
23
27
|
|
|
24
|
-
1. Copies the chosen template from `create-quadrokit/templates/<name>` when installed from npm, or `packages/templates/<name>` when you run the CLI from the monorepo (skips `node_modules`, `dist
|
|
25
|
-
2.
|
|
26
|
-
3.
|
|
27
|
-
4.
|
|
28
|
-
5.
|
|
28
|
+
1. Copies the chosen template from `create-quadrokit/templates/<name>` when installed from npm, or `packages/templates/<name>` when you run the CLI from the monorepo (skips `node_modules`, `dist`, `.quadrokit`, lockfiles).
|
|
29
|
+
2. Rewrites `src/lib/quadro-client.ts` to import from `../../.quadrokit/generated/client.gen.js`.
|
|
30
|
+
3. Removes `@quadrokit/sample-client` and rewrites other `workspace:*` deps to published semver (`@quadrokit/client` → `^0.2.1`, other `@quadrokit/*` → `^0.2.0`) unless `--keep-workspace`.
|
|
31
|
+
4. Writes `QUADROKIT.md` with proxy and `quadrokit:generate` instructions. Does **not** copy `.quadrokit/generated` — run `bun run quadrokit:generate` after install.
|
|
32
|
+
5. Optionally runs `git init` and installs dependencies (`bun` if available, otherwise `npm`).
|
|
29
33
|
|
|
30
34
|
## Published usage (future)
|
|
31
35
|
|
|
@@ -1,8 +1,57 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
2
3
|
import { cp, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import prompts from 'prompts';
|
|
7
|
+
const MIN_NODE_MAJOR = 18;
|
|
8
|
+
function cmdVersion(cmd, args) {
|
|
9
|
+
const r = spawnSync(cmd, args, { encoding: 'utf8', shell: false });
|
|
10
|
+
if (r.error || r.status !== 0)
|
|
11
|
+
return null;
|
|
12
|
+
const line = r.stdout.trim().split('\n')[0];
|
|
13
|
+
return line || null;
|
|
14
|
+
}
|
|
15
|
+
function nodeMajor() {
|
|
16
|
+
const m = /^v?(\d+)/.exec(process.version);
|
|
17
|
+
return m ? Number(m[1]) : 0;
|
|
18
|
+
}
|
|
19
|
+
function checkPrereqs() {
|
|
20
|
+
const nodeVersion = process.version;
|
|
21
|
+
const nodeOk = nodeMajor() >= MIN_NODE_MAJOR;
|
|
22
|
+
const bunVersion = cmdVersion('bun', ['--version']);
|
|
23
|
+
const gitVersion = cmdVersion('git', ['--version']);
|
|
24
|
+
return {
|
|
25
|
+
nodeOk,
|
|
26
|
+
nodeVersion,
|
|
27
|
+
bunOk: bunVersion !== null,
|
|
28
|
+
bunVersion,
|
|
29
|
+
gitOk: gitVersion !== null,
|
|
30
|
+
gitVersion,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function printWelcome() {
|
|
34
|
+
console.log(`
|
|
35
|
+
${'✨'.repeat(2)} ${'\u001b[1mcreate-quadrokit\u001b[0m'} ${'✨'.repeat(2)}
|
|
36
|
+
${'🚀'.repeat(2)} Scaffold a QuadroKit Vite + React app for 4D REST
|
|
37
|
+
`);
|
|
38
|
+
}
|
|
39
|
+
function printPrereqReport(p) {
|
|
40
|
+
console.log(' Prerequisites');
|
|
41
|
+
const line = (ok, emoji, label, detail) => {
|
|
42
|
+
const mark = ok ? '✅' : '⚠️ ';
|
|
43
|
+
console.log(` ${mark} ${emoji} ${label}: ${detail}`);
|
|
44
|
+
};
|
|
45
|
+
line(p.nodeOk, '📗', 'Node.js', p.nodeOk ? p.nodeVersion : `${p.nodeVersion} (need >= ${MIN_NODE_MAJOR})`);
|
|
46
|
+
line(p.bunOk, '🥟', 'Bun', p.bunVersion ?? 'not found (you can use npm install instead)');
|
|
47
|
+
line(p.gitOk, '📌', 'Git', p.gitVersion ?? 'not found (needed only to init a repository)');
|
|
48
|
+
console.log('');
|
|
49
|
+
}
|
|
50
|
+
async function runInDir(cwd, cmd, args) {
|
|
51
|
+
const shell = process.platform === 'win32';
|
|
52
|
+
const r = spawnSync(cmd, args, { cwd, stdio: 'inherit', shell });
|
|
53
|
+
return r.status === 0;
|
|
54
|
+
}
|
|
6
55
|
const TEMPLATES = ['dashboard', 'website', 'ecommerce', 'admin-shell'];
|
|
7
56
|
/** Directory of the create-quadrokit package (works when installed under node_modules). */
|
|
8
57
|
function packageRoot() {
|
|
@@ -32,22 +81,13 @@ async function resolveBiomeJsonPath() {
|
|
|
32
81
|
}
|
|
33
82
|
return null;
|
|
34
83
|
}
|
|
35
|
-
async function resolveGeneratedClientDir() {
|
|
36
|
-
const pkg = packageRoot();
|
|
37
|
-
const bundled = path.join(pkg, 'vendor', 'generated');
|
|
38
|
-
if (await pathExists(bundled)) {
|
|
39
|
-
return bundled;
|
|
40
|
-
}
|
|
41
|
-
const monorepo = path.join(pkg, '..', 'packages', 'sample-client', 'generated');
|
|
42
|
-
if (await pathExists(monorepo)) {
|
|
43
|
-
return monorepo;
|
|
44
|
-
}
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
84
|
function parseArgs(argv) {
|
|
48
85
|
let template;
|
|
49
86
|
let dir;
|
|
50
87
|
let keepWorkspace = false;
|
|
88
|
+
/** `undefined` = prompt later */
|
|
89
|
+
let initGit;
|
|
90
|
+
let installDeps;
|
|
51
91
|
for (let i = 0; i < argv.length; i++) {
|
|
52
92
|
const a = argv[i];
|
|
53
93
|
if (a === '--template' && argv[i + 1]) {
|
|
@@ -59,8 +99,20 @@ function parseArgs(argv) {
|
|
|
59
99
|
else if (a === '--keep-workspace') {
|
|
60
100
|
keepWorkspace = true;
|
|
61
101
|
}
|
|
102
|
+
else if (a === '--git') {
|
|
103
|
+
initGit = true;
|
|
104
|
+
}
|
|
105
|
+
else if (a === '--no-git') {
|
|
106
|
+
initGit = false;
|
|
107
|
+
}
|
|
108
|
+
else if (a === '--install') {
|
|
109
|
+
installDeps = true;
|
|
110
|
+
}
|
|
111
|
+
else if (a === '--no-install') {
|
|
112
|
+
installDeps = false;
|
|
113
|
+
}
|
|
62
114
|
}
|
|
63
|
-
return { template, dir, keepWorkspace };
|
|
115
|
+
return { template, dir, keepWorkspace, initGit, installDeps };
|
|
64
116
|
}
|
|
65
117
|
async function pathExists(p) {
|
|
66
118
|
try {
|
|
@@ -71,11 +123,22 @@ async function pathExists(p) {
|
|
|
71
123
|
return false;
|
|
72
124
|
}
|
|
73
125
|
}
|
|
126
|
+
const SKIP_TEMPLATE_NAMES = new Set([
|
|
127
|
+
'node_modules',
|
|
128
|
+
'dist',
|
|
129
|
+
'.turbo',
|
|
130
|
+
'.quadrokit',
|
|
131
|
+
'bun.lock',
|
|
132
|
+
'bun.lockb',
|
|
133
|
+
'package-lock.json',
|
|
134
|
+
'pnpm-lock.yaml',
|
|
135
|
+
'yarn.lock',
|
|
136
|
+
]);
|
|
74
137
|
async function copyTemplate(src, dest) {
|
|
75
138
|
await mkdir(dest, { recursive: true });
|
|
76
139
|
const entries = await readdir(src, { withFileTypes: true });
|
|
77
140
|
for (const e of entries) {
|
|
78
|
-
if (
|
|
141
|
+
if (SKIP_TEMPLATE_NAMES.has(e.name)) {
|
|
79
142
|
continue;
|
|
80
143
|
}
|
|
81
144
|
const from = path.join(src, e.name);
|
|
@@ -96,7 +159,7 @@ function rewriteWorkspaceDeps(pkg) {
|
|
|
96
159
|
}
|
|
97
160
|
for (const name of Object.keys(deps)) {
|
|
98
161
|
if (deps[name] === 'workspace:*') {
|
|
99
|
-
deps[name] = '^0.2.0';
|
|
162
|
+
deps[name] = name === '@quadrokit/client' ? '^0.2.1' : '^0.2.0';
|
|
100
163
|
}
|
|
101
164
|
}
|
|
102
165
|
}
|
|
@@ -141,23 +204,13 @@ async function writeStandaloneBiome(dest, keepWorkspace) {
|
|
|
141
204
|
}
|
|
142
205
|
async function writeQuadroClientImport(dest) {
|
|
143
206
|
const p = path.join(dest, 'src', 'lib', 'quadro-client.ts');
|
|
144
|
-
const body = `import { createClient } from '../../.quadrokit/generated/client.gen.
|
|
207
|
+
const body = `import { createClient } from '../../.quadrokit/generated/client.gen.mjs';
|
|
145
208
|
|
|
146
209
|
/** Same-origin \`/rest\` in dev (Vite proxy) and production (reverse proxy). */
|
|
147
210
|
export const quadro = createClient({ baseURL: '/rest' });
|
|
148
211
|
`;
|
|
149
212
|
await writeFile(p, body, 'utf8');
|
|
150
213
|
}
|
|
151
|
-
async function seedGenerated(dest) {
|
|
152
|
-
const genSrc = await resolveGeneratedClientDir();
|
|
153
|
-
const genDest = path.join(dest, '.quadrokit', 'generated');
|
|
154
|
-
if (!genSrc) {
|
|
155
|
-
console.warn('Warning: sample generated client not found — run `quadrokit-client generate` after install.');
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
await mkdir(path.join(dest, '.quadrokit'), { recursive: true });
|
|
159
|
-
await cp(genSrc, genDest, { recursive: true });
|
|
160
|
-
}
|
|
161
214
|
async function writeReadme(dest, template) {
|
|
162
215
|
const text = `# ${path.basename(dest)}
|
|
163
216
|
|
|
@@ -165,14 +218,20 @@ Created with **create-quadrokit** (template: \`${template}\`).
|
|
|
165
218
|
|
|
166
219
|
## Next steps
|
|
167
220
|
|
|
168
|
-
1. \`cd ${path.basename(dest)}\` and \`bun install\`
|
|
169
|
-
2.
|
|
170
|
-
|
|
221
|
+
1. \`cd ${path.basename(dest)}\` and install dependencies (\`bun install\` or \`npm install\`).
|
|
222
|
+
2. Generate the typed REST client (required before \`dev\` / \`build\`):
|
|
223
|
+
|
|
224
|
+
\`\`\`bash
|
|
225
|
+
bun run quadrokit:generate
|
|
226
|
+
\`\`\`
|
|
227
|
+
|
|
228
|
+
This uses \`assets/catalog.placeholder.json\` by default (demo catalog from QuadroKit). Point at your live 4D catalog:
|
|
171
229
|
|
|
172
230
|
\`\`\`bash
|
|
173
|
-
|
|
231
|
+
QUADROKIT_CATALOG_URL='http://localhost:7080/rest/\\$catalog' QUADROKIT_CATALOG_TOKEN='…' bun run quadrokit:generate
|
|
174
232
|
\`\`\`
|
|
175
233
|
|
|
234
|
+
3. Copy \`.env.example\` to \`.env\` and set \`VITE_4D_ORIGIN\` to your 4D web server.
|
|
176
235
|
4. \`bun run dev\` — the dev server proxies \`/rest\` to \`VITE_4D_ORIGIN\` so **4DSID_** cookies stay same-origin.
|
|
177
236
|
|
|
178
237
|
Production: serve the SPA and reverse-proxy \`/rest\` to 4D on the **same host** as the UI.
|
|
@@ -181,7 +240,14 @@ Production: serve the SPA and reverse-proxy \`/rest\` to 4D on the **same host**
|
|
|
181
240
|
}
|
|
182
241
|
async function main() {
|
|
183
242
|
const argv = process.argv.slice(2);
|
|
184
|
-
const { template: tArg, dir: dirArg, keepWorkspace } = parseArgs(argv);
|
|
243
|
+
const { template: tArg, dir: dirArg, keepWorkspace, initGit: gitFlag, installDeps: installFlag, } = parseArgs(argv);
|
|
244
|
+
printWelcome();
|
|
245
|
+
const prereqs = checkPrereqs();
|
|
246
|
+
printPrereqReport(prereqs);
|
|
247
|
+
if (!prereqs.nodeOk) {
|
|
248
|
+
console.error(` ❌ Node.js ${MIN_NODE_MAJOR}+ is required.`);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
185
251
|
const template = tArg && TEMPLATES.includes(tArg)
|
|
186
252
|
? tArg
|
|
187
253
|
: (await prompts({
|
|
@@ -218,19 +284,75 @@ async function main() {
|
|
|
218
284
|
}
|
|
219
285
|
await copyTemplate(src, dest);
|
|
220
286
|
await writeStandaloneBiome(dest, keepWorkspace);
|
|
221
|
-
await seedGenerated(dest);
|
|
222
287
|
await patchPackageJson(dest, path
|
|
223
288
|
.basename(dest)
|
|
224
289
|
.replace(/[^a-z0-9-]/gi, '-')
|
|
225
290
|
.toLowerCase() || 'quadro-app', keepWorkspace);
|
|
226
291
|
await writeQuadroClientImport(dest);
|
|
227
292
|
await writeReadme(dest, template);
|
|
228
|
-
console.log(`
|
|
229
|
-
|
|
293
|
+
console.log(`\n 🎉 Project files are ready at ${'\u001b[1m'}${dest}${'\u001b[0m'}\n`);
|
|
294
|
+
let initGit = gitFlag;
|
|
295
|
+
if (initGit === undefined) {
|
|
296
|
+
const g = await prompts({
|
|
297
|
+
type: 'confirm',
|
|
298
|
+
name: 'v',
|
|
299
|
+
message: 'Initialize a git repository here?',
|
|
300
|
+
initial: true,
|
|
301
|
+
});
|
|
302
|
+
if (typeof g.v !== 'boolean') {
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
initGit = g.v;
|
|
306
|
+
}
|
|
307
|
+
if (initGit) {
|
|
308
|
+
if (!prereqs.gitOk) {
|
|
309
|
+
console.log(' ⚠️ Skipping git init — Git is not available on your PATH.\n');
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
const ok = await runInDir(dest, 'git', ['init']);
|
|
313
|
+
if (ok) {
|
|
314
|
+
console.log(' ✅ Git repository initialized.\n');
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
console.log(' ⚠️ `git init` failed — you can run it manually later.\n');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
let installDeps = installFlag;
|
|
322
|
+
if (installDeps === undefined) {
|
|
323
|
+
const ins = await prompts({
|
|
324
|
+
type: 'confirm',
|
|
325
|
+
name: 'v',
|
|
326
|
+
message: 'Install dependencies now?',
|
|
327
|
+
initial: true,
|
|
328
|
+
});
|
|
329
|
+
if (typeof ins.v !== 'boolean') {
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
installDeps = ins.v;
|
|
333
|
+
}
|
|
334
|
+
if (installDeps) {
|
|
335
|
+
const useBun = prereqs.bunOk;
|
|
336
|
+
const cmd = useBun ? 'bun' : 'npm';
|
|
337
|
+
const args = useBun ? ['install'] : ['install'];
|
|
338
|
+
console.log(` 📦 Running ${cmd} ${args.join(' ')} …\n`);
|
|
339
|
+
const ok = await runInDir(dest, cmd, args);
|
|
340
|
+
if (ok) {
|
|
341
|
+
console.log(` ✅ Dependencies installed with ${cmd}.\n`);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
console.log(` ⚠️ Install failed — run \`${useBun ? 'bun install' : 'npm install'}\` inside the project.\n`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const pm = prereqs.bunOk ? 'bun' : 'npm';
|
|
348
|
+
const runDev = prereqs.bunOk ? 'bun run dev' : 'npm run dev';
|
|
349
|
+
const installLine = installDeps ? ` → ${runDev}` : ` → ${pm} install\n → ${runDev}`;
|
|
350
|
+
console.log(` 🚀🚀 Next steps
|
|
351
|
+
→ cd ${path.basename(dest)}
|
|
352
|
+
${installLine}
|
|
353
|
+
→ Copy .env.example → .env and set VITE_4D_ORIGIN
|
|
230
354
|
|
|
231
|
-
|
|
232
|
-
bun install
|
|
233
|
-
bun run dev
|
|
355
|
+
Happy building! ✨
|
|
234
356
|
`);
|
|
235
357
|
}
|
|
236
358
|
main().catch((e) => {
|
package/package.json
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-quadrokit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Scaffold a QuadroKit Vite + React app from a template",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"bin": "./dist/index.
|
|
6
|
+
"bin": "./dist/index.mjs",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist",
|
|
9
9
|
"templates",
|
|
10
|
-
"vendor",
|
|
11
10
|
"biome.monorepo.json"
|
|
12
11
|
],
|
|
13
12
|
"scripts": {
|
|
14
|
-
"build": "tsc -p tsconfig.build.json",
|
|
13
|
+
"build": "tsc -p tsconfig.build.json && bun ../scripts/rename-dist-js-to-mjs.ts dist",
|
|
15
14
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
16
15
|
"prepublishOnly": "bun run scripts/sync-for-publish.ts && bun run build"
|
|
17
16
|
},
|
package/templates/README.md
CHANGED
|
@@ -34,4 +34,6 @@ Each template has a [`biome.json`](dashboard/biome.json) with `"root": false` an
|
|
|
34
34
|
|
|
35
35
|
## Scaffolding outside the monorepo
|
|
36
36
|
|
|
37
|
-
Use [`create-quadrokit`](../../create-quadrokit/README.md); it copies a template,
|
|
37
|
+
Use [`create-quadrokit`](../../create-quadrokit/README.md); it copies a template, rewrites `workspace:*` to published semver, and skips lockfiles and `.quadrokit/` (so new projects run `bun run quadrokit:generate` after install).
|
|
38
|
+
|
|
39
|
+
For **monorepo** template packages, `.quadrokit/generated` is kept (regenerate with `bun run quadrokit:generate` in a template after changing `assets/catalog.placeholder.json`) so `tsc` can typecheck against the demo catalog.
|