create-questpie 1.0.0
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 +81 -0
- package/dist/index.mjs +284 -0
- package/package.json +44 -0
- package/templates/tanstack-start/AGENTS.md +563 -0
- package/templates/tanstack-start/CLAUDE.md +105 -0
- package/templates/tanstack-start/Dockerfile +23 -0
- package/templates/tanstack-start/README.md +94 -0
- package/templates/tanstack-start/components.json +22 -0
- package/templates/tanstack-start/docker-compose.yml +20 -0
- package/templates/tanstack-start/env.example +16 -0
- package/templates/tanstack-start/gitignore +12 -0
- package/templates/tanstack-start/package.json +43 -0
- package/templates/tanstack-start/questpie.config.ts +12 -0
- package/templates/tanstack-start/src/admin.css +4 -0
- package/templates/tanstack-start/src/lib/auth-client.ts +12 -0
- package/templates/tanstack-start/src/lib/cms-client.ts +12 -0
- package/templates/tanstack-start/src/lib/env.ts +27 -0
- package/templates/tanstack-start/src/lib/query-client.ts +9 -0
- package/templates/tanstack-start/src/migrations/index.ts +8 -0
- package/templates/tanstack-start/src/questpie/admin/admin.ts +5 -0
- package/templates/tanstack-start/src/questpie/admin/builder.ts +4 -0
- package/templates/tanstack-start/src/questpie/server/app.ts +52 -0
- package/templates/tanstack-start/src/questpie/server/builder.ts +4 -0
- package/templates/tanstack-start/src/questpie/server/collections/index.ts +1 -0
- package/templates/tanstack-start/src/questpie/server/collections/posts.collection.ts +72 -0
- package/templates/tanstack-start/src/questpie/server/dashboard.ts +68 -0
- package/templates/tanstack-start/src/questpie/server/globals/index.ts +1 -0
- package/templates/tanstack-start/src/questpie/server/globals/site-settings.global.ts +24 -0
- package/templates/tanstack-start/src/questpie/server/rpc.ts +4 -0
- package/templates/tanstack-start/src/questpie/server/sidebar.ts +26 -0
- package/templates/tanstack-start/src/router.tsx +10 -0
- package/templates/tanstack-start/src/routes/__root.tsx +16 -0
- package/templates/tanstack-start/src/routes/admin/$.tsx +21 -0
- package/templates/tanstack-start/src/routes/admin/index.tsx +18 -0
- package/templates/tanstack-start/src/routes/admin/login.tsx +17 -0
- package/templates/tanstack-start/src/routes/admin.tsx +68 -0
- package/templates/tanstack-start/src/routes/api/cms/$.ts +45 -0
- package/templates/tanstack-start/src/styles.css +125 -0
- package/templates/tanstack-start/tsconfig.json +27 -0
- package/templates/tanstack-start/vite.config.ts +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# create-questpie
|
|
2
|
+
|
|
3
|
+
Interactive CLI for scaffolding new QUESTPIE projects.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bunx create-questpie
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with a project name:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bunx create-questpie my-app
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Options
|
|
18
|
+
|
|
19
|
+
| Flag | Description |
|
|
20
|
+
| ----------------------- | ------------------------------------------- |
|
|
21
|
+
| `-t, --template <name>` | Template to use (default: `tanstack-start`) |
|
|
22
|
+
| `--no-install` | Skip dependency installation |
|
|
23
|
+
| `--no-git` | Skip git initialization |
|
|
24
|
+
|
|
25
|
+
## Templates
|
|
26
|
+
|
|
27
|
+
### tanstack-start (default)
|
|
28
|
+
|
|
29
|
+
Full-stack TypeScript project with:
|
|
30
|
+
|
|
31
|
+
- **TanStack Start** — File-based routing with SSR
|
|
32
|
+
- **QUESTPIE** — Collections, globals, auth, storage, jobs pre-configured
|
|
33
|
+
- **@questpie/admin** — Admin panel with sidebar, dashboard, and form views
|
|
34
|
+
- **Tailwind CSS v4** — Styling with shadcn components
|
|
35
|
+
- **Drizzle ORM** — Migrations and typed database access
|
|
36
|
+
- **Vite** — Dev server with HMR
|
|
37
|
+
|
|
38
|
+
The template includes example collections, a site settings global, admin config, and everything wired together — ready to run with `bun dev`.
|
|
39
|
+
|
|
40
|
+
## What It Creates
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
my-app/
|
|
44
|
+
├── src/
|
|
45
|
+
│ ├── questpie/
|
|
46
|
+
│ │ ├── server/
|
|
47
|
+
│ │ │ ├── builder.ts # q.use(adminModule) setup
|
|
48
|
+
│ │ │ ├── cms.ts # CMS assembly + build
|
|
49
|
+
│ │ │ ├── collections/ # Collection definitions
|
|
50
|
+
│ │ │ └── globals/ # Global definitions
|
|
51
|
+
│ │ └── admin/
|
|
52
|
+
│ │ └── admin.ts # Client admin builder
|
|
53
|
+
│ ├── lib/
|
|
54
|
+
│ │ ├── cms-client.ts # Typed CMS client
|
|
55
|
+
│ │ └── query-client.ts # TanStack Query client
|
|
56
|
+
│ ├── routes/
|
|
57
|
+
│ │ ├── api/cms.ts # CMS route handler
|
|
58
|
+
│ │ └── admin/ # Admin panel routes
|
|
59
|
+
│ └── migrations/ # Drizzle migrations
|
|
60
|
+
├── questpie.config.ts # CLI config
|
|
61
|
+
├── AGENTS.md # AI agent guidance
|
|
62
|
+
├── package.json
|
|
63
|
+
└── vite.config.ts
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## After Scaffolding
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
cd my-app
|
|
70
|
+
cp .env.example .env # Set DATABASE_URL
|
|
71
|
+
bun questpie migrate # Run migrations
|
|
72
|
+
bun dev # Start dev server
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Documentation
|
|
76
|
+
|
|
77
|
+
Full documentation: [https://questpie.com/docs/getting-started/quickstart](https://questpie.com/docs/getting-started/quickstart)
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { cp, readFile, readdir, rename, writeFile } from "node:fs/promises";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
9
|
+
|
|
10
|
+
//#region src/templates.ts
|
|
11
|
+
const templates = [{
|
|
12
|
+
id: "tanstack-start",
|
|
13
|
+
label: "TanStack Start",
|
|
14
|
+
hint: "recommended",
|
|
15
|
+
description: "Full-stack React with TanStack Start, Vite, Tailwind CSS, and Nitro server"
|
|
16
|
+
}];
|
|
17
|
+
function getTemplate(id) {
|
|
18
|
+
return templates.find((t) => t.id === id);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/utils.ts
|
|
23
|
+
function toDbName(str) {
|
|
24
|
+
return str.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
25
|
+
}
|
|
26
|
+
function isValidPackageName(name) {
|
|
27
|
+
return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);
|
|
28
|
+
}
|
|
29
|
+
function generatePassword(length = 24) {
|
|
30
|
+
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
31
|
+
let result = "";
|
|
32
|
+
const randomBytes = new Uint8Array(length);
|
|
33
|
+
crypto.getRandomValues(randomBytes);
|
|
34
|
+
for (let i = 0; i < length; i++) result += chars[randomBytes[i] % 62];
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
function isGitInstalled() {
|
|
38
|
+
try {
|
|
39
|
+
execSync("git --version", { stdio: "ignore" });
|
|
40
|
+
return true;
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function gitInit(cwd) {
|
|
46
|
+
execSync("git init", {
|
|
47
|
+
cwd,
|
|
48
|
+
stdio: "ignore"
|
|
49
|
+
});
|
|
50
|
+
execSync("git add -A", {
|
|
51
|
+
cwd,
|
|
52
|
+
stdio: "ignore"
|
|
53
|
+
});
|
|
54
|
+
execSync("git commit -m \"Initial commit from create-questpie\"", {
|
|
55
|
+
cwd,
|
|
56
|
+
stdio: "ignore"
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function installDependencies(cwd, packageManager) {
|
|
60
|
+
execSync(packageManager === "npm" ? "npm install" : `${packageManager} install`, {
|
|
61
|
+
cwd,
|
|
62
|
+
stdio: "inherit"
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function detectPackageManager() {
|
|
66
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
67
|
+
if (userAgent) {
|
|
68
|
+
if (userAgent.startsWith("bun")) return "bun";
|
|
69
|
+
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
70
|
+
if (userAgent.startsWith("yarn")) return "yarn";
|
|
71
|
+
}
|
|
72
|
+
return "bun";
|
|
73
|
+
}
|
|
74
|
+
const label = {
|
|
75
|
+
info: (msg) => `${pc.cyan("ℹ")} ${msg}`,
|
|
76
|
+
success: (msg) => `${pc.green("✓")} ${msg}`,
|
|
77
|
+
warn: (msg) => `${pc.yellow("⚠")} ${msg}`,
|
|
78
|
+
error: (msg) => `${pc.red("✗")} ${msg}`
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/prompts.ts
|
|
83
|
+
async function runPrompts(args) {
|
|
84
|
+
p.intro(pc.bgCyan(pc.black(" QUESTPIE — Create a new project ")));
|
|
85
|
+
const questions = await p.group({
|
|
86
|
+
projectName: () => {
|
|
87
|
+
if (args.projectName) return Promise.resolve(args.projectName);
|
|
88
|
+
return p.text({
|
|
89
|
+
message: "Project name",
|
|
90
|
+
placeholder: "my-questpie-app",
|
|
91
|
+
validate: (value) => {
|
|
92
|
+
if (!value) return "Project name is required";
|
|
93
|
+
if (!isValidPackageName(value)) return "Invalid package name (use lowercase, hyphens, no spaces)";
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
templateId: () => {
|
|
98
|
+
if (args.templateId) return Promise.resolve(args.templateId);
|
|
99
|
+
if (templates.length === 1) return Promise.resolve(templates[0].id);
|
|
100
|
+
return p.select({
|
|
101
|
+
message: "Select a template",
|
|
102
|
+
options: templates.map((t) => ({
|
|
103
|
+
value: t.id,
|
|
104
|
+
label: t.label,
|
|
105
|
+
hint: t.hint
|
|
106
|
+
}))
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
databaseName: ({ results }) => {
|
|
110
|
+
if (args.databaseName) return Promise.resolve(args.databaseName);
|
|
111
|
+
const defaultDb = toDbName(results.projectName);
|
|
112
|
+
return p.text({
|
|
113
|
+
message: "Database name",
|
|
114
|
+
placeholder: defaultDb,
|
|
115
|
+
defaultValue: defaultDb
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
installDeps: () => {
|
|
119
|
+
if (args.installDeps !== void 0) return Promise.resolve(args.installDeps);
|
|
120
|
+
return p.confirm({
|
|
121
|
+
message: "Install dependencies?",
|
|
122
|
+
initialValue: true
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
initGit: () => {
|
|
126
|
+
if (args.initGit !== void 0) return Promise.resolve(args.initGit);
|
|
127
|
+
return p.confirm({
|
|
128
|
+
message: "Initialize git repository?",
|
|
129
|
+
initialValue: true
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}, { onCancel: () => {
|
|
133
|
+
p.cancel("Operation cancelled.");
|
|
134
|
+
process.exit(0);
|
|
135
|
+
} });
|
|
136
|
+
return {
|
|
137
|
+
projectName: questions.projectName,
|
|
138
|
+
templateId: questions.templateId,
|
|
139
|
+
databaseName: questions.databaseName,
|
|
140
|
+
installDeps: questions.installDeps,
|
|
141
|
+
initGit: questions.initGit
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/scaffolder.ts
|
|
147
|
+
const TEMPLATE_VAR_REGEX = /\{\{(\w+)\}\}/g;
|
|
148
|
+
/**
|
|
149
|
+
* Resolves the path to the templates directory.
|
|
150
|
+
* Works both in dev (src/) and built (dist/) contexts.
|
|
151
|
+
*/
|
|
152
|
+
function getTemplatesDir() {
|
|
153
|
+
const fromDist = resolve(import.meta.dirname, "..", "templates");
|
|
154
|
+
if (existsSync(fromDist)) return fromDist;
|
|
155
|
+
const fromSrc = resolve(import.meta.dirname, "..", "..", "templates");
|
|
156
|
+
if (existsSync(fromSrc)) return fromSrc;
|
|
157
|
+
throw new Error("Could not find templates directory");
|
|
158
|
+
}
|
|
159
|
+
const TEXT_EXTENSIONS = new Set([
|
|
160
|
+
".ts",
|
|
161
|
+
".tsx",
|
|
162
|
+
".js",
|
|
163
|
+
".jsx",
|
|
164
|
+
".json",
|
|
165
|
+
".md",
|
|
166
|
+
".css",
|
|
167
|
+
".html",
|
|
168
|
+
".yml",
|
|
169
|
+
".yaml",
|
|
170
|
+
".toml",
|
|
171
|
+
".env",
|
|
172
|
+
".example",
|
|
173
|
+
".hbs",
|
|
174
|
+
""
|
|
175
|
+
]);
|
|
176
|
+
function isTextFile(filename) {
|
|
177
|
+
const ext = filename.slice(filename.lastIndexOf("."));
|
|
178
|
+
if (filename.startsWith(".")) return true;
|
|
179
|
+
return TEXT_EXTENSIONS.has(ext);
|
|
180
|
+
}
|
|
181
|
+
async function replaceInFile(filePath, vars) {
|
|
182
|
+
const content = await readFile(filePath, "utf-8");
|
|
183
|
+
const replaced = content.replace(TEMPLATE_VAR_REGEX, (match, key) => {
|
|
184
|
+
return key in vars ? vars[key] : match;
|
|
185
|
+
});
|
|
186
|
+
if (replaced !== content) await writeFile(filePath, replaced, "utf-8");
|
|
187
|
+
}
|
|
188
|
+
async function processDirectory(dir, vars) {
|
|
189
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
190
|
+
for (const entry of entries) {
|
|
191
|
+
const fullPath = join(dir, entry.name);
|
|
192
|
+
if (entry.isDirectory()) {
|
|
193
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
194
|
+
await processDirectory(fullPath, vars);
|
|
195
|
+
} else if (entry.isFile() && isTextFile(entry.name)) await replaceInFile(fullPath, vars);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function renameGitignore(targetDir) {
|
|
199
|
+
const gitignorePath = join(targetDir, "gitignore");
|
|
200
|
+
if (existsSync(gitignorePath)) await rename(gitignorePath, join(targetDir, ".gitignore"));
|
|
201
|
+
}
|
|
202
|
+
async function renameEnvExample(targetDir) {
|
|
203
|
+
const envPath = join(targetDir, "env.example");
|
|
204
|
+
if (existsSync(envPath)) await rename(envPath, join(targetDir, ".env.example"));
|
|
205
|
+
}
|
|
206
|
+
async function scaffold(options) {
|
|
207
|
+
const spinner = p.spinner();
|
|
208
|
+
const targetDir = resolve(process.cwd(), options.projectName);
|
|
209
|
+
if (existsSync(targetDir)) {
|
|
210
|
+
p.log.error(`Directory ${options.projectName} already exists.`);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
const vars = {
|
|
214
|
+
projectName: options.projectName,
|
|
215
|
+
databaseName: options.databaseName,
|
|
216
|
+
databaseUser: options.databaseName,
|
|
217
|
+
databasePassword: generatePassword()
|
|
218
|
+
};
|
|
219
|
+
spinner.start("Copying template files");
|
|
220
|
+
const templateDir = join(getTemplatesDir(), options.templateId);
|
|
221
|
+
if (!existsSync(templateDir)) {
|
|
222
|
+
spinner.stop(label.error(`Template "${options.templateId}" not found`));
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
await cp(templateDir, targetDir, { recursive: true });
|
|
226
|
+
spinner.stop(label.success("Copied template files"));
|
|
227
|
+
spinner.start("Processing template");
|
|
228
|
+
await renameGitignore(targetDir);
|
|
229
|
+
await renameEnvExample(targetDir);
|
|
230
|
+
await processDirectory(targetDir, vars);
|
|
231
|
+
spinner.stop(label.success("Processed template variables"));
|
|
232
|
+
if (options.installDeps) {
|
|
233
|
+
const pm$1 = detectPackageManager();
|
|
234
|
+
spinner.start(`Installing dependencies with ${pm$1}`);
|
|
235
|
+
try {
|
|
236
|
+
installDependencies(targetDir, pm$1);
|
|
237
|
+
spinner.stop(label.success("Installed dependencies"));
|
|
238
|
+
} catch {
|
|
239
|
+
spinner.stop(label.warn("Failed to install dependencies — run manually"));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (options.initGit && isGitInstalled()) {
|
|
243
|
+
spinner.start("Initializing git repository");
|
|
244
|
+
try {
|
|
245
|
+
gitInit(targetDir);
|
|
246
|
+
spinner.stop(label.success("Initialized git repository"));
|
|
247
|
+
} catch {
|
|
248
|
+
spinner.stop(label.warn("Failed to initialize git — run manually"));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const pm = detectPackageManager();
|
|
252
|
+
const runCmd = pm === "npm" ? "npm run" : pm;
|
|
253
|
+
p.note([
|
|
254
|
+
`cd ${options.projectName}`,
|
|
255
|
+
"",
|
|
256
|
+
"# Start PostgreSQL",
|
|
257
|
+
"docker compose up -d",
|
|
258
|
+
"",
|
|
259
|
+
"# Run migrations",
|
|
260
|
+
`${runCmd} questpie migrate`,
|
|
261
|
+
"",
|
|
262
|
+
"# Start dev server",
|
|
263
|
+
`${runCmd} dev`
|
|
264
|
+
].join("\n"), "Next steps");
|
|
265
|
+
p.outro(`${label.success("Done!")} Happy building with QUESTPIE!`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
//#endregion
|
|
269
|
+
//#region src/index.ts
|
|
270
|
+
new Command().name("create-questpie").description("Create a new QUESTPIE CMS project").version("0.1.0").argument("[project-name]", "Name of the project").option("-t, --template <name>", "Template to use (default: tanstack-start)").option("--no-install", "Skip dependency installation").option("--no-git", "Skip git initialization").action(async (projectName, opts) => {
|
|
271
|
+
if (opts.template && !getTemplate(opts.template)) {
|
|
272
|
+
console.error(`Unknown template: ${opts.template}`);
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
await scaffold(await runPrompts({
|
|
276
|
+
projectName,
|
|
277
|
+
templateId: opts.template,
|
|
278
|
+
installDeps: opts.install === false ? false : void 0,
|
|
279
|
+
initGit: opts.git === false ? false : void 0
|
|
280
|
+
}));
|
|
281
|
+
}).parse();
|
|
282
|
+
|
|
283
|
+
//#endregion
|
|
284
|
+
export { };
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-questpie",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Create a new QUESTPIE CMS project",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-questpie": "./dist/index.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsdown",
|
|
15
|
+
"dev": "tsdown --watch",
|
|
16
|
+
"check-types": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@clack/prompts": "^0.10.0",
|
|
20
|
+
"commander": "^13.0.0",
|
|
21
|
+
"picocolors": "^1.1.1"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"bun-types": "latest",
|
|
25
|
+
"tsdown": "^0.18.3",
|
|
26
|
+
"typescript": "^5.9.2"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/questpie/questpie-cms.git",
|
|
31
|
+
"directory": "packages/create-questpie"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"questpie",
|
|
38
|
+
"cms",
|
|
39
|
+
"create",
|
|
40
|
+
"scaffold",
|
|
41
|
+
"template"
|
|
42
|
+
],
|
|
43
|
+
"license": "MIT"
|
|
44
|
+
}
|