create-quadrokit 0.2.2 → 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 +13 -6
- package/dist/copy-tree.mjs +33 -0
- package/dist/index.mjs +205 -75
- package/dist/inject-quadrokit-generate.mjs +10 -0
- package/package.json +4 -2
- package/template-common/.cursor/rules/commitlint-conventional.mdc +47 -0
- package/template-common/.env.example +7 -0
- package/template-common/biome.json +12 -0
- package/template-common/postcss.config.js +6 -0
- package/template-common/src/vite-env.d.ts +9 -0
- package/template-common/tsconfig.app.json +16 -0
- package/template-common/tsconfig.base.json +20 -0
- package/template-common/tsconfig.json +4 -0
- package/template-common/tsconfig.node.json +10 -0
- package/templates/admin-shell/.env.example +5 -0
- package/templates/admin-shell/package.json +1 -1
- package/templates/admin-shell/tsconfig.app.json +1 -1
- package/templates/admin-shell/tsconfig.base.json +20 -0
- package/templates/admin-shell/tsconfig.node.json +1 -1
- package/templates/dashboard/.env.example +5 -0
- package/templates/dashboard/package.json +1 -1
- package/templates/dashboard/tsconfig.app.json +1 -1
- package/templates/dashboard/tsconfig.base.json +20 -0
- package/templates/dashboard/tsconfig.node.json +1 -1
- package/templates/ecommerce/.env.example +5 -0
- package/templates/ecommerce/package.json +1 -1
- package/templates/ecommerce/tsconfig.app.json +1 -1
- package/templates/ecommerce/tsconfig.base.json +20 -0
- package/templates/ecommerce/tsconfig.node.json +1 -1
- package/templates/website/.env.example +5 -0
- package/templates/website/package.json +1 -1
- package/templates/website/tsconfig.app.json +1 -1
- package/templates/website/tsconfig.base.json +20 -0
- package/templates/website/tsconfig.node.json +1 -1
package/README.md
CHANGED
|
@@ -14,22 +14,29 @@ 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
|
|
|
28
33
|
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.
|
|
30
|
-
3.
|
|
31
|
-
4.
|
|
32
|
-
5.
|
|
34
|
+
2. Overlays **`template-common/`** (shared across all templates): `.env.example`, `tsconfig*.json`, `postcss.config.js`, `biome.json`, `src/vite-env.d.ts`, `.cursor/rules/commitlint-conventional.mdc`, etc. In the monorepo, run `bun run sync:template-common` before template `build`/`typecheck` (also runs as part of root `bun run build`).
|
|
35
|
+
3. Rewrites `src/lib/quadro-client.ts` to import from `../../.quadrokit/generated/client.gen.js`.
|
|
36
|
+
4. 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`.
|
|
37
|
+
5. Writes `QUADROKIT.md` with proxy and `quadrokit:generate` instructions. Does **not** copy `.quadrokit/generated` — run `bun run quadrokit:generate` after install.
|
|
38
|
+
6. Adds a root `.gitignore` (unless the template already shipped one).
|
|
39
|
+
7. Optionally runs `git init` and installs dependencies (`bun` if available, otherwise `npm`).
|
|
33
40
|
|
|
34
41
|
## Published usage (future)
|
|
35
42
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { cp, mkdir, readdir } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/** Names skipped when copying templates or template-common (lockfiles, build output). */
|
|
4
|
+
export const TEMPLATE_COPY_SKIP = new Set([
|
|
5
|
+
'node_modules',
|
|
6
|
+
'dist',
|
|
7
|
+
'.turbo',
|
|
8
|
+
'.quadrokit',
|
|
9
|
+
'bun.lock',
|
|
10
|
+
'bun.lockb',
|
|
11
|
+
'package-lock.json',
|
|
12
|
+
'pnpm-lock.yaml',
|
|
13
|
+
'yarn.lock',
|
|
14
|
+
]);
|
|
15
|
+
export async function copyTree(src, dest, options = {}) {
|
|
16
|
+
const skip = options.skipNames ?? TEMPLATE_COPY_SKIP;
|
|
17
|
+
const { dereference = false } = options;
|
|
18
|
+
await mkdir(dest, { recursive: true });
|
|
19
|
+
const entries = await readdir(src, { withFileTypes: true });
|
|
20
|
+
for (const e of entries) {
|
|
21
|
+
if (skip.has(e.name)) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const from = path.join(src, e.name);
|
|
25
|
+
const to = path.join(dest, e.name);
|
|
26
|
+
if (e.isDirectory()) {
|
|
27
|
+
await copyTree(from, to, options);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
await cp(from, to, { force: true, dereference });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
3
|
import { readFileSync } from 'node:fs';
|
|
4
|
-
import {
|
|
4
|
+
import { readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import prompts from 'prompts';
|
|
8
|
+
import { copyTree, TEMPLATE_COPY_SKIP } from './copy-tree.mjs';
|
|
8
9
|
const MIN_NODE_MAJOR = 18;
|
|
9
10
|
function cmdVersion(cmd, args) {
|
|
10
11
|
const r = spawnSync(cmd, args, { encoding: 'utf8', shell: false });
|
|
@@ -31,12 +32,49 @@ function checkPrereqs() {
|
|
|
31
32
|
gitVersion,
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
|
-
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) {
|
|
35
46
|
console.log(`
|
|
36
|
-
${'✨'.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)}
|
|
37
48
|
${'🚀'.repeat(2)} Scaffold a QuadroKit Vite + React app for 4D REST
|
|
38
49
|
`);
|
|
39
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
|
+
}
|
|
40
78
|
function printPrereqReport(p) {
|
|
41
79
|
console.log(' Prerequisites');
|
|
42
80
|
const line = (ok, emoji, label, detail) => {
|
|
@@ -85,8 +123,11 @@ async function resolveBiomeJsonPath() {
|
|
|
85
123
|
function parseArgs(argv) {
|
|
86
124
|
let template;
|
|
87
125
|
let dir;
|
|
126
|
+
let name;
|
|
88
127
|
let keepWorkspace = false;
|
|
89
|
-
/**
|
|
128
|
+
/** Non-interactive: default project dir, git init, and install (no prompts). */
|
|
129
|
+
let yes = false;
|
|
130
|
+
/** `undefined` = prompt later (unless yes) */
|
|
90
131
|
let initGit;
|
|
91
132
|
let installDeps;
|
|
92
133
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -97,9 +138,15 @@ function parseArgs(argv) {
|
|
|
97
138
|
else if (a === '--dir' && argv[i + 1]) {
|
|
98
139
|
dir = argv[++i];
|
|
99
140
|
}
|
|
141
|
+
else if (a === '--name' && argv[i + 1]) {
|
|
142
|
+
name = argv[++i];
|
|
143
|
+
}
|
|
100
144
|
else if (a === '--keep-workspace') {
|
|
101
145
|
keepWorkspace = true;
|
|
102
146
|
}
|
|
147
|
+
else if (a === '-y' || a === '--yes') {
|
|
148
|
+
yes = true;
|
|
149
|
+
}
|
|
103
150
|
else if (a === '--git') {
|
|
104
151
|
initGit = true;
|
|
105
152
|
}
|
|
@@ -113,7 +160,10 @@ function parseArgs(argv) {
|
|
|
113
160
|
installDeps = false;
|
|
114
161
|
}
|
|
115
162
|
}
|
|
116
|
-
|
|
163
|
+
if (!dir && name) {
|
|
164
|
+
dir = name;
|
|
165
|
+
}
|
|
166
|
+
return { template, dir, keepWorkspace, yes, initGit, installDeps };
|
|
117
167
|
}
|
|
118
168
|
async function pathExists(p) {
|
|
119
169
|
try {
|
|
@@ -124,34 +174,80 @@ async function pathExists(p) {
|
|
|
124
174
|
return false;
|
|
125
175
|
}
|
|
126
176
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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;
|
|
143
224
|
continue;
|
|
144
225
|
}
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
await copyTemplate(from, to);
|
|
226
|
+
const files = await readdir(dest);
|
|
227
|
+
if (files.length === 0) {
|
|
228
|
+
return dest;
|
|
149
229
|
}
|
|
150
|
-
|
|
151
|
-
|
|
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);
|
|
152
233
|
}
|
|
234
|
+
console.log(` ⚠️ Directory not empty: ${dest}`);
|
|
235
|
+
dirInput = undefined;
|
|
236
|
+
retryAfterConflict = true;
|
|
153
237
|
}
|
|
154
238
|
}
|
|
239
|
+
async function copyTemplate(src, dest) {
|
|
240
|
+
await copyTree(src, dest, { skipNames: TEMPLATE_COPY_SKIP });
|
|
241
|
+
}
|
|
242
|
+
/** Overlay shared files from template-common (same as monorepo packages/templates sync). */
|
|
243
|
+
async function injectTemplateCommon(dest) {
|
|
244
|
+
const common = path.join(packageRoot(), 'template-common');
|
|
245
|
+
if (!(await pathExists(common))) {
|
|
246
|
+
console.warn(' ⚠️ template-common missing — skipping common file overlay.');
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
await copyTree(common, dest, { skipNames: TEMPLATE_COPY_SKIP });
|
|
250
|
+
}
|
|
155
251
|
/** Caret range for published @quadrokit/* deps (matches create-quadrokit release line). */
|
|
156
252
|
function quadrokitPublishedSemverRange() {
|
|
157
253
|
try {
|
|
@@ -190,6 +286,10 @@ async function patchPackageJson(dest, projectName, keepWorkspace) {
|
|
|
190
286
|
if (!keepWorkspace) {
|
|
191
287
|
rewriteWorkspaceDeps(pkg);
|
|
192
288
|
}
|
|
289
|
+
const scripts = pkg.scripts;
|
|
290
|
+
if (scripts) {
|
|
291
|
+
scripts['quadrokit:generate'] = 'quadrokit-client generate';
|
|
292
|
+
}
|
|
193
293
|
await writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8');
|
|
194
294
|
}
|
|
195
295
|
/** Standalone Biome config for projects outside the monorepo (no `extends` to repo root). */
|
|
@@ -234,70 +334,89 @@ Created with **create-quadrokit** (template: \`${template}\`).
|
|
|
234
334
|
## Next steps
|
|
235
335
|
|
|
236
336
|
1. \`cd ${path.basename(dest)}\` and install dependencies (\`bun install\` or \`npm install\`).
|
|
237
|
-
2.
|
|
337
|
+
2. Copy \`.env.example\` to \`.env\` and set \`VITE_4D_ORIGIN\` to your 4D web server (used for the dev proxy and as the default catalog origin for generate).
|
|
338
|
+
|
|
339
|
+
3. Generate the typed REST client (required before \`dev\` / \`build\`). The CLI is \`quadrokit-client\` from \`@quadrokit/client\` (reads \`.env\` in the project cwd for defaults):
|
|
238
340
|
|
|
239
341
|
\`\`\`bash
|
|
240
342
|
bun run quadrokit:generate
|
|
241
343
|
\`\`\`
|
|
242
344
|
|
|
243
|
-
|
|
345
|
+
Same as \`quadrokit-client generate\`. One-off (no local install): \`bunx -p @quadrokit/client quadrokit-client generate\` (or \`bunx quadrokit-client generate\`).
|
|
244
346
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
347
|
+
Default catalog URL: \`\${VITE_4D_ORIGIN}/rest/\\$catalog\`, or \`http://127.0.0.1:7080/rest/\\$catalog\` if unset. Generator-only \`.env\` keys (not used by the browser):
|
|
348
|
+
|
|
349
|
+
- \`QUADROKIT_ACCESS_KEY\` — multipart \`accessKey\` to \`/api/login\`; then \`4DAdminSID\` for the catalog request.
|
|
350
|
+
- \`QUADROKIT_LOGIN_URL\` — full login URL if not \`\${catalog origin}/api/login\`.
|
|
351
|
+
|
|
352
|
+
Flags: \`--url\`, \`--access-key\`, \`--login-url\`, \`--token\` / \`QUADROKIT_CATALOG_TOKEN\` (Bearer).
|
|
248
353
|
|
|
249
|
-
3. Copy \`.env.example\` to \`.env\` and set \`VITE_4D_ORIGIN\` to your 4D web server.
|
|
250
354
|
4. \`bun run dev\` — the dev server proxies \`/rest\` to \`VITE_4D_ORIGIN\` so **4DSID_** cookies stay same-origin.
|
|
251
355
|
|
|
252
356
|
Production: serve the SPA and reverse-proxy \`/rest\` to 4D on the **same host** as the UI.
|
|
253
357
|
`;
|
|
254
358
|
await writeFile(path.join(dest, 'QUADROKIT.md'), text, 'utf8');
|
|
255
359
|
}
|
|
360
|
+
async function writeGitignore(dest) {
|
|
361
|
+
const p = path.join(dest, '.gitignore');
|
|
362
|
+
if (await pathExists(p)) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const body = `node_modules
|
|
366
|
+
dist
|
|
367
|
+
.DS_Store
|
|
368
|
+
*.log
|
|
369
|
+
*.local
|
|
370
|
+
.env
|
|
371
|
+
.env.*.local
|
|
372
|
+
|
|
373
|
+
# Vite / tsc
|
|
374
|
+
*.tsbuildinfo
|
|
375
|
+
|
|
376
|
+
# Optional: do not commit generated REST client (see QUADROKIT.md)
|
|
377
|
+
# .quadrokit/generated/
|
|
378
|
+
`;
|
|
379
|
+
await writeFile(p, body, 'utf8');
|
|
380
|
+
}
|
|
256
381
|
async function main() {
|
|
257
382
|
const argv = process.argv.slice(2);
|
|
258
|
-
|
|
259
|
-
|
|
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());
|
|
260
389
|
const prereqs = checkPrereqs();
|
|
261
390
|
printPrereqReport(prereqs);
|
|
262
391
|
if (!prereqs.nodeOk) {
|
|
263
392
|
console.error(` ❌ Node.js ${MIN_NODE_MAJOR}+ is required.`);
|
|
264
393
|
process.exit(1);
|
|
265
394
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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({
|
|
269
402
|
type: 'select',
|
|
270
403
|
name: 'template',
|
|
271
404
|
message: 'Template',
|
|
272
405
|
choices: TEMPLATES.map((value) => ({ title: value, value })),
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
process.exit(1);
|
|
406
|
+
});
|
|
407
|
+
template = picked.template;
|
|
276
408
|
}
|
|
277
|
-
|
|
278
|
-
(await prompts({
|
|
279
|
-
type: 'text',
|
|
280
|
-
name: 'dir',
|
|
281
|
-
message: 'Project directory',
|
|
282
|
-
initial: `quadro-${template}`,
|
|
283
|
-
})).dir;
|
|
284
|
-
if (!dirAns || typeof dirAns !== 'string') {
|
|
409
|
+
if (!template) {
|
|
285
410
|
process.exit(1);
|
|
286
411
|
}
|
|
287
|
-
const dest =
|
|
288
|
-
if (await pathExists(dest)) {
|
|
289
|
-
const files = await readdir(dest);
|
|
290
|
-
if (files.length > 0) {
|
|
291
|
-
console.error(`Directory not empty: ${dest}`);
|
|
292
|
-
process.exit(1);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
412
|
+
const dest = await resolveProjectDest(dirArg, template, yesFlag);
|
|
295
413
|
const src = await resolveTemplateDir(template);
|
|
296
414
|
if (!src) {
|
|
297
415
|
console.error(`Template not found: ${template} (expected under create-quadrokit/templates or packages/templates)`);
|
|
298
416
|
process.exit(1);
|
|
299
417
|
}
|
|
300
418
|
await copyTemplate(src, dest);
|
|
419
|
+
await injectTemplateCommon(dest);
|
|
301
420
|
await writeStandaloneBiome(dest, keepWorkspace);
|
|
302
421
|
await patchPackageJson(dest, path
|
|
303
422
|
.basename(dest)
|
|
@@ -305,19 +424,25 @@ async function main() {
|
|
|
305
424
|
.toLowerCase() || 'quadro-app', keepWorkspace);
|
|
306
425
|
await writeQuadroClientImport(dest);
|
|
307
426
|
await writeReadme(dest, template);
|
|
427
|
+
await writeGitignore(dest);
|
|
308
428
|
console.log(`\n 🎉 Project files are ready at ${'\u001b[1m'}${dest}${'\u001b[0m'}\n`);
|
|
309
429
|
let initGit = gitFlag;
|
|
310
430
|
if (initGit === undefined) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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;
|
|
319
445
|
}
|
|
320
|
-
initGit = g.v;
|
|
321
446
|
}
|
|
322
447
|
if (initGit) {
|
|
323
448
|
if (!prereqs.gitOk) {
|
|
@@ -335,16 +460,21 @@ async function main() {
|
|
|
335
460
|
}
|
|
336
461
|
let installDeps = installFlag;
|
|
337
462
|
if (installDeps === undefined) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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;
|
|
346
477
|
}
|
|
347
|
-
installDeps = ins.v;
|
|
348
478
|
}
|
|
349
479
|
if (installDeps) {
|
|
350
480
|
const useBun = prereqs.bunOk;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/** Copy the shared generate runner into the new project (TypeScript; run with `bun`). */
|
|
4
|
+
export async function injectQuadrokitGenerateScript(dest, createPackageRoot) {
|
|
5
|
+
const srcPath = path.join(createPackageRoot, 'scaffold-scripts', 'quadrokit-generate.ts');
|
|
6
|
+
const body = await readFile(srcPath, 'utf8');
|
|
7
|
+
const outDir = path.join(dest, 'scripts');
|
|
8
|
+
await mkdir(outDir, { recursive: true });
|
|
9
|
+
await writeFile(path.join(outDir, 'quadrokit-generate.ts'), body, 'utf8');
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-quadrokit",
|
|
3
|
-
"version": "0.2.
|
|
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",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist",
|
|
9
9
|
"templates",
|
|
10
|
+
"template-common",
|
|
10
11
|
"biome.monorepo.json"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
13
14
|
"build": "tsc -p tsconfig.build.json && bun ../scripts/rename-dist-js-to-mjs.ts dist",
|
|
14
15
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
15
|
-
"prepublishOnly": "bun run scripts/sync-for-publish.ts && bun run build"
|
|
16
|
+
"prepublishOnly": "bun run scripts/sync-template-common-into-packages.ts && bun run scripts/sync-for-publish.ts && bun run build",
|
|
17
|
+
"sync:template-common": "bun run scripts/sync-template-common-into-packages.ts"
|
|
16
18
|
},
|
|
17
19
|
"dependencies": {
|
|
18
20
|
"prompts": "^2.4.2"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Commit messages must follow Conventional Commits (commitlint @commitlint/config-conventional)
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Git commits (Conventional Commits / commitlint)
|
|
7
|
+
|
|
8
|
+
When proposing or writing **commit messages** (including one-line suggestions, full messages, or PR squash titles), use **Conventional Commits** compatible with **@commitlint/config-conventional**.
|
|
9
|
+
|
|
10
|
+
## Header format
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
<type>(<optional-scope>): <short description>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- **type** (pick one): `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`
|
|
17
|
+
- **scope** (optional): lowercase, short package or area (e.g. `client`, `templates`, `ui`, `create-quadrokit`)
|
|
18
|
+
- **description**: imperative mood, **lowercase**, **no trailing period**, **≤ ~72 characters** for the subject line
|
|
19
|
+
|
|
20
|
+
## Breaking changes
|
|
21
|
+
|
|
22
|
+
- Add `!` after type or scope: `feat(api)!: remove legacy login endpoint`
|
|
23
|
+
- Or explain in the body with a line starting with `BREAKING CHANGE:`
|
|
24
|
+
|
|
25
|
+
## Body and footer (when needed)
|
|
26
|
+
|
|
27
|
+
- Separate body from subject with a blank line.
|
|
28
|
+
- Wrap body at ~100 chars.
|
|
29
|
+
- Footer: `Refs: #123` / `Fixes: #123` if applicable.
|
|
30
|
+
|
|
31
|
+
## Examples
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
feat(client): add entity set release helper
|
|
35
|
+
|
|
36
|
+
fix(templates): correct vite proxy target env var
|
|
37
|
+
|
|
38
|
+
chore: bump biome to 2.4.10
|
|
39
|
+
|
|
40
|
+
docs(readme): document ci:quick script
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Do not
|
|
44
|
+
|
|
45
|
+
- Vague subjects: `update`, `fix stuff`, `wip`
|
|
46
|
+
- End the subject line with a period
|
|
47
|
+
- Use title case for the description (use sentence case / lowercase)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Origin of your 4D web server (REST). The Vite dev server proxies /rest here.
|
|
2
|
+
VITE_4D_ORIGIN=http://127.0.0.1:7080
|
|
3
|
+
|
|
4
|
+
# Generator only (quadrokit:generate): multipart accessKey → 4DAdminSID cookie for fetching /rest/$catalog.
|
|
5
|
+
# Not used by the browser runtime.
|
|
6
|
+
# QUADROKIT_ACCESS_KEY=
|
|
7
|
+
# QUADROKIT_LOGIN_URL=https://127.0.0.1:7443/api/login
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
5
|
+
"useDefineForClassFields": true,
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"jsx": "react-jsx",
|
|
10
|
+
"verbatimModuleSyntax": true,
|
|
11
|
+
"paths": {
|
|
12
|
+
"@/*": ["./src/*"]
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*.ts", "src/**/*.tsx"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"jsx": "react-jsx",
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": true,
|
|
16
|
+
"noFallthroughCasesInSwitch": true,
|
|
17
|
+
"esModuleInterop": true,
|
|
18
|
+
"allowSyntheticDefaultImports": true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
5
|
+
"noEmit": true,
|
|
6
|
+
"moduleDetection": "force",
|
|
7
|
+
"types": ["node"]
|
|
8
|
+
},
|
|
9
|
+
"include": ["vite.config.ts", "tailwind.config.ts"]
|
|
10
|
+
}
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
# Origin of your 4D web server (REST). The Vite dev server proxies /rest here.
|
|
2
2
|
VITE_4D_ORIGIN=http://127.0.0.1:7080
|
|
3
|
+
|
|
4
|
+
# Generator only (quadrokit:generate): multipart accessKey → 4DAdminSID cookie for fetching /rest/$catalog.
|
|
5
|
+
# Not used by the browser runtime.
|
|
6
|
+
# QUADROKIT_ACCESS_KEY=
|
|
7
|
+
# QUADROKIT_LOGIN_URL=https://127.0.0.1:7443/api/login
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"jsx": "react-jsx",
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": true,
|
|
16
|
+
"noFallthroughCasesInSwitch": true,
|
|
17
|
+
"esModuleInterop": true,
|
|
18
|
+
"allowSyntheticDefaultImports": true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
# Origin of your 4D web server (REST). The Vite dev server proxies /rest here.
|
|
2
2
|
VITE_4D_ORIGIN=http://127.0.0.1:7080
|
|
3
|
+
|
|
4
|
+
# Generator only (quadrokit:generate): multipart accessKey → 4DAdminSID cookie for fetching /rest/$catalog.
|
|
5
|
+
# Not used by the browser runtime.
|
|
6
|
+
# QUADROKIT_ACCESS_KEY=
|
|
7
|
+
# QUADROKIT_LOGIN_URL=https://127.0.0.1:7443/api/login
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"jsx": "react-jsx",
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": true,
|
|
16
|
+
"noFallthroughCasesInSwitch": true,
|
|
17
|
+
"esModuleInterop": true,
|
|
18
|
+
"allowSyntheticDefaultImports": true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
# Origin of your 4D web server (REST). The Vite dev server proxies /rest here.
|
|
2
2
|
VITE_4D_ORIGIN=http://127.0.0.1:7080
|
|
3
|
+
|
|
4
|
+
# Generator only (quadrokit:generate): multipart accessKey → 4DAdminSID cookie for fetching /rest/$catalog.
|
|
5
|
+
# Not used by the browser runtime.
|
|
6
|
+
# QUADROKIT_ACCESS_KEY=
|
|
7
|
+
# QUADROKIT_LOGIN_URL=https://127.0.0.1:7443/api/login
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"jsx": "react-jsx",
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": true,
|
|
16
|
+
"noFallthroughCasesInSwitch": true,
|
|
17
|
+
"esModuleInterop": true,
|
|
18
|
+
"allowSyntheticDefaultImports": true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
# Origin of your 4D web server (REST). The Vite dev server proxies /rest here.
|
|
2
2
|
VITE_4D_ORIGIN=http://127.0.0.1:7080
|
|
3
|
+
|
|
4
|
+
# Generator only (quadrokit:generate): multipart accessKey → 4DAdminSID cookie for fetching /rest/$catalog.
|
|
5
|
+
# Not used by the browser runtime.
|
|
6
|
+
# QUADROKIT_ACCESS_KEY=
|
|
7
|
+
# QUADROKIT_LOGIN_URL=https://127.0.0.1:7443/api/login
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"jsx": "react-jsx",
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": true,
|
|
16
|
+
"noFallthroughCasesInSwitch": true,
|
|
17
|
+
"esModuleInterop": true,
|
|
18
|
+
"allowSyntheticDefaultImports": true
|
|
19
|
+
}
|
|
20
|
+
}
|