@kardoe/quickback 0.4.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.
Files changed (61) hide show
  1. package/dist/commands/compile.d.ts +18 -0
  2. package/dist/commands/compile.d.ts.map +1 -0
  3. package/dist/commands/compile.js +144 -0
  4. package/dist/commands/compile.js.map +1 -0
  5. package/dist/commands/create.d.ts +18 -0
  6. package/dist/commands/create.d.ts.map +1 -0
  7. package/dist/commands/create.js +669 -0
  8. package/dist/commands/create.js.map +1 -0
  9. package/dist/commands/init.d.ts +6 -0
  10. package/dist/commands/init.d.ts.map +1 -0
  11. package/dist/commands/init.js +383 -0
  12. package/dist/commands/init.js.map +1 -0
  13. package/dist/commands/login.d.ts +8 -0
  14. package/dist/commands/login.d.ts.map +1 -0
  15. package/dist/commands/login.js +190 -0
  16. package/dist/commands/login.js.map +1 -0
  17. package/dist/commands/logout.d.ts +7 -0
  18. package/dist/commands/logout.d.ts.map +1 -0
  19. package/dist/commands/logout.js +19 -0
  20. package/dist/commands/logout.js.map +1 -0
  21. package/dist/commands/whoami.d.ts +7 -0
  22. package/dist/commands/whoami.d.ts.map +1 -0
  23. package/dist/commands/whoami.js +37 -0
  24. package/dist/commands/whoami.js.map +1 -0
  25. package/dist/index.d.ts +12 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +140 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/lib/api-client.d.ts +82 -0
  30. package/dist/lib/api-client.d.ts.map +1 -0
  31. package/dist/lib/api-client.js +59 -0
  32. package/dist/lib/api-client.js.map +1 -0
  33. package/dist/lib/auth.d.ts +44 -0
  34. package/dist/lib/auth.d.ts.map +1 -0
  35. package/dist/lib/auth.js +85 -0
  36. package/dist/lib/auth.js.map +1 -0
  37. package/dist/lib/compiler-stubs.d.ts +66 -0
  38. package/dist/lib/compiler-stubs.d.ts.map +1 -0
  39. package/dist/lib/compiler-stubs.js +75 -0
  40. package/dist/lib/compiler-stubs.js.map +1 -0
  41. package/dist/lib/file-loader.d.ts +73 -0
  42. package/dist/lib/file-loader.d.ts.map +1 -0
  43. package/dist/lib/file-loader.js +291 -0
  44. package/dist/lib/file-loader.js.map +1 -0
  45. package/dist/lib/file-writer.d.ts +33 -0
  46. package/dist/lib/file-writer.d.ts.map +1 -0
  47. package/dist/lib/file-writer.js +110 -0
  48. package/dist/lib/file-writer.js.map +1 -0
  49. package/dist/lib/helpers.d.ts +39 -0
  50. package/dist/lib/helpers.d.ts.map +1 -0
  51. package/dist/lib/helpers.js +299 -0
  52. package/dist/lib/helpers.js.map +1 -0
  53. package/dist/lib/shell.d.ts +15 -0
  54. package/dist/lib/shell.d.ts.map +1 -0
  55. package/dist/lib/shell.js +32 -0
  56. package/dist/lib/shell.js.map +1 -0
  57. package/dist/templates/registry.d.ts +36 -0
  58. package/dist/templates/registry.d.ts.map +1 -0
  59. package/dist/templates/registry.js +143 -0
  60. package/dist/templates/registry.js.map +1 -0
  61. package/package.json +37 -0
@@ -0,0 +1,669 @@
1
+ /**
2
+ * Create Command
3
+ *
4
+ * One-shot project creation with templates.
5
+ * Usage: quickback create <template> <app-name>
6
+ * Example: quickback create betterauth-d1-cloudflare my-app
7
+ */
8
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
9
+ import { join, resolve } from "path";
10
+ import { spawnSync } from "child_process";
11
+ import pc from "picocolors";
12
+ import ora from "ora";
13
+ import prompts from "prompts";
14
+ import { getTemplate, listTemplates, formatTemplateList, TEMPLATE_ALIASES, } from "../templates/registry.js";
15
+ import { callCompiler } from "../lib/api-client.js";
16
+ import { loadConfig, loadFeatures, findConfigPath, findFeaturesDir } from "../lib/file-loader.js";
17
+ import { writeFiles } from "../lib/file-writer.js";
18
+ // ============================================================================
19
+ // Main Create Function
20
+ // ============================================================================
21
+ export async function create(args) {
22
+ console.log(pc.bold("\nQuickback Create") + pc.gray(" · One-shot project generator\n"));
23
+ try {
24
+ // Parse arguments
25
+ const options = await parseCreateArgs(args);
26
+ if (options.dryRun) {
27
+ await performDryRun(options);
28
+ return;
29
+ }
30
+ // Resolve template
31
+ const template = getTemplate(options.template);
32
+ if (!template) {
33
+ console.error(pc.red(`Unknown template: ${options.template}\n`));
34
+ console.log(formatTemplateList());
35
+ process.exit(1);
36
+ }
37
+ // Validate target directory
38
+ const targetDir = resolve(process.cwd(), options.appName);
39
+ if (existsSync(targetDir)) {
40
+ console.error(pc.red(`Directory already exists: ${options.appName}`));
41
+ console.log(pc.gray("Choose a different name or delete the existing directory."));
42
+ process.exit(1);
43
+ }
44
+ console.log(pc.cyan(`Creating ${template.name}...`));
45
+ console.log(pc.gray(`Template: ${options.template}`));
46
+ console.log(pc.gray(`Location: ${targetDir}\n`));
47
+ // Step 1: Create project directory
48
+ const spinner = ora("Creating project structure...").start();
49
+ mkdirSync(targetDir, { recursive: true });
50
+ mkdirSync(join(targetDir, "quickback"), { recursive: true });
51
+ mkdirSync(join(targetDir, "quickback", "definitions", "features", "todos"), { recursive: true });
52
+ spinner.succeed("Project structure created");
53
+ // Step 2: Generate config file
54
+ spinner.start("Generating configuration...");
55
+ generateConfigFile(targetDir, options.appName, template);
56
+ generateTodoFeature(targetDir);
57
+ generateAuthConfig(targetDir);
58
+ spinner.succeed("Configuration generated");
59
+ // Step 3: Setup Cloudflare resources (if applicable)
60
+ if (template.requiresCloudflare && !options.skipCloudflareSetup) {
61
+ await setupCloudflareResources(options.appName, targetDir, options.verbose);
62
+ }
63
+ // Step 4: Compile the project via remote API
64
+ spinner.start("Compiling via Quickback API...");
65
+ const quickbackDir = join(targetDir, "quickback");
66
+ const configPath = findConfigPath(quickbackDir);
67
+ if (!configPath) {
68
+ spinner.fail("Configuration not found");
69
+ throw new Error("Could not find quickback.config.ts");
70
+ }
71
+ const config = await loadConfig(configPath);
72
+ const featuresDir = findFeaturesDir(quickbackDir);
73
+ const features = featuresDir ? await loadFeatures(featuresDir) : [];
74
+ const result = await callCompiler({
75
+ config: {
76
+ name: config.name,
77
+ preset: config.preset,
78
+ template: config.template,
79
+ providers: config.providers,
80
+ build: config.build,
81
+ },
82
+ features: features.map((f) => ({
83
+ name: f.name,
84
+ schema: f.schema,
85
+ resource: f.resource,
86
+ actions: f.actions,
87
+ })),
88
+ });
89
+ // Write output files to project root
90
+ writeFiles(targetDir, result.files);
91
+ spinner.succeed(`Compiled ${result.meta.fileCount} files (v${result.meta.version})`);
92
+ // Step 5: Install dependencies
93
+ if (!options.skipInstall) {
94
+ spinner.start("Installing dependencies...");
95
+ // Prefer bun for bun templates (better peer dep handling)
96
+ const preferBun = template.preset === 'bun' || !template.requiresCloudflare;
97
+ const pm = detectPackageManager(preferBun);
98
+ const installResult = runCommand(pm, ["install"], targetDir, options.verbose);
99
+ if (installResult.success) {
100
+ spinner.succeed(`Dependencies installed with ${pm}`);
101
+ }
102
+ else {
103
+ spinner.warn("Dependency installation had issues");
104
+ }
105
+ }
106
+ // Step 6: Generate database schemas (drizzle-kit generate)
107
+ spinner.start("Generating database schemas...");
108
+ const schemaResult = runCommand("npx", ["drizzle-kit", "generate"], targetDir, options.verbose);
109
+ if (schemaResult.success) {
110
+ spinner.succeed("Database schemas generated");
111
+ }
112
+ else {
113
+ spinner.warn("Schema generation skipped (install dependencies first)");
114
+ }
115
+ // Step 7: Apply local migrations (for non-Cloudflare)
116
+ if (!template.requiresCloudflare && !options.skipMigrations) {
117
+ // Ensure data directory exists for SQLite
118
+ const dataDir = join(targetDir, "data");
119
+ if (!existsSync(dataDir)) {
120
+ mkdirSync(dataDir, { recursive: true });
121
+ }
122
+ spinner.start("Applying local migrations...");
123
+ const migrateResult = runCommand("npx", ["drizzle-kit", "migrate"], targetDir, options.verbose);
124
+ if (migrateResult.success) {
125
+ spinner.succeed("Local migrations applied");
126
+ }
127
+ else {
128
+ spinner.warn("Migrations skipped");
129
+ }
130
+ }
131
+ // Success!
132
+ printSuccessMessage(options.appName, template);
133
+ }
134
+ catch (error) {
135
+ console.error(pc.red("\nProject creation failed:"));
136
+ console.error(pc.red(String(error)));
137
+ process.exit(1);
138
+ }
139
+ }
140
+ // ============================================================================
141
+ // Argument Parsing
142
+ // ============================================================================
143
+ async function parseCreateArgs(args) {
144
+ let template;
145
+ let appName;
146
+ let skipCloudflareSetup = false;
147
+ let skipInstall = false;
148
+ let skipMigrations = false;
149
+ let dryRun = false;
150
+ let verbose = false;
151
+ // Parse flags and positional args
152
+ const positional = [];
153
+ for (const arg of args) {
154
+ if (arg === "--skip-cloudflare-setup" || arg === "--skip-cf") {
155
+ skipCloudflareSetup = true;
156
+ }
157
+ else if (arg === "--skip-install") {
158
+ skipInstall = true;
159
+ }
160
+ else if (arg === "--skip-migrations") {
161
+ skipMigrations = true;
162
+ }
163
+ else if (arg === "--dry-run") {
164
+ dryRun = true;
165
+ }
166
+ else if (arg === "--verbose" || arg === "-v") {
167
+ verbose = true;
168
+ }
169
+ else if (arg === "--help" || arg === "-h") {
170
+ printHelp();
171
+ process.exit(0);
172
+ }
173
+ else if (arg === "--list" || arg === "-l") {
174
+ console.log(formatTemplateList());
175
+ process.exit(0);
176
+ }
177
+ else if (!arg.startsWith("-")) {
178
+ positional.push(arg);
179
+ }
180
+ }
181
+ // Extract template and appName from positional args
182
+ if (positional.length >= 2) {
183
+ template = positional[0];
184
+ appName = positional[1];
185
+ }
186
+ else if (positional.length === 1) {
187
+ // Could be template or app name - check if it's a known template
188
+ if (getTemplate(positional[0]) || TEMPLATE_ALIASES[positional[0]]) {
189
+ template = positional[0];
190
+ }
191
+ else {
192
+ appName = positional[0];
193
+ }
194
+ }
195
+ // Interactive prompts if needed
196
+ if (!template) {
197
+ const templates = listTemplates();
198
+ const response = await prompts({
199
+ type: "select",
200
+ name: "template",
201
+ message: "Choose a template",
202
+ choices: templates.map(t => ({
203
+ title: t.name,
204
+ description: t.description,
205
+ value: Object.entries(TEMPLATE_ALIASES).find(([_, v]) => v === Object.keys(TEMPLATE_ALIASES).find(k => getTemplate(k)?.name === t.name))?.[0] || t.name.toLowerCase().replace(/\s+/g, "-"),
206
+ })),
207
+ });
208
+ template = response.template;
209
+ if (!template) {
210
+ console.log(pc.yellow("\nOperation cancelled."));
211
+ process.exit(0);
212
+ }
213
+ }
214
+ if (!appName) {
215
+ const response = await prompts({
216
+ type: "text",
217
+ name: "appName",
218
+ message: "Project name",
219
+ validate: (v) => {
220
+ if (!v || v.trim().length === 0)
221
+ return "Name is required";
222
+ if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(v)) {
223
+ return "Use lowercase letters, numbers, and hyphens only";
224
+ }
225
+ return true;
226
+ },
227
+ });
228
+ appName = response.appName;
229
+ if (!appName) {
230
+ console.log(pc.yellow("\nOperation cancelled."));
231
+ process.exit(0);
232
+ }
233
+ }
234
+ return {
235
+ template: template,
236
+ appName: appName,
237
+ skipCloudflareSetup,
238
+ skipInstall,
239
+ skipMigrations,
240
+ dryRun,
241
+ verbose,
242
+ };
243
+ }
244
+ // ============================================================================
245
+ // File Generation
246
+ // ============================================================================
247
+ function generateConfigFile(targetDir, appName, template) {
248
+ // Generate providers based on template preset
249
+ const providers = getProvidersForPreset(template.preset);
250
+ const configContent = `/**
251
+ * Quickback Configuration
252
+ * Generated by: quickback create ${template.preset} ${appName}
253
+ *
254
+ * Project structure:
255
+ * ${appName}/
256
+ * ├── quickback/ ← definitions live here
257
+ * │ ├── quickback.config.ts
258
+ * │ └── definitions/features/...
259
+ * ├── src/ ← compiled code
260
+ * ├── drizzle/ ← migrations (or supabase/migrations/)
261
+ * └── package.json
262
+ *
263
+ * Run 'quickback compile' to regenerate after editing definitions.
264
+ */
265
+
266
+ export default {
267
+ name: "${appName}",
268
+ template: "hono",
269
+ providers: ${JSON.stringify(providers, null, 8).replace(/^/gm, ' ').trim()},
270
+ };
271
+ `;
272
+ writeFileSync(join(targetDir, "quickback", "quickback.config.ts"), configContent);
273
+ }
274
+ function getProvidersForPreset(preset) {
275
+ switch (preset) {
276
+ case "bun":
277
+ return {
278
+ runtime: { name: "bun", config: {} },
279
+ database: { name: "bun-sqlite", config: { path: "./data/app.db" } },
280
+ auth: { name: "better-auth", config: {} },
281
+ };
282
+ case "cloudflare":
283
+ return {
284
+ runtime: { name: "cloudflare", config: {} },
285
+ database: { name: "cloudflare-d1", config: { binding: "DB" } },
286
+ auth: { name: "better-auth", config: {} },
287
+ };
288
+ case "turso":
289
+ return {
290
+ runtime: { name: "bun", config: {} },
291
+ database: { name: "libsql", config: {} },
292
+ auth: { name: "better-auth", config: {} },
293
+ };
294
+ default:
295
+ return {
296
+ runtime: { name: "bun", config: {} },
297
+ database: { name: "bun-sqlite", config: {} },
298
+ auth: { name: "better-auth", config: {} },
299
+ };
300
+ }
301
+ }
302
+ function generateTodoFeature(targetDir) {
303
+ // Schema with all fields required for security pillars
304
+ const schemaContent = `/**
305
+ * Todo Feature Schema
306
+ * Demonstrates all 4 Security Pillars: Firewall, Guards, Safety, Masking
307
+ */
308
+ import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
309
+
310
+ export const todos = sqliteTable("todos", {
311
+ // Primary key
312
+ id: text("id").primaryKey().notNull(),
313
+
314
+ // Business fields
315
+ title: text("title").notNull(),
316
+ description: text("description"),
317
+ completed: integer("completed", { mode: "boolean" }).default(false),
318
+ priority: text("priority").default("medium"), // low, medium, high
319
+ dueDate: integer("due_date", { mode: "timestamp" }),
320
+
321
+ // Ownership (for Firewall)
322
+ organizationId: text("organization_id").notNull(),
323
+ ownerId: text("owner_id").notNull(),
324
+
325
+ // Audit trail (for Safety - immutable fields)
326
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
327
+ createdBy: text("created_by").notNull(),
328
+ modifiedAt: integer("modified_at", { mode: "timestamp" }).notNull(),
329
+ modifiedBy: text("modified_by").notNull(),
330
+
331
+ // Soft delete (for Firewall)
332
+ deletedAt: integer("deleted_at", { mode: "timestamp" }),
333
+ });
334
+ `;
335
+ // Resource definition with all 4 security pillars
336
+ const resourceContent = `/**
337
+ * Todo Resource Definition
338
+ * Demonstrates all 4 Security Pillars
339
+ */
340
+ export default {
341
+ name: "todos",
342
+ schema: "./schema",
343
+
344
+ // =========================================================================
345
+ // PILLAR 1: FIREWALL
346
+ // Controls data isolation at the query level
347
+ // =========================================================================
348
+ firewall: {
349
+ organization: {
350
+ column: "organizationId",
351
+ source: "ctx.activeOrgId",
352
+ },
353
+ owner: {
354
+ column: "ownerId",
355
+ source: "ctx.userId",
356
+ mode: "optional", // Can filter by owner when needed
357
+ },
358
+ softDelete: {
359
+ column: "deletedAt",
360
+ },
361
+ },
362
+
363
+ // =========================================================================
364
+ // PILLAR 2: GUARDS (via CRUD config)
365
+ // Controls who can perform which operations
366
+ // =========================================================================
367
+ crud: {
368
+ list: {
369
+ guard: { roles: ["member", "admin"] },
370
+ },
371
+ get: {
372
+ guard: { roles: ["member", "admin"] },
373
+ },
374
+ create: {
375
+ guard: { roles: ["member", "admin"] },
376
+ },
377
+ update: {
378
+ guard: { roles: ["member", "admin"], record: { ownerId: { equals: "$ctx.userId" } } },
379
+ },
380
+ delete: {
381
+ guard: { roles: ["admin"] },
382
+ mode: "soft",
383
+ },
384
+ },
385
+
386
+ // =========================================================================
387
+ // PILLAR 3: SAFETY
388
+ // Protects data integrity with field-level validation
389
+ // =========================================================================
390
+ safety: {
391
+ // Fields that can be set on create
392
+ createable: [
393
+ "title",
394
+ "description",
395
+ "completed",
396
+ "priority",
397
+ "dueDate",
398
+ ],
399
+ // Fields that can be modified on update
400
+ updatable: [
401
+ "title",
402
+ "description",
403
+ "completed",
404
+ "priority",
405
+ "dueDate",
406
+ ],
407
+ // Fields that can never be changed after creation
408
+ immutable: [
409
+ "id",
410
+ "organizationId",
411
+ "ownerId",
412
+ "createdAt",
413
+ "createdBy",
414
+ ],
415
+ },
416
+
417
+ // =========================================================================
418
+ // PILLAR 4: MASKING
419
+ // Controls what data is visible based on context
420
+ // =========================================================================
421
+ masking: {
422
+ // Hide description for non-owners (privacy)
423
+ description: {
424
+ type: "redact",
425
+ show: {
426
+ roles: ["admin"],
427
+ or: "owner",
428
+ },
429
+ },
430
+ },
431
+ };
432
+ `;
433
+ // Actions definition for custom business logic
434
+ const actionsContent = `/**
435
+ * Todo Actions Definition
436
+ * Custom business logic with security guards
437
+ *
438
+ * Input schemas use JSON Schema format which gets converted to Zod at compile time.
439
+ */
440
+ export default {
441
+ name: "todos",
442
+ schema: "./schema",
443
+
444
+ actions: {
445
+ // Mark a todo as complete
446
+ complete: {
447
+ description: "Mark todo as complete",
448
+ input: {
449
+ type: "object",
450
+ properties: {
451
+ completedAt: { type: "string", format: "datetime", optional: true },
452
+ },
453
+ },
454
+ guard: {
455
+ roles: ["owner", "member", "admin"],
456
+ record: { completed: { equals: false } },
457
+ },
458
+ sideEffects: "sync",
459
+ },
460
+
461
+ // Mark a todo as incomplete
462
+ uncomplete: {
463
+ description: "Mark todo as incomplete",
464
+ input: { type: "object", properties: {} },
465
+ guard: {
466
+ roles: ["owner", "member", "admin"],
467
+ record: { completed: { equals: true } },
468
+ },
469
+ sideEffects: "sync",
470
+ },
471
+ },
472
+ };
473
+ `;
474
+ const featureDir = join(targetDir, "quickback", "definitions", "features", "todos");
475
+ writeFileSync(join(featureDir, "schema.ts"), schemaContent);
476
+ writeFileSync(join(featureDir, "resource.ts"), resourceContent);
477
+ writeFileSync(join(featureDir, "actions.ts"), actionsContent);
478
+ }
479
+ function generateAuthConfig(targetDir) {
480
+ const authContent = `/**
481
+ * Better Auth Configuration
482
+ */
483
+ export default {
484
+ emailAndPassword: {
485
+ enabled: true,
486
+ requireEmailVerification: false,
487
+ },
488
+ plugins: ["anonymous"],
489
+ session: {
490
+ expiresInDays: 7,
491
+ updateAgeInDays: 1,
492
+ },
493
+ database: {
494
+ usePlural: true,
495
+ debugLogs: true,
496
+ },
497
+ };
498
+ `;
499
+ mkdirSync(join(targetDir, "quickback", "definitions", "auth", "better-auth"), { recursive: true });
500
+ writeFileSync(join(targetDir, "quickback", "definitions", "auth", "better-auth", "config.ts"), authContent);
501
+ }
502
+ // ============================================================================
503
+ // Cloudflare Setup
504
+ // ============================================================================
505
+ async function setupCloudflareResources(appName, targetDir, verbose) {
506
+ const spinner = ora("Setting up Cloudflare resources...").start();
507
+ // Check if wrangler is available
508
+ const wranglerCheck = spawnSync("npx", ["wrangler", "--version"], { encoding: "utf-8" });
509
+ if (wranglerCheck.status !== 0) {
510
+ spinner.warn("Wrangler not available - skipping Cloudflare setup");
511
+ console.log(pc.gray(" Install wrangler and run: npx wrangler d1 create " + appName + "-db"));
512
+ return;
513
+ }
514
+ // Check authentication
515
+ const whoami = spawnSync("npx", ["wrangler", "whoami"], { encoding: "utf-8" });
516
+ if (whoami.status !== 0 || whoami.stdout.includes("not authenticated")) {
517
+ spinner.warn("Not authenticated with Cloudflare - skipping resource creation");
518
+ console.log(pc.gray(" Run 'npx wrangler login' first, then create resources manually."));
519
+ return;
520
+ }
521
+ // Create D1 database
522
+ spinner.text = `Creating D1 database: ${appName}-db`;
523
+ const d1Result = spawnSync("npx", ["wrangler", "d1", "create", `${appName}-db`], { encoding: "utf-8", cwd: targetDir });
524
+ if (d1Result.status === 0) {
525
+ // Extract database ID from output
526
+ const dbIdMatch = d1Result.stdout.match(/database_id\s*=\s*"([^"]+)"/);
527
+ if (dbIdMatch) {
528
+ spinner.succeed(`D1 database created: ${appName}-db (${dbIdMatch[1]})`);
529
+ }
530
+ else {
531
+ spinner.succeed(`D1 database created: ${appName}-db`);
532
+ }
533
+ }
534
+ else if (d1Result.stderr.includes("already exists")) {
535
+ spinner.info(`D1 database already exists: ${appName}-db`);
536
+ }
537
+ else {
538
+ spinner.warn("D1 database creation failed");
539
+ if (verbose) {
540
+ console.log(pc.gray(d1Result.stderr));
541
+ }
542
+ }
543
+ }
544
+ // ============================================================================
545
+ // Utilities
546
+ // ============================================================================
547
+ function runCommand(cmd, args, cwd, verbose) {
548
+ const result = spawnSync(cmd, args, {
549
+ cwd,
550
+ encoding: "utf-8",
551
+ stdio: verbose ? "inherit" : "pipe",
552
+ });
553
+ return {
554
+ success: result.status === 0,
555
+ stdout: result.stdout || "",
556
+ stderr: result.stderr || "",
557
+ };
558
+ }
559
+ function detectPackageManager(preferBun = false) {
560
+ // Check for existing lockfiles first
561
+ if (existsSync("bun.lockb"))
562
+ return "bun";
563
+ if (existsSync("pnpm-lock.yaml"))
564
+ return "pnpm";
565
+ if (existsSync("yarn.lock"))
566
+ return "yarn";
567
+ // If preferBun and bun is available, use it (better peer dep handling)
568
+ if (preferBun) {
569
+ try {
570
+ const result = spawnSync("bun", ["--version"], { encoding: "utf-8" });
571
+ if (result.status === 0)
572
+ return "bun";
573
+ }
574
+ catch {
575
+ // bun not available, fall through
576
+ }
577
+ }
578
+ return "npm";
579
+ }
580
+ function performDryRun(options) {
581
+ const template = getTemplate(options.template);
582
+ console.log(pc.bold("Dry Run - No changes will be made\n"));
583
+ console.log(pc.cyan("Configuration:"));
584
+ console.log(` Template: ${options.template}${template ? ` (${template.name})` : ""}`);
585
+ console.log(` App Name: ${options.appName}`);
586
+ console.log(` Target Dir: ${resolve(process.cwd(), options.appName)}`);
587
+ console.log("");
588
+ console.log(pc.cyan("Steps that would be executed:"));
589
+ console.log(" 1. Create project directory structure");
590
+ console.log(" 2. Generate quickback.config.ts");
591
+ console.log(" 3. Generate feature definitions");
592
+ if (template?.requiresCloudflare && !options.skipCloudflareSetup) {
593
+ console.log(" 4. Create Cloudflare D1 database");
594
+ }
595
+ else {
596
+ console.log(" 4. [SKIP] Cloudflare setup");
597
+ }
598
+ console.log(" 5. Compile project (quickback compile)");
599
+ if (!options.skipInstall) {
600
+ console.log(" 6. Install dependencies");
601
+ }
602
+ else {
603
+ console.log(" 6. [SKIP] Install dependencies");
604
+ }
605
+ console.log(" 7. Generate database schemas");
606
+ if (!template?.requiresCloudflare && !options.skipMigrations) {
607
+ console.log(" 8. Apply local migrations");
608
+ }
609
+ else {
610
+ console.log(" 8. [SKIP] Local migrations");
611
+ }
612
+ console.log(pc.green("\nDry run complete. Remove --dry-run to execute."));
613
+ }
614
+ function printSuccessMessage(appName, template) {
615
+ console.log("");
616
+ console.log(pc.green("✔ Project created successfully!"));
617
+ console.log("");
618
+ console.log(pc.bold("Next steps:"));
619
+ console.log(pc.cyan(` cd ${appName}`));
620
+ console.log(pc.cyan(" npm run dev"));
621
+ console.log("");
622
+ if (template.requiresCloudflare) {
623
+ console.log(pc.bold("Deploy to Cloudflare:"));
624
+ console.log(pc.cyan(" npm run deploy"));
625
+ console.log("");
626
+ }
627
+ console.log(pc.bold("Useful commands:"));
628
+ console.log(" npm run dev " + pc.gray("Start development server"));
629
+ console.log(" quickback compile " + pc.gray("Regenerate after editing definitions"));
630
+ console.log(" npx drizzle-kit generate " + pc.gray("Generate new migrations"));
631
+ console.log(" npx drizzle-kit migrate " + pc.gray("Apply database migrations"));
632
+ if (template.requiresCloudflare) {
633
+ console.log(" npm run deploy " + pc.gray("Deploy to Cloudflare Workers"));
634
+ }
635
+ console.log("");
636
+ console.log(pc.bold("Project structure:"));
637
+ console.log(pc.gray(` ${appName}/`));
638
+ console.log(pc.gray(" ├── quickback/ ← edit definitions here"));
639
+ console.log(pc.gray(" ├── src/ ← generated API code"));
640
+ console.log(pc.gray(" └── drizzle/ ← database migrations"));
641
+ console.log("");
642
+ }
643
+ function printHelp() {
644
+ console.log(`
645
+ ${pc.bold("quickback create")} - Create a new Quickback project
646
+
647
+ ${pc.bold("Usage:")}
648
+ quickback create <template> <app-name>
649
+ quickback create <app-name> ${pc.gray("(interactive template selection)")}
650
+
651
+ ${pc.bold("Examples:")}
652
+ quickback create betterauth-d1-cloudflare my-app
653
+ quickback create cloudflare my-app ${pc.gray("(alias)")}
654
+ quickback create bun my-app ${pc.gray("(local development)")}
655
+
656
+ ${pc.bold("Options:")}
657
+ --skip-cloudflare-setup Skip automatic Cloudflare resource creation
658
+ --skip-install Skip npm/bun install
659
+ --skip-migrations Skip database migrations
660
+ --dry-run Preview what would be created
661
+ --verbose, -v Show detailed output
662
+ --list, -l List available templates
663
+ --help, -h Show this help
664
+
665
+ ${pc.bold("Templates:")}
666
+ ${formatTemplateList()}
667
+ `);
668
+ }
669
+ //# sourceMappingURL=create.js.map