create-questpie 2.0.3 → 2.0.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/dist/index.mjs +244 -30
- package/package.json +1 -1
- package/skills/questpie/AGENTS.md +299 -98
- package/skills/questpie/SKILL.md +50 -17
- package/skills/questpie/coverage.json +213 -0
- package/skills/questpie/references/auth.md +119 -4
- package/skills/questpie/references/business-logic.md +126 -56
- package/skills/questpie/references/crud-api.md +231 -29
- package/skills/questpie/references/data-modeling.md +22 -6
- package/skills/questpie/references/extend.md +34 -7
- package/skills/questpie/references/field-types.md +14 -2
- package/skills/questpie/references/infrastructure-adapters.md +207 -32
- package/skills/questpie/references/mcp.md +147 -0
- package/skills/questpie/references/multi-tenancy.md +1 -2
- package/skills/questpie/references/production.md +218 -53
- package/skills/questpie/references/quickstart.md +6 -8
- package/skills/questpie/references/rules.md +86 -21
- package/skills/questpie/references/sandbox.md +110 -0
- package/skills/questpie/references/tanstack-query.md +34 -11
- package/skills/questpie/references/type-inference.md +167 -0
- package/skills/questpie/references/workflows.md +155 -0
- package/skills/questpie-admin/AGENTS.md +47 -40
- package/skills/questpie-admin/SKILL.md +46 -39
- package/skills/questpie-admin/references/custom-ui.md +1 -1
- package/templates/tanstack-start/AGENTS.md +15 -8
- package/templates/tanstack-start/CLAUDE.md +12 -5
- package/templates/tanstack-start/README.md +7 -6
- package/templates/tanstack-start/package.json +1 -0
- package/templates/tanstack-start/src/questpie/admin/modules.ts +3 -1
- package/templates/tanstack-start/src/questpie/server/.generated/factories.ts +10 -9
- package/templates/tanstack-start/src/questpie/server/config/auth.ts +1 -1
- package/templates/tanstack-start/src/questpie/server/modules.ts +4 -5
- package/templates/tanstack-start/src/questpie/server/questpie.config.ts +2 -1
- package/templates/tanstack-start/src/routes/api/$.ts +1 -2
- package/templates/tanstack-start/vite.config.ts +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
2
4
|
import { Command } from "commander";
|
|
3
5
|
import * as p from "@clack/prompts";
|
|
4
6
|
import pc from "picocolors";
|
|
5
7
|
import { execFileSync, execSync } from "node:child_process";
|
|
6
|
-
import { existsSync } from "node:fs";
|
|
7
8
|
import { cp, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
8
|
-
import { join, resolve } from "node:path";
|
|
9
9
|
|
|
10
10
|
//#region src/templates.ts
|
|
11
11
|
const templates = [{
|
|
@@ -86,19 +86,62 @@ const label = {
|
|
|
86
86
|
|
|
87
87
|
//#endregion
|
|
88
88
|
//#region src/prompts.ts
|
|
89
|
+
const queueAdapters = [
|
|
90
|
+
"pg-boss",
|
|
91
|
+
"bullmq",
|
|
92
|
+
"none"
|
|
93
|
+
];
|
|
94
|
+
const emailAdapters = [
|
|
95
|
+
"console",
|
|
96
|
+
"smtp",
|
|
97
|
+
"resend",
|
|
98
|
+
"plunk"
|
|
99
|
+
];
|
|
100
|
+
const realtimeAdapters = [
|
|
101
|
+
"none",
|
|
102
|
+
"pg-notify",
|
|
103
|
+
"redis-streams"
|
|
104
|
+
];
|
|
105
|
+
const kvAdapters = ["memory", "redis"];
|
|
106
|
+
function assertChoice(name, value, choices) {
|
|
107
|
+
if (value === void 0) return void 0;
|
|
108
|
+
if (choices.includes(value)) return value;
|
|
109
|
+
throw new Error(`Invalid ${name}: ${value}. Expected one of: ${choices.join(", ")}.`);
|
|
110
|
+
}
|
|
111
|
+
function withOptionDefaults(options) {
|
|
112
|
+
return {
|
|
113
|
+
...options,
|
|
114
|
+
queueAdapter: options.queueAdapter ?? "pg-boss",
|
|
115
|
+
emailAdapter: options.emailAdapter ?? "console",
|
|
116
|
+
realtimeAdapter: options.realtimeAdapter ?? "none",
|
|
117
|
+
kvAdapter: options.kvAdapter ?? "memory",
|
|
118
|
+
includeWorkflows: options.includeWorkflows ?? false
|
|
119
|
+
};
|
|
120
|
+
}
|
|
89
121
|
async function runPrompts(args) {
|
|
90
|
-
|
|
122
|
+
const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
123
|
+
const queueAdapter = assertChoice("queue adapter", args.queueAdapter, queueAdapters);
|
|
124
|
+
const emailAdapter = assertChoice("email adapter", args.emailAdapter, emailAdapters);
|
|
125
|
+
const realtimeAdapter = assertChoice("realtime adapter", args.realtimeAdapter, realtimeAdapters);
|
|
126
|
+
const kvAdapter = assertChoice("KV adapter", args.kvAdapter, kvAdapters);
|
|
127
|
+
if (!isInteractive) {
|
|
91
128
|
if (!args.projectName) throw new Error("Project name is required in non-interactive mode.");
|
|
92
129
|
if (!isValidPackageName(args.projectName)) throw new Error("Invalid package name (use lowercase, hyphens, no spaces).");
|
|
93
|
-
return {
|
|
130
|
+
return withOptionDefaults({
|
|
94
131
|
projectName: args.projectName,
|
|
95
132
|
templateId: args.templateId ?? templates[0].id,
|
|
96
133
|
databaseName: args.databaseName ?? toDbName(args.projectName),
|
|
97
134
|
installDeps: args.installDeps ?? true,
|
|
98
135
|
initGit: args.initGit ?? true,
|
|
99
136
|
installSkills: args.installSkills ?? true,
|
|
100
|
-
runCodegen: args.runCodegen ?? true
|
|
101
|
-
|
|
137
|
+
runCodegen: args.runCodegen ?? true,
|
|
138
|
+
continueOnError: args.continueOnError ?? false,
|
|
139
|
+
queueAdapter,
|
|
140
|
+
emailAdapter,
|
|
141
|
+
realtimeAdapter,
|
|
142
|
+
kvAdapter,
|
|
143
|
+
includeWorkflows: args.includeWorkflows ?? false
|
|
144
|
+
});
|
|
102
145
|
}
|
|
103
146
|
p.intro(pc.bgCyan(pc.black(" QUESTPIE — Create a new project ")));
|
|
104
147
|
const questions = await p.group({
|
|
@@ -170,15 +213,21 @@ async function runPrompts(args) {
|
|
|
170
213
|
p.cancel("Operation cancelled.");
|
|
171
214
|
process.exit(0);
|
|
172
215
|
} });
|
|
173
|
-
return {
|
|
216
|
+
return withOptionDefaults({
|
|
174
217
|
projectName: questions.projectName,
|
|
175
218
|
templateId: questions.templateId,
|
|
176
219
|
databaseName: questions.databaseName,
|
|
177
220
|
installDeps: questions.installDeps,
|
|
178
221
|
initGit: questions.initGit,
|
|
179
222
|
installSkills: questions.installSkills,
|
|
180
|
-
runCodegen: questions.runCodegen
|
|
181
|
-
|
|
223
|
+
runCodegen: questions.runCodegen,
|
|
224
|
+
continueOnError: args.continueOnError ?? false,
|
|
225
|
+
queueAdapter,
|
|
226
|
+
emailAdapter,
|
|
227
|
+
realtimeAdapter,
|
|
228
|
+
kvAdapter,
|
|
229
|
+
includeWorkflows: args.includeWorkflows ?? false
|
|
230
|
+
});
|
|
182
231
|
}
|
|
183
232
|
|
|
184
233
|
//#endregion
|
|
@@ -286,24 +335,168 @@ async function installProjectSkills(targetDir) {
|
|
|
286
335
|
}
|
|
287
336
|
return installed;
|
|
288
337
|
}
|
|
338
|
+
function handleFatalStepFailure(message, error, continueOnError) {
|
|
339
|
+
if (continueOnError) return;
|
|
340
|
+
const cause = error instanceof Error ? error.message : typeof error === "string" ? error : String(error);
|
|
341
|
+
throw new Error(`${message}: ${cause}`);
|
|
342
|
+
}
|
|
343
|
+
async function applyProjectOptions(targetDir, options) {
|
|
344
|
+
await updatePackageJson(targetDir, options);
|
|
345
|
+
await writeFile(join(targetDir, "src", "lib", "env.ts"), buildEnvFile(options), "utf-8");
|
|
346
|
+
await writeFile(join(targetDir, "src", "questpie", "server", "questpie.config.ts"), buildRuntimeConfig(options), "utf-8");
|
|
347
|
+
await writeFile(join(targetDir, "src", "questpie", "server", "modules.ts"), buildServerModules(options), "utf-8");
|
|
348
|
+
await writeFile(join(targetDir, "src", "questpie", "admin", "modules.ts"), buildAdminModules(options), "utf-8");
|
|
349
|
+
}
|
|
350
|
+
async function updatePackageJson(targetDir, options) {
|
|
351
|
+
const packageJsonPath = join(targetDir, "package.json");
|
|
352
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
|
|
353
|
+
if (options.queueAdapter === "bullmq") packageJson.dependencies.bullmq = "^5.0.0";
|
|
354
|
+
if (options.queueAdapter === "bullmq" || options.realtimeAdapter === "redis-streams" || options.kvAdapter === "redis") packageJson.dependencies.redis = "^5.0.0";
|
|
355
|
+
if (options.includeWorkflows) packageJson.dependencies["@questpie/workflows"] = "latest";
|
|
356
|
+
await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, " ")}\n`);
|
|
357
|
+
}
|
|
358
|
+
function buildEnvFile(options) {
|
|
359
|
+
const mailAdapters = Array.from(new Set(["console", options.emailAdapter ?? "console"]));
|
|
360
|
+
const lines = [
|
|
361
|
+
`import { createEnv } from "@t3-oss/env-core";`,
|
|
362
|
+
`import { z } from "zod";`,
|
|
363
|
+
``,
|
|
364
|
+
`export const env = createEnv({`,
|
|
365
|
+
`\tserver: {`,
|
|
366
|
+
`\t\tDATABASE_URL: z.string().url(),`,
|
|
367
|
+
`\t\tAPP_URL: z.string().url().default("http://localhost:3000"),`,
|
|
368
|
+
`\t\tPORT: z`,
|
|
369
|
+
`\t\t\t.string()`,
|
|
370
|
+
`\t\t\t.transform(Number)`,
|
|
371
|
+
`\t\t\t.pipe(z.number().int().positive())`,
|
|
372
|
+
`\t\t\t.default(3000),`,
|
|
373
|
+
`\t\tBETTER_AUTH_SECRET: z.string().min(1).default("change-me-in-production"),`,
|
|
374
|
+
`\t\tMAIL_ADAPTER: z.enum(${JSON.stringify(mailAdapters)}).default("console"),`
|
|
375
|
+
];
|
|
376
|
+
if (options.emailAdapter === "smtp") lines.push(`\t\tSMTP_HOST: z.string().optional(),`, `\t\tSMTP_PORT: z`, `\t\t\t.string()`, `\t\t\t.transform(Number)`, `\t\t\t.pipe(z.number().int().positive())`, `\t\t\t.optional(),`);
|
|
377
|
+
if (options.emailAdapter === "resend") lines.push(`\t\tRESEND_API_KEY: z.string().optional(),`);
|
|
378
|
+
if (options.emailAdapter === "plunk") lines.push(`\t\tPLUNK_SECRET_KEY: z.string().optional(),`);
|
|
379
|
+
if (options.queueAdapter === "bullmq" || options.realtimeAdapter === "redis-streams" || options.kvAdapter === "redis") lines.push(`\t\tREDIS_URL: z.string().url().default("redis://localhost:6379"),`);
|
|
380
|
+
lines.push(`\t},`, `\truntimeEnv: process.env,`, `\temptyStringAsUndefined: true,`, `});`, ``);
|
|
381
|
+
return lines.join("\n");
|
|
382
|
+
}
|
|
383
|
+
function buildRuntimeConfig(options) {
|
|
384
|
+
const imports = [`import { runtimeConfig } from "questpie/app";`, `import { ConsoleAdapter } from "questpie/adapters/console";`];
|
|
385
|
+
if (options.queueAdapter === "pg-boss") imports.push(`import { pgBossAdapter } from "questpie/adapters/pg-boss";`);
|
|
386
|
+
if (options.queueAdapter === "bullmq") imports.push(`import { bullMQAdapter } from "questpie/adapters/bullmq";`);
|
|
387
|
+
if (options.emailAdapter === "smtp") imports.push(`import { SmtpAdapter } from "questpie/adapters/smtp";`);
|
|
388
|
+
if (options.emailAdapter === "resend") imports.push(`import { ResendAdapter } from "questpie/adapters/resend";`);
|
|
389
|
+
if (options.emailAdapter === "plunk") imports.push(`import { PlunkAdapter } from "questpie/adapters/plunk";`);
|
|
390
|
+
if (options.realtimeAdapter === "pg-notify") imports.push(`import { pgNotifyAdapter } from "questpie/adapters/pg-notify";`);
|
|
391
|
+
if (options.realtimeAdapter === "redis-streams") imports.push(`import { redisStreamsAdapter } from "questpie/adapters/redis-streams";`);
|
|
392
|
+
if (options.kvAdapter === "redis") {
|
|
393
|
+
imports.push(`import { redisKVAdapter } from "questpie/adapters/redis-kv";`);
|
|
394
|
+
imports.push(`import { createClient } from "redis";`);
|
|
395
|
+
}
|
|
396
|
+
imports.push(``, `import { env } from "@/lib/env.js";`, ``);
|
|
397
|
+
const helpers = [];
|
|
398
|
+
if (options.emailAdapter === "resend" || options.emailAdapter === "plunk") helpers.push(`function requiredEnv(value: string | undefined, name: string): string {`, `\tif (!value) throw new Error(\`Missing required environment variable: \${name}\`);`, `\treturn value;`, `}`, ``);
|
|
399
|
+
if (options.kvAdapter === "redis") helpers.push(`async function getRedis() {`, `\tconst redis = createClient({ url: env.REDIS_URL });`, `\tawait redis.connect();`, `\treturn redis;`, `}`, ``);
|
|
400
|
+
const configEntries = [
|
|
401
|
+
`\tapp: { url: env.APP_URL },`,
|
|
402
|
+
`\tdb: { url: env.DATABASE_URL },`,
|
|
403
|
+
`\tstorage: { basePath: "/api" },`,
|
|
404
|
+
`\temail: {`,
|
|
405
|
+
`\t\tadapter: ${buildEmailAdapterExpression(options)},`,
|
|
406
|
+
`\t},`
|
|
407
|
+
];
|
|
408
|
+
if (options.queueAdapter === "pg-boss") configEntries.push(`\tqueue: {`, `\t\tadapter: pgBossAdapter({ connectionString: env.DATABASE_URL }),`, `\t},`);
|
|
409
|
+
if (options.queueAdapter === "bullmq") configEntries.push(`\tqueue: {`, `\t\tadapter: bullMQAdapter({ connection: { url: env.REDIS_URL } }),`, `\t},`);
|
|
410
|
+
if (options.realtimeAdapter === "pg-notify") configEntries.push(`\trealtime: {`, `\t\tadapter: pgNotifyAdapter({ connectionString: env.DATABASE_URL }),`, `\t},`);
|
|
411
|
+
if (options.realtimeAdapter === "redis-streams") configEntries.push(`\trealtime: {`, `\t\tadapter: redisStreamsAdapter({ url: env.REDIS_URL }),`, `\t},`);
|
|
412
|
+
if (options.kvAdapter === "redis") configEntries.push(`\tkv: {`, `\t\tadapter: redisKVAdapter({ client: getRedis, keyPrefix: "${options.projectName}:" }),`, `\t},`);
|
|
413
|
+
return [
|
|
414
|
+
`/**`,
|
|
415
|
+
` * QUESTPIE Runtime Configuration`,
|
|
416
|
+
` *`,
|
|
417
|
+
` * Runtime-only configuration: database, adapters, secrets.`,
|
|
418
|
+
` * Entity definitions are codegen-generated.`,
|
|
419
|
+
` */`,
|
|
420
|
+
``,
|
|
421
|
+
...imports,
|
|
422
|
+
...helpers,
|
|
423
|
+
`export default runtimeConfig({`,
|
|
424
|
+
...configEntries,
|
|
425
|
+
`});`,
|
|
426
|
+
``
|
|
427
|
+
].join("\n");
|
|
428
|
+
}
|
|
429
|
+
function buildEmailAdapterExpression(options) {
|
|
430
|
+
if (options.emailAdapter === "smtp") return `env.MAIL_ADAPTER === "smtp"\n\t\t\t? new SmtpAdapter({\n\t\t\t\t\ttransport: {\n\t\t\t\t\t\thost: env.SMTP_HOST || "localhost",\n\t\t\t\t\t\tport: env.SMTP_PORT || 1025,\n\t\t\t\t\t\tsecure: false,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t: new ConsoleAdapter({ logHtml: false })`;
|
|
431
|
+
if (options.emailAdapter === "resend") return `env.MAIL_ADAPTER === "resend"\n\t\t\t? new ResendAdapter({ apiKey: requiredEnv(env.RESEND_API_KEY, "RESEND_API_KEY") })\n\t\t\t: new ConsoleAdapter({ logHtml: false })`;
|
|
432
|
+
if (options.emailAdapter === "plunk") return `env.MAIL_ADAPTER === "plunk"\n\t\t\t? new PlunkAdapter({ apiKey: requiredEnv(env.PLUNK_SECRET_KEY, "PLUNK_SECRET_KEY") })\n\t\t\t: new ConsoleAdapter({ logHtml: false })`;
|
|
433
|
+
return `new ConsoleAdapter({ logHtml: false })`;
|
|
434
|
+
}
|
|
435
|
+
function buildServerModules(options) {
|
|
436
|
+
const imports = [
|
|
437
|
+
`/**`,
|
|
438
|
+
` * Modules — static module dependencies for this project.`,
|
|
439
|
+
` */`,
|
|
440
|
+
`import { adminModule } from "@questpie/admin/modules/admin";`,
|
|
441
|
+
`import { openApiModule } from "@questpie/openapi";`
|
|
442
|
+
];
|
|
443
|
+
const modules = ["adminModule", "openApiModule"];
|
|
444
|
+
if (options.includeWorkflows) {
|
|
445
|
+
imports.push(`import { workflowsModule } from "@questpie/workflows/modules/workflows";`);
|
|
446
|
+
modules.push("workflowsModule");
|
|
447
|
+
}
|
|
448
|
+
return [
|
|
449
|
+
...imports,
|
|
450
|
+
``,
|
|
451
|
+
`const modules = [`,
|
|
452
|
+
...modules.map((mod) => `\t${mod},`),
|
|
453
|
+
`] as const;`,
|
|
454
|
+
``,
|
|
455
|
+
`export default modules;`,
|
|
456
|
+
``
|
|
457
|
+
].join("\n");
|
|
458
|
+
}
|
|
459
|
+
function buildAdminModules(options) {
|
|
460
|
+
const imports = [`import { adminClientModule } from "@questpie/admin/client/modules/admin";`];
|
|
461
|
+
const modules = ["adminClientModule"];
|
|
462
|
+
if (options.includeWorkflows) {
|
|
463
|
+
imports.push(`import { workflowsClientModule } from "@questpie/workflows/client/modules/workflows";`);
|
|
464
|
+
modules.push("workflowsClientModule");
|
|
465
|
+
}
|
|
466
|
+
return [
|
|
467
|
+
...imports,
|
|
468
|
+
``,
|
|
469
|
+
`export default [${modules.join(", ")}] as const;`,
|
|
470
|
+
``
|
|
471
|
+
].join("\n");
|
|
472
|
+
}
|
|
289
473
|
async function scaffold(options) {
|
|
474
|
+
const resolvedOptions = {
|
|
475
|
+
...options,
|
|
476
|
+
queueAdapter: options.queueAdapter ?? "pg-boss",
|
|
477
|
+
emailAdapter: options.emailAdapter ?? "console",
|
|
478
|
+
realtimeAdapter: options.realtimeAdapter ?? "none",
|
|
479
|
+
kvAdapter: options.kvAdapter ?? "memory",
|
|
480
|
+
includeWorkflows: options.includeWorkflows ?? false
|
|
481
|
+
};
|
|
290
482
|
const spinner = p.spinner();
|
|
291
|
-
const targetDir = resolve(process.cwd(),
|
|
483
|
+
const targetDir = resolve(process.cwd(), resolvedOptions.projectName);
|
|
484
|
+
const continueOnError = resolvedOptions.continueOnError === true;
|
|
292
485
|
if (existsSync(targetDir)) {
|
|
293
|
-
p.log.error(`Directory ${
|
|
486
|
+
p.log.error(`Directory ${resolvedOptions.projectName} already exists.`);
|
|
294
487
|
process.exit(1);
|
|
295
488
|
}
|
|
296
489
|
const vars = {
|
|
297
|
-
projectName:
|
|
298
|
-
databaseName:
|
|
299
|
-
databaseUser:
|
|
490
|
+
projectName: resolvedOptions.projectName,
|
|
491
|
+
databaseName: resolvedOptions.databaseName,
|
|
492
|
+
databaseUser: resolvedOptions.databaseName,
|
|
300
493
|
databasePassword: generatePassword(),
|
|
301
494
|
authSecret: generatePassword(48)
|
|
302
495
|
};
|
|
303
496
|
spinner.start("Copying template files");
|
|
304
|
-
const templateDir = join(getTemplatesDir(),
|
|
497
|
+
const templateDir = join(getTemplatesDir(), resolvedOptions.templateId);
|
|
305
498
|
if (!existsSync(templateDir)) {
|
|
306
|
-
spinner.stop(label.error(`Template "${
|
|
499
|
+
spinner.stop(label.error(`Template "${resolvedOptions.templateId}" not found`));
|
|
307
500
|
process.exit(1);
|
|
308
501
|
}
|
|
309
502
|
await cp(templateDir, targetDir, { recursive: true });
|
|
@@ -313,18 +506,20 @@ async function scaffold(options) {
|
|
|
313
506
|
await renameEnvExample(targetDir);
|
|
314
507
|
await processDirectory(targetDir, vars);
|
|
315
508
|
await createLocalEnv(targetDir);
|
|
509
|
+
await applyProjectOptions(targetDir, resolvedOptions);
|
|
316
510
|
spinner.stop(label.success("Processed template variables"));
|
|
317
511
|
const pm = detectPackageManager();
|
|
318
|
-
if (
|
|
512
|
+
if (resolvedOptions.installDeps) {
|
|
319
513
|
spinner.start(`Installing dependencies with ${pm}`);
|
|
320
514
|
try {
|
|
321
515
|
installDependencies(targetDir, pm);
|
|
322
516
|
spinner.stop(label.success("Installed dependencies"));
|
|
323
|
-
} catch {
|
|
324
|
-
spinner.stop(label.warn("Failed to install dependencies
|
|
517
|
+
} catch (error) {
|
|
518
|
+
spinner.stop(label.warn("Failed to install dependencies"));
|
|
519
|
+
handleFatalStepFailure("Dependency installation failed", error, continueOnError);
|
|
325
520
|
}
|
|
326
521
|
}
|
|
327
|
-
if (
|
|
522
|
+
if (resolvedOptions.installSkills) {
|
|
328
523
|
spinner.start("Installing QUESTPIE agent skills");
|
|
329
524
|
try {
|
|
330
525
|
const installedSkills = await installProjectSkills(targetDir);
|
|
@@ -334,16 +529,17 @@ async function scaffold(options) {
|
|
|
334
529
|
spinner.stop(label.warn("Failed to install skills — continuing"));
|
|
335
530
|
}
|
|
336
531
|
}
|
|
337
|
-
if (
|
|
532
|
+
if (resolvedOptions.installDeps && resolvedOptions.runCodegen) {
|
|
338
533
|
spinner.start("Generating QUESTPIE app");
|
|
339
534
|
try {
|
|
340
535
|
runPackageScript(targetDir, pm, "scaffold:generate");
|
|
341
536
|
spinner.stop(label.success("Generated QUESTPIE app"));
|
|
342
|
-
} catch {
|
|
343
|
-
spinner.stop(label.warn("Failed to run codegen
|
|
537
|
+
} catch (error) {
|
|
538
|
+
spinner.stop(label.warn("Failed to run codegen"));
|
|
539
|
+
handleFatalStepFailure("QUESTPIE codegen failed", error, continueOnError);
|
|
344
540
|
}
|
|
345
541
|
}
|
|
346
|
-
if (
|
|
542
|
+
if (resolvedOptions.initGit && isGitInstalled()) {
|
|
347
543
|
spinner.start("Initializing git repository");
|
|
348
544
|
try {
|
|
349
545
|
gitInit(targetDir);
|
|
@@ -355,7 +551,7 @@ async function scaffold(options) {
|
|
|
355
551
|
const runScript = (script) => pm === "npm" ? `npm run ${script}` : `${pm} run ${script}`;
|
|
356
552
|
const questpieBin = pm === "npm" ? "npx questpie" : "bunx questpie";
|
|
357
553
|
p.note([
|
|
358
|
-
`cd ${
|
|
554
|
+
`cd ${resolvedOptions.projectName}`,
|
|
359
555
|
"",
|
|
360
556
|
"# Review the generated environment",
|
|
361
557
|
"# .env has already been created from .env.example",
|
|
@@ -366,8 +562,8 @@ async function scaffold(options) {
|
|
|
366
562
|
"# Regenerate and type-check the scaffold",
|
|
367
563
|
runScript("scaffold:verify"),
|
|
368
564
|
"",
|
|
369
|
-
"#
|
|
370
|
-
runScript("
|
|
565
|
+
"# Create local database tables",
|
|
566
|
+
runScript("db:push"),
|
|
371
567
|
"",
|
|
372
568
|
"# Start dev server",
|
|
373
569
|
runScript("dev"),
|
|
@@ -377,14 +573,26 @@ async function scaffold(options) {
|
|
|
377
573
|
`${questpieBin} add global marketing`,
|
|
378
574
|
"",
|
|
379
575
|
"# If you create files manually",
|
|
380
|
-
runScript("questpie:generate")
|
|
576
|
+
runScript("questpie:generate"),
|
|
577
|
+
"",
|
|
578
|
+
"# For production migrations",
|
|
579
|
+
runScript("migrate:create"),
|
|
580
|
+
runScript("migrate")
|
|
381
581
|
].join("\n"), "Next steps");
|
|
382
582
|
p.outro(`${label.success("Done!")} Happy building with QUESTPIE!`);
|
|
383
583
|
}
|
|
384
584
|
|
|
385
585
|
//#endregion
|
|
386
586
|
//#region src/index.ts
|
|
387
|
-
|
|
587
|
+
function readPackageVersion() {
|
|
588
|
+
for (const candidate of [resolve(import.meta.dirname, "..", "package.json"), resolve(import.meta.dirname, "..", "..", "package.json")]) {
|
|
589
|
+
if (!existsSync(candidate)) continue;
|
|
590
|
+
const packageJson = JSON.parse(readFileSync(candidate, "utf-8"));
|
|
591
|
+
if (packageJson.version) return packageJson.version;
|
|
592
|
+
}
|
|
593
|
+
return "0.0.0";
|
|
594
|
+
}
|
|
595
|
+
new Command().name("create-questpie").description("Create a new QUESTPIE project").version(readPackageVersion()).argument("[project-name]", "Name of the project").option("-t, --template <name>", "Template to use (default: tanstack-start)").option("--database <name>", "Database name (default: derived from project name)").option("--no-install", "Skip dependency installation").option("--no-git", "Skip git initialization").option("--no-skills", "Skip installing project-local QUESTPIE agent skills").option("--no-generate", "Skip running QUESTPIE codegen after install").option("--queue <adapter>", "Queue adapter: pg-boss, bullmq, none (default: pg-boss)").option("--email <adapter>", "Email adapters to scaffold: console, smtp, resend, plunk (default: console)").option("--realtime <adapter>", "Realtime adapter: none, pg-notify, redis-streams (default: none)").option("--kv <adapter>", "KV adapter: memory, redis (default: memory)").option("--workflows", "Install and register @questpie/workflows").option("--continue-on-error", "Keep scaffold files when dependency install or codegen fails").action(async (projectName, opts) => {
|
|
388
596
|
try {
|
|
389
597
|
if (opts.template && !getTemplate(opts.template)) throw new Error(`Unknown template: ${opts.template}`);
|
|
390
598
|
await scaffold(await runPrompts({
|
|
@@ -394,7 +602,13 @@ new Command().name("create-questpie").description("Create a new QUESTPIE project
|
|
|
394
602
|
installDeps: opts.install === false ? false : void 0,
|
|
395
603
|
initGit: opts.git === false ? false : void 0,
|
|
396
604
|
installSkills: opts.skills === false ? false : void 0,
|
|
397
|
-
runCodegen: opts.generate === false ? false : void 0
|
|
605
|
+
runCodegen: opts.generate === false ? false : void 0,
|
|
606
|
+
continueOnError: opts.continueOnError === true,
|
|
607
|
+
queueAdapter: opts.queue,
|
|
608
|
+
emailAdapter: opts.email,
|
|
609
|
+
realtimeAdapter: opts.realtime,
|
|
610
|
+
kvAdapter: opts.kv,
|
|
611
|
+
includeWorkflows: opts.workflows === true
|
|
398
612
|
}));
|
|
399
613
|
} catch (error) {
|
|
400
614
|
console.error(error instanceof Error ? error.message : String(error));
|