create-quadrokit 0.2.0 → 0.2.2
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} +175 -38
- 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 `workspace:*` on `@quadrokit/*` deps to `^<version>` matching **create-quadrokit**’s own `package.json` `version` (run `bun run version:set` at repo root to bump public packages) 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,58 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
2
4
|
import { cp, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
3
5
|
import path from 'node:path';
|
|
4
6
|
import { fileURLToPath } from 'node:url';
|
|
5
7
|
import prompts from 'prompts';
|
|
8
|
+
const MIN_NODE_MAJOR = 18;
|
|
9
|
+
function cmdVersion(cmd, args) {
|
|
10
|
+
const r = spawnSync(cmd, args, { encoding: 'utf8', shell: false });
|
|
11
|
+
if (r.error || r.status !== 0)
|
|
12
|
+
return null;
|
|
13
|
+
const line = r.stdout.trim().split('\n')[0];
|
|
14
|
+
return line || null;
|
|
15
|
+
}
|
|
16
|
+
function nodeMajor() {
|
|
17
|
+
const m = /^v?(\d+)/.exec(process.version);
|
|
18
|
+
return m ? Number(m[1]) : 0;
|
|
19
|
+
}
|
|
20
|
+
function checkPrereqs() {
|
|
21
|
+
const nodeVersion = process.version;
|
|
22
|
+
const nodeOk = nodeMajor() >= MIN_NODE_MAJOR;
|
|
23
|
+
const bunVersion = cmdVersion('bun', ['--version']);
|
|
24
|
+
const gitVersion = cmdVersion('git', ['--version']);
|
|
25
|
+
return {
|
|
26
|
+
nodeOk,
|
|
27
|
+
nodeVersion,
|
|
28
|
+
bunOk: bunVersion !== null,
|
|
29
|
+
bunVersion,
|
|
30
|
+
gitOk: gitVersion !== null,
|
|
31
|
+
gitVersion,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function printWelcome() {
|
|
35
|
+
console.log(`
|
|
36
|
+
${'✨'.repeat(2)} ${'\u001b[1mcreate-quadrokit\u001b[0m'} ${'✨'.repeat(2)}
|
|
37
|
+
${'🚀'.repeat(2)} Scaffold a QuadroKit Vite + React app for 4D REST
|
|
38
|
+
`);
|
|
39
|
+
}
|
|
40
|
+
function printPrereqReport(p) {
|
|
41
|
+
console.log(' Prerequisites');
|
|
42
|
+
const line = (ok, emoji, label, detail) => {
|
|
43
|
+
const mark = ok ? '✅' : '⚠️ ';
|
|
44
|
+
console.log(` ${mark} ${emoji} ${label}: ${detail}`);
|
|
45
|
+
};
|
|
46
|
+
line(p.nodeOk, '📗', 'Node.js', p.nodeOk ? p.nodeVersion : `${p.nodeVersion} (need >= ${MIN_NODE_MAJOR})`);
|
|
47
|
+
line(p.bunOk, '🥟', 'Bun', p.bunVersion ?? 'not found (you can use npm install instead)');
|
|
48
|
+
line(p.gitOk, '📌', 'Git', p.gitVersion ?? 'not found (needed only to init a repository)');
|
|
49
|
+
console.log('');
|
|
50
|
+
}
|
|
51
|
+
async function runInDir(cwd, cmd, args) {
|
|
52
|
+
const shell = process.platform === 'win32';
|
|
53
|
+
const r = spawnSync(cmd, args, { cwd, stdio: 'inherit', shell });
|
|
54
|
+
return r.status === 0;
|
|
55
|
+
}
|
|
6
56
|
const TEMPLATES = ['dashboard', 'website', 'ecommerce', 'admin-shell'];
|
|
7
57
|
/** Directory of the create-quadrokit package (works when installed under node_modules). */
|
|
8
58
|
function packageRoot() {
|
|
@@ -32,22 +82,13 @@ async function resolveBiomeJsonPath() {
|
|
|
32
82
|
}
|
|
33
83
|
return null;
|
|
34
84
|
}
|
|
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
85
|
function parseArgs(argv) {
|
|
48
86
|
let template;
|
|
49
87
|
let dir;
|
|
50
88
|
let keepWorkspace = false;
|
|
89
|
+
/** `undefined` = prompt later */
|
|
90
|
+
let initGit;
|
|
91
|
+
let installDeps;
|
|
51
92
|
for (let i = 0; i < argv.length; i++) {
|
|
52
93
|
const a = argv[i];
|
|
53
94
|
if (a === '--template' && argv[i + 1]) {
|
|
@@ -59,8 +100,20 @@ function parseArgs(argv) {
|
|
|
59
100
|
else if (a === '--keep-workspace') {
|
|
60
101
|
keepWorkspace = true;
|
|
61
102
|
}
|
|
103
|
+
else if (a === '--git') {
|
|
104
|
+
initGit = true;
|
|
105
|
+
}
|
|
106
|
+
else if (a === '--no-git') {
|
|
107
|
+
initGit = false;
|
|
108
|
+
}
|
|
109
|
+
else if (a === '--install') {
|
|
110
|
+
installDeps = true;
|
|
111
|
+
}
|
|
112
|
+
else if (a === '--no-install') {
|
|
113
|
+
installDeps = false;
|
|
114
|
+
}
|
|
62
115
|
}
|
|
63
|
-
return { template, dir, keepWorkspace };
|
|
116
|
+
return { template, dir, keepWorkspace, initGit, installDeps };
|
|
64
117
|
}
|
|
65
118
|
async function pathExists(p) {
|
|
66
119
|
try {
|
|
@@ -71,11 +124,22 @@ async function pathExists(p) {
|
|
|
71
124
|
return false;
|
|
72
125
|
}
|
|
73
126
|
}
|
|
127
|
+
const SKIP_TEMPLATE_NAMES = new Set([
|
|
128
|
+
'node_modules',
|
|
129
|
+
'dist',
|
|
130
|
+
'.turbo',
|
|
131
|
+
'.quadrokit',
|
|
132
|
+
'bun.lock',
|
|
133
|
+
'bun.lockb',
|
|
134
|
+
'package-lock.json',
|
|
135
|
+
'pnpm-lock.yaml',
|
|
136
|
+
'yarn.lock',
|
|
137
|
+
]);
|
|
74
138
|
async function copyTemplate(src, dest) {
|
|
75
139
|
await mkdir(dest, { recursive: true });
|
|
76
140
|
const entries = await readdir(src, { withFileTypes: true });
|
|
77
141
|
for (const e of entries) {
|
|
78
|
-
if (
|
|
142
|
+
if (SKIP_TEMPLATE_NAMES.has(e.name)) {
|
|
79
143
|
continue;
|
|
80
144
|
}
|
|
81
145
|
const from = path.join(src, e.name);
|
|
@@ -88,15 +152,29 @@ async function copyTemplate(src, dest) {
|
|
|
88
152
|
}
|
|
89
153
|
}
|
|
90
154
|
}
|
|
155
|
+
/** Caret range for published @quadrokit/* deps (matches create-quadrokit release line). */
|
|
156
|
+
function quadrokitPublishedSemverRange() {
|
|
157
|
+
try {
|
|
158
|
+
const raw = readFileSync(path.join(packageRoot(), 'package.json'), 'utf8');
|
|
159
|
+
const v = JSON.parse(raw).version;
|
|
160
|
+
if (v)
|
|
161
|
+
return `^${v}`;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
/* ignore */
|
|
165
|
+
}
|
|
166
|
+
return '^0.0.0';
|
|
167
|
+
}
|
|
91
168
|
function rewriteWorkspaceDeps(pkg) {
|
|
169
|
+
const range = quadrokitPublishedSemverRange();
|
|
92
170
|
for (const key of ['dependencies', 'devDependencies']) {
|
|
93
171
|
const deps = pkg[key];
|
|
94
172
|
if (!deps) {
|
|
95
173
|
continue;
|
|
96
174
|
}
|
|
97
175
|
for (const name of Object.keys(deps)) {
|
|
98
|
-
if (deps[name] === 'workspace:*') {
|
|
99
|
-
deps[name] =
|
|
176
|
+
if (deps[name] === 'workspace:*' && name.startsWith('@quadrokit/')) {
|
|
177
|
+
deps[name] = range;
|
|
100
178
|
}
|
|
101
179
|
}
|
|
102
180
|
}
|
|
@@ -141,23 +219,13 @@ async function writeStandaloneBiome(dest, keepWorkspace) {
|
|
|
141
219
|
}
|
|
142
220
|
async function writeQuadroClientImport(dest) {
|
|
143
221
|
const p = path.join(dest, 'src', 'lib', 'quadro-client.ts');
|
|
144
|
-
const body = `import { createClient } from '../../.quadrokit/generated/client.gen.
|
|
222
|
+
const body = `import { createClient } from '../../.quadrokit/generated/client.gen.mjs';
|
|
145
223
|
|
|
146
224
|
/** Same-origin \`/rest\` in dev (Vite proxy) and production (reverse proxy). */
|
|
147
225
|
export const quadro = createClient({ baseURL: '/rest' });
|
|
148
226
|
`;
|
|
149
227
|
await writeFile(p, body, 'utf8');
|
|
150
228
|
}
|
|
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
229
|
async function writeReadme(dest, template) {
|
|
162
230
|
const text = `# ${path.basename(dest)}
|
|
163
231
|
|
|
@@ -165,14 +233,20 @@ Created with **create-quadrokit** (template: \`${template}\`).
|
|
|
165
233
|
|
|
166
234
|
## Next steps
|
|
167
235
|
|
|
168
|
-
1. \`cd ${path.basename(dest)}\` and \`bun install\`
|
|
169
|
-
2.
|
|
170
|
-
|
|
236
|
+
1. \`cd ${path.basename(dest)}\` and install dependencies (\`bun install\` or \`npm install\`).
|
|
237
|
+
2. Generate the typed REST client (required before \`dev\` / \`build\`):
|
|
238
|
+
|
|
239
|
+
\`\`\`bash
|
|
240
|
+
bun run quadrokit:generate
|
|
241
|
+
\`\`\`
|
|
242
|
+
|
|
243
|
+
This uses \`assets/catalog.placeholder.json\` by default (demo catalog from QuadroKit). Point at your live 4D catalog:
|
|
171
244
|
|
|
172
245
|
\`\`\`bash
|
|
173
|
-
|
|
246
|
+
QUADROKIT_CATALOG_URL='http://localhost:7080/rest/\\$catalog' QUADROKIT_CATALOG_TOKEN='…' bun run quadrokit:generate
|
|
174
247
|
\`\`\`
|
|
175
248
|
|
|
249
|
+
3. Copy \`.env.example\` to \`.env\` and set \`VITE_4D_ORIGIN\` to your 4D web server.
|
|
176
250
|
4. \`bun run dev\` — the dev server proxies \`/rest\` to \`VITE_4D_ORIGIN\` so **4DSID_** cookies stay same-origin.
|
|
177
251
|
|
|
178
252
|
Production: serve the SPA and reverse-proxy \`/rest\` to 4D on the **same host** as the UI.
|
|
@@ -181,7 +255,14 @@ Production: serve the SPA and reverse-proxy \`/rest\` to 4D on the **same host**
|
|
|
181
255
|
}
|
|
182
256
|
async function main() {
|
|
183
257
|
const argv = process.argv.slice(2);
|
|
184
|
-
const { template: tArg, dir: dirArg, keepWorkspace } = parseArgs(argv);
|
|
258
|
+
const { template: tArg, dir: dirArg, keepWorkspace, initGit: gitFlag, installDeps: installFlag, } = parseArgs(argv);
|
|
259
|
+
printWelcome();
|
|
260
|
+
const prereqs = checkPrereqs();
|
|
261
|
+
printPrereqReport(prereqs);
|
|
262
|
+
if (!prereqs.nodeOk) {
|
|
263
|
+
console.error(` ❌ Node.js ${MIN_NODE_MAJOR}+ is required.`);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
185
266
|
const template = tArg && TEMPLATES.includes(tArg)
|
|
186
267
|
? tArg
|
|
187
268
|
: (await prompts({
|
|
@@ -218,19 +299,75 @@ async function main() {
|
|
|
218
299
|
}
|
|
219
300
|
await copyTemplate(src, dest);
|
|
220
301
|
await writeStandaloneBiome(dest, keepWorkspace);
|
|
221
|
-
await seedGenerated(dest);
|
|
222
302
|
await patchPackageJson(dest, path
|
|
223
303
|
.basename(dest)
|
|
224
304
|
.replace(/[^a-z0-9-]/gi, '-')
|
|
225
305
|
.toLowerCase() || 'quadro-app', keepWorkspace);
|
|
226
306
|
await writeQuadroClientImport(dest);
|
|
227
307
|
await writeReadme(dest, template);
|
|
228
|
-
console.log(`
|
|
229
|
-
|
|
308
|
+
console.log(`\n 🎉 Project files are ready at ${'\u001b[1m'}${dest}${'\u001b[0m'}\n`);
|
|
309
|
+
let initGit = gitFlag;
|
|
310
|
+
if (initGit === undefined) {
|
|
311
|
+
const g = await prompts({
|
|
312
|
+
type: 'confirm',
|
|
313
|
+
name: 'v',
|
|
314
|
+
message: 'Initialize a git repository here?',
|
|
315
|
+
initial: true,
|
|
316
|
+
});
|
|
317
|
+
if (typeof g.v !== 'boolean') {
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
initGit = g.v;
|
|
321
|
+
}
|
|
322
|
+
if (initGit) {
|
|
323
|
+
if (!prereqs.gitOk) {
|
|
324
|
+
console.log(' ⚠️ Skipping git init — Git is not available on your PATH.\n');
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
const ok = await runInDir(dest, 'git', ['init']);
|
|
328
|
+
if (ok) {
|
|
329
|
+
console.log(' ✅ Git repository initialized.\n');
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
console.log(' ⚠️ `git init` failed — you can run it manually later.\n');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
let installDeps = installFlag;
|
|
337
|
+
if (installDeps === undefined) {
|
|
338
|
+
const ins = await prompts({
|
|
339
|
+
type: 'confirm',
|
|
340
|
+
name: 'v',
|
|
341
|
+
message: 'Install dependencies now?',
|
|
342
|
+
initial: true,
|
|
343
|
+
});
|
|
344
|
+
if (typeof ins.v !== 'boolean') {
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
installDeps = ins.v;
|
|
348
|
+
}
|
|
349
|
+
if (installDeps) {
|
|
350
|
+
const useBun = prereqs.bunOk;
|
|
351
|
+
const cmd = useBun ? 'bun' : 'npm';
|
|
352
|
+
const args = useBun ? ['install'] : ['install'];
|
|
353
|
+
console.log(` 📦 Running ${cmd} ${args.join(' ')} …\n`);
|
|
354
|
+
const ok = await runInDir(dest, cmd, args);
|
|
355
|
+
if (ok) {
|
|
356
|
+
console.log(` ✅ Dependencies installed with ${cmd}.\n`);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
console.log(` ⚠️ Install failed — run \`${useBun ? 'bun install' : 'npm install'}\` inside the project.\n`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
const pm = prereqs.bunOk ? 'bun' : 'npm';
|
|
363
|
+
const runDev = prereqs.bunOk ? 'bun run dev' : 'npm run dev';
|
|
364
|
+
const installLine = installDeps ? ` → ${runDev}` : ` → ${pm} install\n → ${runDev}`;
|
|
365
|
+
console.log(` 🚀🚀 Next steps
|
|
366
|
+
→ cd ${path.basename(dest)}
|
|
367
|
+
${installLine}
|
|
368
|
+
→ Copy .env.example → .env and set VITE_4D_ORIGIN
|
|
230
369
|
|
|
231
|
-
|
|
232
|
-
bun install
|
|
233
|
-
bun run dev
|
|
370
|
+
Happy building! ✨
|
|
234
371
|
`);
|
|
235
372
|
}
|
|
236
373
|
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.2",
|
|
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.
|