create-githat-app 1.0.3 → 1.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.
Files changed (43) hide show
  1. package/dist/cli.js +271 -89
  2. package/package.json +1 -1
  3. package/templates/fullstack/apps-api-express/.env.example.hbs +6 -0
  4. package/templates/fullstack/apps-api-express/.env.local.hbs +6 -0
  5. package/templates/fullstack/apps-api-express/package.json.hbs +24 -0
  6. package/templates/fullstack/apps-api-express/src/index.ts.hbs +41 -0
  7. package/templates/fullstack/apps-api-express/src/routes/health.ts.hbs +11 -0
  8. package/templates/fullstack/apps-api-express/src/routes/users.ts.hbs +43 -0
  9. package/templates/fullstack/apps-api-express/tsconfig.json.hbs +16 -0
  10. package/templates/fullstack/apps-api-fastify/.env.example.hbs +6 -0
  11. package/templates/fullstack/apps-api-fastify/.env.local.hbs +6 -0
  12. package/templates/fullstack/apps-api-fastify/package.json.hbs +22 -0
  13. package/templates/fullstack/apps-api-fastify/src/index.ts.hbs +28 -0
  14. package/templates/fullstack/apps-api-fastify/src/routes/health.ts.hbs +11 -0
  15. package/templates/fullstack/apps-api-fastify/src/routes/users.ts.hbs +43 -0
  16. package/templates/fullstack/apps-api-fastify/tsconfig.json.hbs +16 -0
  17. package/templates/fullstack/apps-api-hono/.env.example.hbs +6 -0
  18. package/templates/fullstack/apps-api-hono/.env.local.hbs +6 -0
  19. package/templates/fullstack/apps-api-hono/package.json.hbs +22 -0
  20. package/templates/fullstack/apps-api-hono/src/index.ts.hbs +35 -0
  21. package/templates/fullstack/apps-api-hono/src/routes/health.ts.hbs +11 -0
  22. package/templates/fullstack/apps-api-hono/src/routes/users.ts.hbs +43 -0
  23. package/templates/fullstack/apps-api-hono/tsconfig.json.hbs +16 -0
  24. package/templates/fullstack/apps-web-nextjs/.env.example.hbs +5 -0
  25. package/templates/fullstack/apps-web-nextjs/.env.local.hbs +5 -0
  26. package/templates/fullstack/apps-web-nextjs/app/(auth)/forgot-password/page.tsx.hbs +11 -0
  27. package/templates/fullstack/apps-web-nextjs/app/(auth)/reset-password/page.tsx.hbs +11 -0
  28. package/templates/fullstack/apps-web-nextjs/app/(auth)/sign-in/page.tsx.hbs +9 -0
  29. package/templates/fullstack/apps-web-nextjs/app/(auth)/sign-up/page.tsx.hbs +9 -0
  30. package/templates/fullstack/apps-web-nextjs/app/(auth)/verify-email/page.tsx.hbs +11 -0
  31. package/templates/fullstack/apps-web-nextjs/app/dashboard/layout.tsx.hbs +9 -0
  32. package/templates/fullstack/apps-web-nextjs/app/dashboard/page.tsx.hbs +27 -0
  33. package/templates/fullstack/apps-web-nextjs/app/globals.css.hbs +20 -0
  34. package/templates/fullstack/apps-web-nextjs/app/layout.tsx.hbs +26 -0
  35. package/templates/fullstack/apps-web-nextjs/app/page.tsx.hbs +18 -0
  36. package/templates/fullstack/apps-web-nextjs/next.config.ts.hbs +15 -0
  37. package/templates/fullstack/apps-web-nextjs/package.json.hbs +33 -0
  38. package/templates/fullstack/apps-web-nextjs/postcss.config.mjs.hbs +9 -0
  39. package/templates/fullstack/apps-web-nextjs/tsconfig.json.hbs +21 -0
  40. package/templates/fullstack/root/.gitignore.hbs +42 -0
  41. package/templates/fullstack/root/githat.yaml.hbs +17 -0
  42. package/templates/fullstack/root/package.json.hbs +15 -0
  43. package/templates/fullstack/root/turbo.json.hbs +20 -0
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@ import { createRequire } from 'module'; const require = createRequire(import.met
2
2
 
3
3
  // src/cli.ts
4
4
  import { Command as Command7 } from "commander";
5
- import * as p9 from "@clack/prompts";
5
+ import * as p11 from "@clack/prompts";
6
6
  import chalk9 from "chalk";
7
7
 
8
8
  // src/utils/ascii.ts
@@ -11,7 +11,7 @@ import gradient from "gradient-string";
11
11
  import chalk from "chalk";
12
12
 
13
13
  // src/constants.ts
14
- var VERSION = "1.0.3";
14
+ var VERSION = "1.0.4";
15
15
  var DEFAULT_API_URL = "https://api.githat.io";
16
16
  var DASHBOARD_URL = "https://githat.io/dashboard/apps";
17
17
  var BRAND_COLORS = ["#7c3aed", "#6366f1", "#8b5cf6"];
@@ -73,6 +73,48 @@ var DEPS = {
73
73
  "drizzle-sqlite": {
74
74
  dependencies: { "drizzle-orm": "^0.39.0", "better-sqlite3": "^11.0.0" },
75
75
  devDependencies: { "drizzle-kit": "^0.30.0", "@types/better-sqlite3": "^7.6.0" }
76
+ },
77
+ // Backend frameworks for fullstack
78
+ hono: {
79
+ dependencies: {
80
+ hono: "^4.6.0",
81
+ "@hono/node-server": "^1.13.0"
82
+ },
83
+ devDependencies: {
84
+ typescript: "^5.9.0",
85
+ "@types/node": "^22.0.0",
86
+ tsx: "^4.19.0"
87
+ }
88
+ },
89
+ express: {
90
+ dependencies: {
91
+ express: "^5.0.0",
92
+ cors: "^2.8.5"
93
+ },
94
+ devDependencies: {
95
+ typescript: "^5.9.0",
96
+ "@types/node": "^22.0.0",
97
+ "@types/express": "^5.0.0",
98
+ "@types/cors": "^2.8.17",
99
+ tsx: "^4.19.0"
100
+ }
101
+ },
102
+ fastify: {
103
+ dependencies: {
104
+ fastify: "^5.2.0",
105
+ "@fastify/cors": "^10.0.0"
106
+ },
107
+ devDependencies: {
108
+ typescript: "^5.9.0",
109
+ "@types/node": "^22.0.0",
110
+ tsx: "^4.19.0"
111
+ }
112
+ },
113
+ // Turborepo for fullstack monorepo
114
+ turbo: {
115
+ devDependencies: {
116
+ turbo: "^2.3.0"
117
+ }
76
118
  }
77
119
  };
78
120
 
@@ -123,35 +165,59 @@ function sectionHeader(title) {
123
165
  console.log(dim(` \u2500\u2500\u2500 ${title} ${"\u2500".repeat(lineLen)}`));
124
166
  console.log("");
125
167
  }
126
- function displaySuccess(projectName, packageManager, framework, hasPublishableKey = true) {
168
+ function displaySuccess(projectName, packageManager, framework, hasPublishableKey = true, isFullstack = false) {
127
169
  const devCmd = packageManager === "npm" ? "npm run dev" : `${packageManager} dev`;
128
170
  const port = framework === "react-vite" ? "5173" : "3000";
129
171
  console.log("");
130
- drawBox([
131
- `${violet("\u2726")} Your GitHat app is ready!`,
132
- "",
133
- `${violet("$")} cd ${projectName}`,
134
- `${violet("$")} ${devCmd}`,
135
- "",
136
- dim(`\u2192 http://localhost:${port}`),
137
- "",
138
- chalk.bold("Routes"),
139
- `${violet("/sign-in")} Sign in`,
140
- `${violet("/sign-up")} Create account`,
141
- `${violet("/dashboard")} Protected dashboard`,
142
- "",
143
- ...hasPublishableKey ? [] : [
144
- chalk.yellow("No key configured \u2014 auth works on localhost."),
145
- `For production: ${violet("githat.io/dashboard/apps")}`,
146
- ""
147
- ],
148
- dim("Docs \u2192 https://githat.io/docs/sdk")
149
- ]);
172
+ if (isFullstack) {
173
+ drawBox([
174
+ `${violet("\u2726")} Your GitHat fullstack app is ready!`,
175
+ "",
176
+ `${violet("$")} cd ${projectName}`,
177
+ `${violet("$")} ${devCmd}`,
178
+ "",
179
+ dim(`\u2192 Web: http://localhost:3000`),
180
+ dim(`\u2192 API: http://localhost:3001`),
181
+ "",
182
+ chalk.bold("Structure"),
183
+ `${violet("apps/web")} Next.js frontend`,
184
+ `${violet("apps/api")} API backend`,
185
+ `${violet("packages/")} Shared code`,
186
+ "",
187
+ ...hasPublishableKey ? [] : [
188
+ chalk.yellow("No key configured \u2014 auth works on localhost."),
189
+ `For production: ${violet("githat.io/dashboard/apps")}`,
190
+ ""
191
+ ],
192
+ dim("Docs \u2192 https://githat.io/docs/sdk")
193
+ ]);
194
+ } else {
195
+ drawBox([
196
+ `${violet("\u2726")} Your GitHat app is ready!`,
197
+ "",
198
+ `${violet("$")} cd ${projectName}`,
199
+ `${violet("$")} ${devCmd}`,
200
+ "",
201
+ dim(`\u2192 http://localhost:${port}`),
202
+ "",
203
+ chalk.bold("Routes"),
204
+ `${violet("/sign-in")} Sign in`,
205
+ `${violet("/sign-up")} Create account`,
206
+ `${violet("/dashboard")} Protected dashboard`,
207
+ "",
208
+ ...hasPublishableKey ? [] : [
209
+ chalk.yellow("No key configured \u2014 auth works on localhost."),
210
+ `For production: ${violet("githat.io/dashboard/apps")}`,
211
+ ""
212
+ ],
213
+ dim("Docs \u2192 https://githat.io/docs/sdk")
214
+ ]);
215
+ }
150
216
  console.log("");
151
217
  }
152
218
 
153
219
  // src/prompts/index.ts
154
- import * as p6 from "@clack/prompts";
220
+ import * as p8 from "@clack/prompts";
155
221
 
156
222
  // src/prompts/project.ts
157
223
  import * as p from "@clack/prompts";
@@ -204,8 +270,41 @@ async function promptProject(initialName) {
204
270
  };
205
271
  }
206
272
 
207
- // src/prompts/framework.ts
273
+ // src/prompts/project-type.ts
208
274
  import * as p2 from "@clack/prompts";
275
+ async function promptProjectType() {
276
+ const answers = await p2.group(
277
+ {
278
+ projectType: () => p2.select({
279
+ message: "Project type",
280
+ options: [
281
+ {
282
+ value: "frontend",
283
+ label: "Frontend only",
284
+ hint: "Next.js with API routes"
285
+ },
286
+ {
287
+ value: "fullstack",
288
+ label: "Fullstack",
289
+ hint: "Next.js + separate API (Turborepo)"
290
+ }
291
+ ]
292
+ })
293
+ },
294
+ {
295
+ onCancel: () => {
296
+ p2.cancel("Setup cancelled.");
297
+ process.exit(0);
298
+ }
299
+ }
300
+ );
301
+ return {
302
+ projectType: answers.projectType
303
+ };
304
+ }
305
+
306
+ // src/prompts/framework.ts
307
+ import * as p3 from "@clack/prompts";
209
308
 
210
309
  // src/utils/package-manager.ts
211
310
  function detectPackageManager() {
@@ -228,16 +327,16 @@ function getInstallCommand(pm) {
228
327
  // src/prompts/framework.ts
229
328
  async function promptFramework(typescriptOverride) {
230
329
  const packageManager = detectPackageManager();
231
- const answers = await p2.group(
330
+ const answers = await p3.group(
232
331
  {
233
- framework: () => p2.select({
332
+ framework: () => p3.select({
234
333
  message: "Framework",
235
334
  options: [
236
335
  { value: "nextjs", label: "Next.js 16", hint: "App Router \xB7 SSR \xB7 middleware auth" },
237
336
  { value: "react-vite", label: "React 19 + Vite 7", hint: "SPA \xB7 client-side routing" }
238
337
  ]
239
338
  }),
240
- typescript: () => typescriptOverride !== void 0 ? Promise.resolve(typescriptOverride) : p2.select({
339
+ typescript: () => typescriptOverride !== void 0 ? Promise.resolve(typescriptOverride) : p3.select({
241
340
  message: "Language",
242
341
  options: [
243
342
  { value: true, label: "TypeScript", hint: "recommended" },
@@ -247,7 +346,7 @@ async function promptFramework(typescriptOverride) {
247
346
  },
248
347
  {
249
348
  onCancel: () => {
250
- p2.cancel("Setup cancelled.");
349
+ p3.cancel("Setup cancelled.");
251
350
  process.exit(0);
252
351
  }
253
352
  }
@@ -259,9 +358,47 @@ async function promptFramework(typescriptOverride) {
259
358
  };
260
359
  }
261
360
 
361
+ // src/prompts/backend.ts
362
+ import * as p4 from "@clack/prompts";
363
+ async function promptBackend() {
364
+ const answers = await p4.group(
365
+ {
366
+ backendFramework: () => p4.select({
367
+ message: "Backend framework",
368
+ options: [
369
+ {
370
+ value: "hono",
371
+ label: "Hono",
372
+ hint: "recommended \xB7 serverless-native \xB7 14KB"
373
+ },
374
+ {
375
+ value: "express",
376
+ label: "Express",
377
+ hint: "classic Node.js \xB7 large ecosystem"
378
+ },
379
+ {
380
+ value: "fastify",
381
+ label: "Fastify",
382
+ hint: "high performance \xB7 schema validation"
383
+ }
384
+ ]
385
+ })
386
+ },
387
+ {
388
+ onCancel: () => {
389
+ p4.cancel("Setup cancelled.");
390
+ process.exit(0);
391
+ }
392
+ }
393
+ );
394
+ return {
395
+ backendFramework: answers.backendFramework
396
+ };
397
+ }
398
+
262
399
  // src/prompts/githat.ts
263
400
  import { execSync } from "child_process";
264
- import * as p3 from "@clack/prompts";
401
+ import * as p5 from "@clack/prompts";
265
402
  function openBrowser(url) {
266
403
  try {
267
404
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
@@ -272,7 +409,7 @@ function openBrowser(url) {
272
409
  async function promptGitHat(existingKey) {
273
410
  let publishableKey = existingKey || "";
274
411
  if (!publishableKey) {
275
- const connectChoice = await p3.select({
412
+ const connectChoice = await p5.select({
276
413
  message: "Connect to GitHat",
277
414
  options: [
278
415
  { value: "browser", label: "Sign in with browser", hint: "opens githat.io \u2014 recommended" },
@@ -280,41 +417,41 @@ async function promptGitHat(existingKey) {
280
417
  { value: "skip", label: "Skip for now", hint: "add key to .env later" }
281
418
  ]
282
419
  });
283
- if (p3.isCancel(connectChoice)) {
284
- p3.cancel("Setup cancelled.");
420
+ if (p5.isCancel(connectChoice)) {
421
+ p5.cancel("Setup cancelled.");
285
422
  process.exit(0);
286
423
  }
287
424
  if (connectChoice === "browser") {
288
- p3.log.step("Opening githat.io in your browser...");
425
+ p5.log.step("Opening githat.io in your browser...");
289
426
  openBrowser("https://githat.io/sign-up");
290
- p3.log.info("Sign up (or sign in), then go to Dashboard \u2192 Apps to copy your key.");
291
- const pastedKey = await p3.text({
427
+ p5.log.info("Sign up (or sign in), then go to Dashboard \u2192 Apps to copy your key.");
428
+ const pastedKey = await p5.text({
292
429
  message: "Paste your publishable key",
293
430
  placeholder: "pk_live_...",
294
431
  validate: validatePublishableKey
295
432
  });
296
- if (p3.isCancel(pastedKey)) {
297
- p3.cancel("Setup cancelled.");
433
+ if (p5.isCancel(pastedKey)) {
434
+ p5.cancel("Setup cancelled.");
298
435
  process.exit(0);
299
436
  }
300
437
  publishableKey = pastedKey || "";
301
438
  } else if (connectChoice === "paste") {
302
- const pastedKey = await p3.text({
439
+ const pastedKey = await p5.text({
303
440
  message: "Publishable key",
304
441
  placeholder: `pk_live_... (get one at ${DASHBOARD_URL})`,
305
442
  validate: validatePublishableKey
306
443
  });
307
- if (p3.isCancel(pastedKey)) {
308
- p3.cancel("Setup cancelled.");
444
+ if (p5.isCancel(pastedKey)) {
445
+ p5.cancel("Setup cancelled.");
309
446
  process.exit(0);
310
447
  }
311
448
  publishableKey = pastedKey || "";
312
449
  } else if (connectChoice === "skip") {
313
- p3.log.info("Auth works on localhost without a key (CORS bypass for development).");
314
- p3.log.info("Sign up at githat.io \u2014 a publishable key is auto-created for you.");
450
+ p5.log.info("Auth works on localhost without a key (CORS bypass for development).");
451
+ p5.log.info("Sign up at githat.io \u2014 a publishable key is auto-created for you.");
315
452
  }
316
453
  }
317
- const authFeatures = await p3.multiselect({
454
+ const authFeatures = await p5.multiselect({
318
455
  message: "Auth features",
319
456
  options: [
320
457
  { value: "forgot-password", label: "Forgot password", hint: "reset via email" },
@@ -326,8 +463,8 @@ async function promptGitHat(existingKey) {
326
463
  initialValues: ["forgot-password"],
327
464
  required: false
328
465
  });
329
- if (p3.isCancel(authFeatures)) {
330
- p3.cancel("Setup cancelled.");
466
+ if (p5.isCancel(authFeatures)) {
467
+ p5.cancel("Setup cancelled.");
331
468
  process.exit(0);
332
469
  }
333
470
  return {
@@ -338,11 +475,11 @@ async function promptGitHat(existingKey) {
338
475
  }
339
476
 
340
477
  // src/prompts/features.ts
341
- import * as p4 from "@clack/prompts";
478
+ import * as p6 from "@clack/prompts";
342
479
  async function promptFeatures() {
343
- const answers = await p4.group(
480
+ const answers = await p6.group(
344
481
  {
345
- databaseChoice: () => p4.select({
482
+ databaseChoice: () => p6.select({
346
483
  message: "Database",
347
484
  options: [
348
485
  { value: "none", label: "None", hint: "use GitHat backend directly" },
@@ -352,12 +489,12 @@ async function promptFeatures() {
352
489
  { value: "drizzle-sqlite", label: "Drizzle + SQLite" }
353
490
  ]
354
491
  }),
355
- useTailwind: () => p4.confirm({ message: "Tailwind CSS?", initialValue: true }),
356
- includeDashboard: () => p4.confirm({ message: "Include dashboard?", initialValue: true })
492
+ useTailwind: () => p6.confirm({ message: "Tailwind CSS?", initialValue: true }),
493
+ includeDashboard: () => p6.confirm({ message: "Include dashboard?", initialValue: true })
357
494
  },
358
495
  {
359
496
  onCancel: () => {
360
- p4.cancel("Setup cancelled.");
497
+ p6.cancel("Setup cancelled.");
361
498
  process.exit(0);
362
499
  }
363
500
  }
@@ -371,14 +508,14 @@ async function promptFeatures() {
371
508
  }
372
509
 
373
510
  // src/prompts/finalize.ts
374
- import * as p5 from "@clack/prompts";
511
+ import * as p7 from "@clack/prompts";
375
512
  async function promptFinalize() {
376
- const installDeps = await p5.confirm({
513
+ const installDeps = await p7.confirm({
377
514
  message: "Install dependencies?",
378
515
  initialValue: true
379
516
  });
380
- if (p5.isCancel(installDeps)) {
381
- p5.cancel("Setup cancelled.");
517
+ if (p7.isCancel(installDeps)) {
518
+ p7.cancel("Setup cancelled.");
382
519
  process.exit(0);
383
520
  }
384
521
  return {
@@ -397,6 +534,7 @@ function getDefaults(projectName, publishableKey, typescript) {
397
534
  projectName,
398
535
  businessName: displayName,
399
536
  description: `${displayName} \u2014 Built with GitHat`,
537
+ projectType: "frontend",
400
538
  framework: "nextjs",
401
539
  typescript: typescript ?? true,
402
540
  packageManager: detectPackageManager(),
@@ -413,21 +551,26 @@ function getDefaults(projectName, publishableKey, typescript) {
413
551
  }
414
552
  async function runPrompts(args) {
415
553
  if (args.yes && args.initialName) {
416
- p6.log.info("Using defaults (--yes flag)");
554
+ p8.log.info("Using defaults (--yes flag)");
417
555
  return getDefaults(args.initialName, args.publishableKey, args.typescript);
418
556
  }
419
- p6.intro("Let\u2019s set up your GitHat app");
557
+ p8.intro("Let's set up your GitHat app");
420
558
  sectionHeader("Project");
421
559
  const project = await promptProject(args.initialName);
560
+ const projectType = await promptProjectType();
422
561
  sectionHeader("Stack");
423
562
  const framework = await promptFramework(args.typescript);
563
+ let backend = {};
564
+ if (projectType.projectType === "fullstack") {
565
+ backend = await promptBackend();
566
+ }
424
567
  sectionHeader("Connect");
425
568
  const githat = await promptGitHat(args.publishableKey);
426
569
  sectionHeader("Features");
427
570
  const features = await promptFeatures();
428
571
  sectionHeader("Finish");
429
572
  const finalize = await promptFinalize();
430
- return { ...project, ...framework, ...githat, ...features, ...finalize };
573
+ return { ...project, ...projectType, ...framework, ...backend, ...githat, ...features, ...finalize };
431
574
  }
432
575
  function answersToContext(answers) {
433
576
  return {
@@ -439,6 +582,9 @@ function answersToContext(answers) {
439
582
  packageManager: answers.packageManager,
440
583
  publishableKey: answers.publishableKey,
441
584
  apiUrl: answers.apiUrl,
585
+ // Project type
586
+ projectType: answers.projectType,
587
+ backendFramework: answers.backendFramework,
442
588
  useDatabase: answers.databaseChoice !== "none",
443
589
  databaseChoice: answers.databaseChoice,
444
590
  useTailwind: answers.useTailwind,
@@ -458,7 +604,7 @@ function answersToContext(answers) {
458
604
  import fs3 from "fs-extra";
459
605
  import path3 from "path";
460
606
  import { execSync as execSync3 } from "child_process";
461
- import * as p7 from "@clack/prompts";
607
+ import * as p9 from "@clack/prompts";
462
608
  import chalk2 from "chalk";
463
609
 
464
610
  // src/utils/template-engine.ts
@@ -611,26 +757,33 @@ function initGit(cwd) {
611
757
  async function scaffold(context, options) {
612
758
  const root = path3.resolve(process.cwd(), context.projectName);
613
759
  if (fs3.existsSync(root)) {
614
- p7.cancel(`Directory "${context.projectName}" already exists.`);
760
+ p9.cancel(`Directory "${context.projectName}" already exists.`);
615
761
  process.exit(1);
616
762
  }
763
+ const isFullstack = context.projectType === "fullstack";
617
764
  await withSpinner("Creating project structure...", async () => {
618
765
  fs3.ensureDirSync(root);
619
766
  const templatesRoot = getTemplatesRoot();
620
- const frameworkDir = path3.join(templatesRoot, context.framework);
621
- if (!fs3.existsSync(frameworkDir)) {
622
- throw new Error(`Templates not found at ${frameworkDir}. This is a bug \u2014 please report it.`);
623
- }
624
- renderTemplateDirectory(frameworkDir, root, context);
625
- const baseDir = path3.join(templatesRoot, "base");
626
- if (fs3.existsSync(baseDir)) {
627
- renderTemplateDirectory(baseDir, root, context);
767
+ if (isFullstack) {
768
+ scaffoldFullstack(templatesRoot, root, context);
769
+ } else {
770
+ const frameworkDir = path3.join(templatesRoot, context.framework);
771
+ if (!fs3.existsSync(frameworkDir)) {
772
+ throw new Error(`Templates not found at ${frameworkDir}. This is a bug \u2014 please report it.`);
773
+ }
774
+ renderTemplateDirectory(frameworkDir, root, context);
775
+ const baseDir = path3.join(templatesRoot, "base");
776
+ if (fs3.existsSync(baseDir)) {
777
+ renderTemplateDirectory(baseDir, root, context);
778
+ }
628
779
  }
629
780
  }, "Project structure created");
630
- await withSpinner("Generating package.json...", async () => {
631
- const pkg = buildPackageJson(context);
632
- writeJson(root, "package.json", pkg);
633
- }, "package.json generated");
781
+ if (!isFullstack) {
782
+ await withSpinner("Generating package.json...", async () => {
783
+ const pkg = buildPackageJson(context);
784
+ writeJson(root, "package.json", pkg);
785
+ }, "package.json generated");
786
+ }
634
787
  if (options.initGit) {
635
788
  const gitSpinner = createSpinner("Initializing git repository...");
636
789
  gitSpinner.start();
@@ -651,32 +804,61 @@ async function scaffold(context, options) {
651
804
  } catch (err) {
652
805
  const msg = err.message || "";
653
806
  if (msg.includes("TIMEOUT")) {
654
- p7.log.warn(`Install timed out. Run ${chalk2.cyan(installCmd)} manually.`);
807
+ p9.log.warn(`Install timed out. Run ${chalk2.cyan(installCmd)} manually.`);
655
808
  } else {
656
- p7.log.warn(`Could not auto-install. Run ${chalk2.cyan(installCmd)} manually.`);
809
+ p9.log.warn(`Could not auto-install. Run ${chalk2.cyan(installCmd)} manually.`);
657
810
  }
658
811
  }
659
812
  },
660
813
  "Dependencies installed"
661
814
  );
662
815
  }
663
- p7.outro("Setup complete!");
664
- displaySuccess(context.projectName, context.packageManager, context.framework, !!context.publishableKey);
816
+ p9.outro("Setup complete!");
817
+ displaySuccess(context.projectName, context.packageManager, context.framework, !!context.publishableKey, isFullstack);
665
818
  if (!options.skipPrompts) {
666
- const starPrompt = await p7.confirm({
819
+ const starPrompt = await p9.confirm({
667
820
  message: "Star GitHat on GitHub? (helps us grow!)",
668
821
  initialValue: false
669
822
  });
670
- if (!p7.isCancel(starPrompt) && starPrompt) {
823
+ if (!p9.isCancel(starPrompt) && starPrompt) {
671
824
  try {
672
825
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
673
826
  execSync3(`${cmd} "https://github.com/GitHat-IO/githat"`, { stdio: "ignore" });
674
827
  } catch {
675
- p7.log.info("Visit https://github.com/GitHat-IO/githat to star us!");
828
+ p9.log.info("Visit https://github.com/GitHat-IO/githat to star us!");
676
829
  }
677
830
  }
678
831
  }
679
832
  }
833
+ function scaffoldFullstack(templatesRoot, root, context) {
834
+ const fullstackDir = path3.join(templatesRoot, "fullstack");
835
+ const rootDir = path3.join(fullstackDir, "root");
836
+ if (fs3.existsSync(rootDir)) {
837
+ renderTemplateDirectory(rootDir, root, context);
838
+ }
839
+ const appsDir = path3.join(root, "apps");
840
+ fs3.ensureDirSync(appsDir);
841
+ const webDir = path3.join(appsDir, "web");
842
+ fs3.ensureDirSync(webDir);
843
+ const webTemplateDir = path3.join(fullstackDir, `apps-web-${context.framework}`);
844
+ if (fs3.existsSync(webTemplateDir)) {
845
+ renderTemplateDirectory(webTemplateDir, webDir, context);
846
+ } else {
847
+ throw new Error(`Web app templates not found at ${webTemplateDir}. This is a bug \u2014 please report it.`);
848
+ }
849
+ const apiDir = path3.join(appsDir, "api");
850
+ fs3.ensureDirSync(apiDir);
851
+ const backendFramework = context.backendFramework || "hono";
852
+ const apiTemplateDir = path3.join(fullstackDir, `apps-api-${backendFramework}`);
853
+ if (fs3.existsSync(apiTemplateDir)) {
854
+ renderTemplateDirectory(apiTemplateDir, apiDir, context);
855
+ } else {
856
+ throw new Error(`API templates not found at ${apiTemplateDir}. This is a bug \u2014 please report it.`);
857
+ }
858
+ const packagesDir = path3.join(root, "packages");
859
+ fs3.ensureDirSync(packagesDir);
860
+ fs3.writeFileSync(path3.join(packagesDir, ".gitkeep"), "");
861
+ }
680
862
 
681
863
  // src/commands/skills/index.ts
682
864
  import { Command as Command6 } from "commander";
@@ -996,7 +1178,7 @@ import { Command as Command5 } from "commander";
996
1178
  import chalk7 from "chalk";
997
1179
  import * as fs6 from "fs";
998
1180
  import * as path6 from "path";
999
- import * as p8 from "@clack/prompts";
1181
+ import * as p10 from "@clack/prompts";
1000
1182
  var SKILL_TYPES = ["template", "integration", "ui", "ai", "workflow"];
1001
1183
  function generateReadme(manifest) {
1002
1184
  return `# ${manifest.name}
@@ -1144,7 +1326,7 @@ var initCommand = new Command5("init").description("Initialize a new skill packa
1144
1326
  if (options.type && SKILL_TYPES.includes(options.type)) {
1145
1327
  type = options.type;
1146
1328
  } else {
1147
- const result = await p8.select({
1329
+ const result = await p10.select({
1148
1330
  message: "What type of skill are you creating?",
1149
1331
  options: [
1150
1332
  { value: "integration", label: "Integration", hint: "Connect to external services (Stripe, SendGrid, etc.)" },
@@ -1154,19 +1336,19 @@ var initCommand = new Command5("init").description("Initialize a new skill packa
1154
1336
  { value: "workflow", label: "Workflow", hint: "Automation recipes" }
1155
1337
  ]
1156
1338
  });
1157
- if (p8.isCancel(result)) {
1158
- p8.cancel("Operation cancelled");
1339
+ if (p10.isCancel(result)) {
1340
+ p10.cancel("Operation cancelled");
1159
1341
  process.exit(0);
1160
1342
  }
1161
1343
  type = result;
1162
1344
  }
1163
- const description = await p8.text({
1345
+ const description = await p10.text({
1164
1346
  message: "Short description:",
1165
1347
  placeholder: `A ${type} skill for...`,
1166
1348
  validate: (v) => v.length < 10 ? "Description must be at least 10 characters" : void 0
1167
1349
  });
1168
- if (p8.isCancel(description)) {
1169
- p8.cancel("Operation cancelled");
1350
+ if (p10.isCancel(description)) {
1351
+ p10.cancel("Operation cancelled");
1170
1352
  process.exit(0);
1171
1353
  }
1172
1354
  const manifest = {
@@ -1259,7 +1441,7 @@ program.command("create [project-name]", { isDefault: true }).description("Scaff
1259
1441
  displayBanner();
1260
1442
  const typescript = opts.js ? false : opts.ts ? true : void 0;
1261
1443
  if (opts.yes && !projectName) {
1262
- p9.cancel(chalk9.red("Project name is required when using --yes flag"));
1444
+ p11.cancel(chalk9.red("Project name is required when using --yes flag"));
1263
1445
  process.exit(1);
1264
1446
  }
1265
1447
  const answers = await runPrompts({
@@ -1275,7 +1457,7 @@ program.command("create [project-name]", { isDefault: true }).description("Scaff
1275
1457
  skipPrompts: opts.yes
1276
1458
  });
1277
1459
  } catch (err) {
1278
- p9.cancel(chalk9.red(err.message || "Something went wrong."));
1460
+ p11.cancel(chalk9.red(err.message || "Something went wrong."));
1279
1461
  process.exit(1);
1280
1462
  }
1281
1463
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-githat-app",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "GitHat CLI — scaffold apps and manage the skills marketplace",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,6 @@
1
+ # GitHat Configuration
2
+ GITHAT_PUBLISHABLE_KEY=pk_live_your_key_here
3
+
4
+ # Server
5
+ PORT=3001
6
+ CORS_ORIGIN=http://localhost:3000
@@ -0,0 +1,6 @@
1
+ # GitHat Configuration
2
+ GITHAT_PUBLISHABLE_KEY={{#if publishableKey}}{{publishableKey}}{{else}}pk_live_your_key_here{{/if}}
3
+
4
+ # Server
5
+ PORT=3001
6
+ CORS_ORIGIN=http://localhost:3000
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@{{projectName}}/api",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "tsx watch src/index.ts",
8
+ "build": "tsc",
9
+ "start": "node dist/index.js",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "@githat/nextjs": "^0.2.2",
14
+ "cors": "^2.8.5",
15
+ "express": "^5.0.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/cors": "^2.8.17",
19
+ "@types/express": "^5.0.0",
20
+ "@types/node": "^22.0.0",
21
+ "tsx": "^4.19.0",
22
+ "typescript": "^5.9.0"
23
+ }
24
+ }