create-githat-app 1.0.3 → 1.0.5
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/cli.js +283 -123
- package/package.json +1 -1
- package/templates/fullstack/apps-api-express/.env.example.hbs +6 -0
- package/templates/fullstack/apps-api-express/.env.local.hbs +6 -0
- package/templates/fullstack/apps-api-express/package.json.hbs +24 -0
- package/templates/fullstack/apps-api-express/src/index.ts.hbs +41 -0
- package/templates/fullstack/apps-api-express/src/routes/health.ts.hbs +11 -0
- package/templates/fullstack/apps-api-express/src/routes/users.ts.hbs +43 -0
- package/templates/fullstack/apps-api-express/tsconfig.json.hbs +16 -0
- package/templates/fullstack/apps-api-fastify/.env.example.hbs +6 -0
- package/templates/fullstack/apps-api-fastify/.env.local.hbs +6 -0
- package/templates/fullstack/apps-api-fastify/package.json.hbs +22 -0
- package/templates/fullstack/apps-api-fastify/src/index.ts.hbs +28 -0
- package/templates/fullstack/apps-api-fastify/src/routes/health.ts.hbs +11 -0
- package/templates/fullstack/apps-api-fastify/src/routes/users.ts.hbs +43 -0
- package/templates/fullstack/apps-api-fastify/tsconfig.json.hbs +16 -0
- package/templates/fullstack/apps-api-hono/.env.example.hbs +6 -0
- package/templates/fullstack/apps-api-hono/.env.local.hbs +6 -0
- package/templates/fullstack/apps-api-hono/package.json.hbs +22 -0
- package/templates/fullstack/apps-api-hono/src/index.ts.hbs +35 -0
- package/templates/fullstack/apps-api-hono/src/routes/health.ts.hbs +11 -0
- package/templates/fullstack/apps-api-hono/src/routes/users.ts.hbs +43 -0
- package/templates/fullstack/apps-api-hono/tsconfig.json.hbs +16 -0
- package/templates/fullstack/apps-web-nextjs/.env.example.hbs +5 -0
- package/templates/fullstack/apps-web-nextjs/.env.local.hbs +5 -0
- package/templates/fullstack/apps-web-nextjs/app/(auth)/forgot-password/page.tsx.hbs +11 -0
- package/templates/fullstack/apps-web-nextjs/app/(auth)/reset-password/page.tsx.hbs +39 -0
- package/templates/fullstack/apps-web-nextjs/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/fullstack/apps-web-nextjs/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/fullstack/apps-web-nextjs/app/(auth)/verify-email/page.tsx.hbs +11 -0
- package/templates/fullstack/apps-web-nextjs/app/dashboard/layout.tsx.hbs +15 -0
- package/templates/fullstack/apps-web-nextjs/app/dashboard/page.tsx.hbs +27 -0
- package/templates/fullstack/apps-web-nextjs/app/globals.css.hbs +20 -0
- package/templates/fullstack/apps-web-nextjs/app/layout.tsx.hbs +26 -0
- package/templates/fullstack/apps-web-nextjs/app/page.tsx.hbs +18 -0
- package/templates/fullstack/apps-web-nextjs/next.config.ts.hbs +15 -0
- package/templates/fullstack/apps-web-nextjs/package.json.hbs +33 -0
- package/templates/fullstack/apps-web-nextjs/postcss.config.mjs.hbs +9 -0
- package/templates/fullstack/apps-web-nextjs/tsconfig.json.hbs +21 -0
- package/templates/fullstack/root/.gitignore.hbs +42 -0
- package/templates/fullstack/root/githat.yaml.hbs +17 -0
- package/templates/fullstack/root/package.json.hbs +15 -0
- package/templates/fullstack/root/turbo.json.hbs +20 -0
- package/templates/nextjs/app/(auth)/reset-password/page.tsx.hbs +10 -2
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
|
|
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.
|
|
14
|
+
var VERSION = "1.0.5";
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
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/
|
|
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
|
|
330
|
+
const answers = await p3.group(
|
|
232
331
|
{
|
|
233
|
-
framework: () =>
|
|
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) :
|
|
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
|
-
|
|
349
|
+
p3.cancel("Setup cancelled.");
|
|
251
350
|
process.exit(0);
|
|
252
351
|
}
|
|
253
352
|
}
|
|
@@ -259,62 +358,74 @@ async function promptFramework(typescriptOverride) {
|
|
|
259
358
|
};
|
|
260
359
|
}
|
|
261
360
|
|
|
262
|
-
// src/prompts/
|
|
263
|
-
import
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
+
};
|
|
271
397
|
}
|
|
398
|
+
|
|
399
|
+
// src/prompts/githat.ts
|
|
400
|
+
import * as p5 from "@clack/prompts";
|
|
272
401
|
async function promptGitHat(existingKey) {
|
|
273
402
|
let publishableKey = existingKey || "";
|
|
274
403
|
if (!publishableKey) {
|
|
275
|
-
const connectChoice = await
|
|
404
|
+
const connectChoice = await p5.select({
|
|
276
405
|
message: "Connect to GitHat",
|
|
277
406
|
options: [
|
|
278
|
-
{ value: "
|
|
279
|
-
{ value: "paste", label: "I have a key", hint: "paste your pk_live_... key" }
|
|
280
|
-
{ value: "skip", label: "Skip for now", hint: "add key to .env later" }
|
|
407
|
+
{ value: "skip", label: "Skip for now", hint: "auth works on localhost \u2014 add key later" },
|
|
408
|
+
{ value: "paste", label: "I have a key", hint: "paste your pk_live_... key" }
|
|
281
409
|
]
|
|
282
410
|
});
|
|
283
|
-
if (
|
|
284
|
-
|
|
411
|
+
if (p5.isCancel(connectChoice)) {
|
|
412
|
+
p5.cancel("Setup cancelled.");
|
|
285
413
|
process.exit(0);
|
|
286
414
|
}
|
|
287
|
-
if (connectChoice === "
|
|
288
|
-
|
|
289
|
-
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({
|
|
292
|
-
message: "Paste your publishable key",
|
|
293
|
-
placeholder: "pk_live_...",
|
|
294
|
-
validate: validatePublishableKey
|
|
295
|
-
});
|
|
296
|
-
if (p3.isCancel(pastedKey)) {
|
|
297
|
-
p3.cancel("Setup cancelled.");
|
|
298
|
-
process.exit(0);
|
|
299
|
-
}
|
|
300
|
-
publishableKey = pastedKey || "";
|
|
301
|
-
} else if (connectChoice === "paste") {
|
|
302
|
-
const pastedKey = await p3.text({
|
|
415
|
+
if (connectChoice === "paste") {
|
|
416
|
+
const pastedKey = await p5.text({
|
|
303
417
|
message: "Publishable key",
|
|
304
418
|
placeholder: `pk_live_... (get one at ${DASHBOARD_URL})`,
|
|
305
419
|
validate: validatePublishableKey
|
|
306
420
|
});
|
|
307
|
-
if (
|
|
308
|
-
|
|
421
|
+
if (p5.isCancel(pastedKey)) {
|
|
422
|
+
p5.cancel("Setup cancelled.");
|
|
309
423
|
process.exit(0);
|
|
310
424
|
}
|
|
311
425
|
publishableKey = pastedKey || "";
|
|
312
|
-
} 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.");
|
|
315
426
|
}
|
|
316
427
|
}
|
|
317
|
-
const authFeatures = await
|
|
428
|
+
const authFeatures = await p5.multiselect({
|
|
318
429
|
message: "Auth features",
|
|
319
430
|
options: [
|
|
320
431
|
{ value: "forgot-password", label: "Forgot password", hint: "reset via email" },
|
|
@@ -326,8 +437,8 @@ async function promptGitHat(existingKey) {
|
|
|
326
437
|
initialValues: ["forgot-password"],
|
|
327
438
|
required: false
|
|
328
439
|
});
|
|
329
|
-
if (
|
|
330
|
-
|
|
440
|
+
if (p5.isCancel(authFeatures)) {
|
|
441
|
+
p5.cancel("Setup cancelled.");
|
|
331
442
|
process.exit(0);
|
|
332
443
|
}
|
|
333
444
|
return {
|
|
@@ -338,11 +449,11 @@ async function promptGitHat(existingKey) {
|
|
|
338
449
|
}
|
|
339
450
|
|
|
340
451
|
// src/prompts/features.ts
|
|
341
|
-
import * as
|
|
452
|
+
import * as p6 from "@clack/prompts";
|
|
342
453
|
async function promptFeatures() {
|
|
343
|
-
const answers = await
|
|
454
|
+
const answers = await p6.group(
|
|
344
455
|
{
|
|
345
|
-
databaseChoice: () =>
|
|
456
|
+
databaseChoice: () => p6.select({
|
|
346
457
|
message: "Database",
|
|
347
458
|
options: [
|
|
348
459
|
{ value: "none", label: "None", hint: "use GitHat backend directly" },
|
|
@@ -352,12 +463,12 @@ async function promptFeatures() {
|
|
|
352
463
|
{ value: "drizzle-sqlite", label: "Drizzle + SQLite" }
|
|
353
464
|
]
|
|
354
465
|
}),
|
|
355
|
-
useTailwind: () =>
|
|
356
|
-
includeDashboard: () =>
|
|
466
|
+
useTailwind: () => p6.confirm({ message: "Tailwind CSS?", initialValue: true }),
|
|
467
|
+
includeDashboard: () => p6.confirm({ message: "Include dashboard?", initialValue: true })
|
|
357
468
|
},
|
|
358
469
|
{
|
|
359
470
|
onCancel: () => {
|
|
360
|
-
|
|
471
|
+
p6.cancel("Setup cancelled.");
|
|
361
472
|
process.exit(0);
|
|
362
473
|
}
|
|
363
474
|
}
|
|
@@ -371,14 +482,14 @@ async function promptFeatures() {
|
|
|
371
482
|
}
|
|
372
483
|
|
|
373
484
|
// src/prompts/finalize.ts
|
|
374
|
-
import * as
|
|
485
|
+
import * as p7 from "@clack/prompts";
|
|
375
486
|
async function promptFinalize() {
|
|
376
|
-
const installDeps = await
|
|
487
|
+
const installDeps = await p7.confirm({
|
|
377
488
|
message: "Install dependencies?",
|
|
378
489
|
initialValue: true
|
|
379
490
|
});
|
|
380
|
-
if (
|
|
381
|
-
|
|
491
|
+
if (p7.isCancel(installDeps)) {
|
|
492
|
+
p7.cancel("Setup cancelled.");
|
|
382
493
|
process.exit(0);
|
|
383
494
|
}
|
|
384
495
|
return {
|
|
@@ -391,12 +502,15 @@ async function promptFinalize() {
|
|
|
391
502
|
function toDisplayName(projectName) {
|
|
392
503
|
return projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
393
504
|
}
|
|
394
|
-
function getDefaults(projectName, publishableKey, typescript) {
|
|
505
|
+
function getDefaults(projectName, publishableKey, typescript, fullstack, backendFramework) {
|
|
395
506
|
const displayName = toDisplayName(projectName);
|
|
507
|
+
const projectType = fullstack ? "fullstack" : "frontend";
|
|
396
508
|
return {
|
|
397
509
|
projectName,
|
|
398
510
|
businessName: displayName,
|
|
399
511
|
description: `${displayName} \u2014 Built with GitHat`,
|
|
512
|
+
projectType,
|
|
513
|
+
backendFramework: fullstack ? backendFramework || "hono" : void 0,
|
|
400
514
|
framework: "nextjs",
|
|
401
515
|
typescript: typescript ?? true,
|
|
402
516
|
packageManager: detectPackageManager(),
|
|
@@ -406,28 +520,33 @@ function getDefaults(projectName, publishableKey, typescript) {
|
|
|
406
520
|
databaseChoice: "none",
|
|
407
521
|
useTailwind: true,
|
|
408
522
|
includeDashboard: true,
|
|
409
|
-
includeGithatFolder:
|
|
523
|
+
includeGithatFolder: projectType === "frontend",
|
|
410
524
|
initGit: true,
|
|
411
525
|
installDeps: true
|
|
412
526
|
};
|
|
413
527
|
}
|
|
414
528
|
async function runPrompts(args) {
|
|
415
529
|
if (args.yes && args.initialName) {
|
|
416
|
-
|
|
417
|
-
return getDefaults(args.initialName, args.publishableKey, args.typescript);
|
|
530
|
+
p8.log.info("Using defaults (--yes flag)");
|
|
531
|
+
return getDefaults(args.initialName, args.publishableKey, args.typescript, args.fullstack, args.backendFramework);
|
|
418
532
|
}
|
|
419
|
-
|
|
533
|
+
p8.intro("Let's set up your GitHat app");
|
|
420
534
|
sectionHeader("Project");
|
|
421
535
|
const project = await promptProject(args.initialName);
|
|
536
|
+
const projectType = await promptProjectType();
|
|
422
537
|
sectionHeader("Stack");
|
|
423
538
|
const framework = await promptFramework(args.typescript);
|
|
539
|
+
let backend = {};
|
|
540
|
+
if (projectType.projectType === "fullstack") {
|
|
541
|
+
backend = await promptBackend();
|
|
542
|
+
}
|
|
424
543
|
sectionHeader("Connect");
|
|
425
544
|
const githat = await promptGitHat(args.publishableKey);
|
|
426
545
|
sectionHeader("Features");
|
|
427
546
|
const features = await promptFeatures();
|
|
428
547
|
sectionHeader("Finish");
|
|
429
548
|
const finalize = await promptFinalize();
|
|
430
|
-
return { ...project, ...framework, ...githat, ...features, ...finalize };
|
|
549
|
+
return { ...project, ...projectType, ...framework, ...backend, ...githat, ...features, ...finalize };
|
|
431
550
|
}
|
|
432
551
|
function answersToContext(answers) {
|
|
433
552
|
return {
|
|
@@ -439,6 +558,9 @@ function answersToContext(answers) {
|
|
|
439
558
|
packageManager: answers.packageManager,
|
|
440
559
|
publishableKey: answers.publishableKey,
|
|
441
560
|
apiUrl: answers.apiUrl,
|
|
561
|
+
// Project type
|
|
562
|
+
projectType: answers.projectType,
|
|
563
|
+
backendFramework: answers.backendFramework,
|
|
442
564
|
useDatabase: answers.databaseChoice !== "none",
|
|
443
565
|
databaseChoice: answers.databaseChoice,
|
|
444
566
|
useTailwind: answers.useTailwind,
|
|
@@ -457,8 +579,8 @@ function answersToContext(answers) {
|
|
|
457
579
|
// src/scaffold/index.ts
|
|
458
580
|
import fs3 from "fs-extra";
|
|
459
581
|
import path3 from "path";
|
|
460
|
-
import { execSync as
|
|
461
|
-
import * as
|
|
582
|
+
import { execSync as execSync2 } from "child_process";
|
|
583
|
+
import * as p9 from "@clack/prompts";
|
|
462
584
|
import chalk2 from "chalk";
|
|
463
585
|
|
|
464
586
|
// src/utils/template-engine.ts
|
|
@@ -592,12 +714,12 @@ async function withSpinner(text4, fn, successText) {
|
|
|
592
714
|
}
|
|
593
715
|
|
|
594
716
|
// src/utils/git.ts
|
|
595
|
-
import { execSync
|
|
717
|
+
import { execSync } from "child_process";
|
|
596
718
|
function initGit(cwd) {
|
|
597
719
|
try {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
720
|
+
execSync("git init", { cwd, stdio: "ignore" });
|
|
721
|
+
execSync("git add -A", { cwd, stdio: "ignore" });
|
|
722
|
+
execSync('git commit -m "Initial commit from create-githat-app"', {
|
|
601
723
|
cwd,
|
|
602
724
|
stdio: "ignore"
|
|
603
725
|
});
|
|
@@ -611,26 +733,33 @@ function initGit(cwd) {
|
|
|
611
733
|
async function scaffold(context, options) {
|
|
612
734
|
const root = path3.resolve(process.cwd(), context.projectName);
|
|
613
735
|
if (fs3.existsSync(root)) {
|
|
614
|
-
|
|
736
|
+
p9.cancel(`Directory "${context.projectName}" already exists.`);
|
|
615
737
|
process.exit(1);
|
|
616
738
|
}
|
|
739
|
+
const isFullstack = context.projectType === "fullstack";
|
|
617
740
|
await withSpinner("Creating project structure...", async () => {
|
|
618
741
|
fs3.ensureDirSync(root);
|
|
619
742
|
const templatesRoot = getTemplatesRoot();
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
renderTemplateDirectory(
|
|
743
|
+
if (isFullstack) {
|
|
744
|
+
scaffoldFullstack(templatesRoot, root, context);
|
|
745
|
+
} else {
|
|
746
|
+
const frameworkDir = path3.join(templatesRoot, context.framework);
|
|
747
|
+
if (!fs3.existsSync(frameworkDir)) {
|
|
748
|
+
throw new Error(`Templates not found at ${frameworkDir}. This is a bug \u2014 please report it.`);
|
|
749
|
+
}
|
|
750
|
+
renderTemplateDirectory(frameworkDir, root, context);
|
|
751
|
+
const baseDir = path3.join(templatesRoot, "base");
|
|
752
|
+
if (fs3.existsSync(baseDir)) {
|
|
753
|
+
renderTemplateDirectory(baseDir, root, context);
|
|
754
|
+
}
|
|
628
755
|
}
|
|
629
756
|
}, "Project structure created");
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
757
|
+
if (!isFullstack) {
|
|
758
|
+
await withSpinner("Generating package.json...", async () => {
|
|
759
|
+
const pkg = buildPackageJson(context);
|
|
760
|
+
writeJson(root, "package.json", pkg);
|
|
761
|
+
}, "package.json generated");
|
|
762
|
+
}
|
|
634
763
|
if (options.initGit) {
|
|
635
764
|
const gitSpinner = createSpinner("Initializing git repository...");
|
|
636
765
|
gitSpinner.start();
|
|
@@ -647,36 +776,65 @@ async function scaffold(context, options) {
|
|
|
647
776
|
`Installing dependencies with ${context.packageManager}...`,
|
|
648
777
|
async () => {
|
|
649
778
|
try {
|
|
650
|
-
|
|
779
|
+
execSync2(installCmd, { cwd: root, stdio: "ignore", timeout: 12e4 });
|
|
651
780
|
} catch (err) {
|
|
652
781
|
const msg = err.message || "";
|
|
653
782
|
if (msg.includes("TIMEOUT")) {
|
|
654
|
-
|
|
783
|
+
p9.log.warn(`Install timed out. Run ${chalk2.cyan(installCmd)} manually.`);
|
|
655
784
|
} else {
|
|
656
|
-
|
|
785
|
+
p9.log.warn(`Could not auto-install. Run ${chalk2.cyan(installCmd)} manually.`);
|
|
657
786
|
}
|
|
658
787
|
}
|
|
659
788
|
},
|
|
660
789
|
"Dependencies installed"
|
|
661
790
|
);
|
|
662
791
|
}
|
|
663
|
-
|
|
664
|
-
displaySuccess(context.projectName, context.packageManager, context.framework, !!context.publishableKey);
|
|
792
|
+
p9.outro("Setup complete!");
|
|
793
|
+
displaySuccess(context.projectName, context.packageManager, context.framework, !!context.publishableKey, isFullstack);
|
|
665
794
|
if (!options.skipPrompts) {
|
|
666
|
-
const starPrompt = await
|
|
795
|
+
const starPrompt = await p9.confirm({
|
|
667
796
|
message: "Star GitHat on GitHub? (helps us grow!)",
|
|
668
797
|
initialValue: false
|
|
669
798
|
});
|
|
670
|
-
if (!
|
|
799
|
+
if (!p9.isCancel(starPrompt) && starPrompt) {
|
|
671
800
|
try {
|
|
672
801
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
673
|
-
|
|
802
|
+
execSync2(`${cmd} "https://github.com/GitHat-IO/githat"`, { stdio: "ignore" });
|
|
674
803
|
} catch {
|
|
675
|
-
|
|
804
|
+
p9.log.info("Visit https://github.com/GitHat-IO/githat to star us!");
|
|
676
805
|
}
|
|
677
806
|
}
|
|
678
807
|
}
|
|
679
808
|
}
|
|
809
|
+
function scaffoldFullstack(templatesRoot, root, context) {
|
|
810
|
+
const fullstackDir = path3.join(templatesRoot, "fullstack");
|
|
811
|
+
const rootDir = path3.join(fullstackDir, "root");
|
|
812
|
+
if (fs3.existsSync(rootDir)) {
|
|
813
|
+
renderTemplateDirectory(rootDir, root, context);
|
|
814
|
+
}
|
|
815
|
+
const appsDir = path3.join(root, "apps");
|
|
816
|
+
fs3.ensureDirSync(appsDir);
|
|
817
|
+
const webDir = path3.join(appsDir, "web");
|
|
818
|
+
fs3.ensureDirSync(webDir);
|
|
819
|
+
const webTemplateDir = path3.join(fullstackDir, `apps-web-${context.framework}`);
|
|
820
|
+
if (fs3.existsSync(webTemplateDir)) {
|
|
821
|
+
renderTemplateDirectory(webTemplateDir, webDir, context);
|
|
822
|
+
} else {
|
|
823
|
+
throw new Error(`Web app templates not found at ${webTemplateDir}. This is a bug \u2014 please report it.`);
|
|
824
|
+
}
|
|
825
|
+
const apiDir = path3.join(appsDir, "api");
|
|
826
|
+
fs3.ensureDirSync(apiDir);
|
|
827
|
+
const backendFramework = context.backendFramework || "hono";
|
|
828
|
+
const apiTemplateDir = path3.join(fullstackDir, `apps-api-${backendFramework}`);
|
|
829
|
+
if (fs3.existsSync(apiTemplateDir)) {
|
|
830
|
+
renderTemplateDirectory(apiTemplateDir, apiDir, context);
|
|
831
|
+
} else {
|
|
832
|
+
throw new Error(`API templates not found at ${apiTemplateDir}. This is a bug \u2014 please report it.`);
|
|
833
|
+
}
|
|
834
|
+
const packagesDir = path3.join(root, "packages");
|
|
835
|
+
fs3.ensureDirSync(packagesDir);
|
|
836
|
+
fs3.writeFileSync(path3.join(packagesDir, ".gitkeep"), "");
|
|
837
|
+
}
|
|
680
838
|
|
|
681
839
|
// src/commands/skills/index.ts
|
|
682
840
|
import { Command as Command6 } from "commander";
|
|
@@ -996,7 +1154,7 @@ import { Command as Command5 } from "commander";
|
|
|
996
1154
|
import chalk7 from "chalk";
|
|
997
1155
|
import * as fs6 from "fs";
|
|
998
1156
|
import * as path6 from "path";
|
|
999
|
-
import * as
|
|
1157
|
+
import * as p10 from "@clack/prompts";
|
|
1000
1158
|
var SKILL_TYPES = ["template", "integration", "ui", "ai", "workflow"];
|
|
1001
1159
|
function generateReadme(manifest) {
|
|
1002
1160
|
return `# ${manifest.name}
|
|
@@ -1144,7 +1302,7 @@ var initCommand = new Command5("init").description("Initialize a new skill packa
|
|
|
1144
1302
|
if (options.type && SKILL_TYPES.includes(options.type)) {
|
|
1145
1303
|
type = options.type;
|
|
1146
1304
|
} else {
|
|
1147
|
-
const result = await
|
|
1305
|
+
const result = await p10.select({
|
|
1148
1306
|
message: "What type of skill are you creating?",
|
|
1149
1307
|
options: [
|
|
1150
1308
|
{ value: "integration", label: "Integration", hint: "Connect to external services (Stripe, SendGrid, etc.)" },
|
|
@@ -1154,19 +1312,19 @@ var initCommand = new Command5("init").description("Initialize a new skill packa
|
|
|
1154
1312
|
{ value: "workflow", label: "Workflow", hint: "Automation recipes" }
|
|
1155
1313
|
]
|
|
1156
1314
|
});
|
|
1157
|
-
if (
|
|
1158
|
-
|
|
1315
|
+
if (p10.isCancel(result)) {
|
|
1316
|
+
p10.cancel("Operation cancelled");
|
|
1159
1317
|
process.exit(0);
|
|
1160
1318
|
}
|
|
1161
1319
|
type = result;
|
|
1162
1320
|
}
|
|
1163
|
-
const description = await
|
|
1321
|
+
const description = await p10.text({
|
|
1164
1322
|
message: "Short description:",
|
|
1165
1323
|
placeholder: `A ${type} skill for...`,
|
|
1166
1324
|
validate: (v) => v.length < 10 ? "Description must be at least 10 characters" : void 0
|
|
1167
1325
|
});
|
|
1168
|
-
if (
|
|
1169
|
-
|
|
1326
|
+
if (p10.isCancel(description)) {
|
|
1327
|
+
p10.cancel("Operation cancelled");
|
|
1170
1328
|
process.exit(0);
|
|
1171
1329
|
}
|
|
1172
1330
|
const manifest = {
|
|
@@ -1254,19 +1412,21 @@ skillsCommand.action(() => {
|
|
|
1254
1412
|
// src/cli.ts
|
|
1255
1413
|
var program = new Command7();
|
|
1256
1414
|
program.name("githat").description("GitHat CLI - Scaffold apps and manage skills").version(VERSION);
|
|
1257
|
-
program.command("create [project-name]", { isDefault: true }).description("Scaffold a new GitHat app").option("--key <key>", "GitHat publishable key (pk_live_...)").option("--ts", "Use TypeScript (default)").option("--js", "Use JavaScript").option("-y, --yes", "Skip prompts and use defaults").action(async (projectName, opts) => {
|
|
1415
|
+
program.command("create [project-name]", { isDefault: true }).description("Scaffold a new GitHat app").option("--key <key>", "GitHat publishable key (pk_live_...)").option("--ts", "Use TypeScript (default)").option("--js", "Use JavaScript").option("--fullstack", "Create fullstack project (Turborepo)").option("--backend <framework>", "Backend framework (hono, express, fastify)").option("-y, --yes", "Skip prompts and use defaults").action(async (projectName, opts) => {
|
|
1258
1416
|
try {
|
|
1259
1417
|
displayBanner();
|
|
1260
1418
|
const typescript = opts.js ? false : opts.ts ? true : void 0;
|
|
1261
1419
|
if (opts.yes && !projectName) {
|
|
1262
|
-
|
|
1420
|
+
p11.cancel(chalk9.red("Project name is required when using --yes flag"));
|
|
1263
1421
|
process.exit(1);
|
|
1264
1422
|
}
|
|
1265
1423
|
const answers = await runPrompts({
|
|
1266
1424
|
initialName: projectName,
|
|
1267
1425
|
publishableKey: opts.key,
|
|
1268
1426
|
typescript,
|
|
1269
|
-
yes: opts.yes
|
|
1427
|
+
yes: opts.yes,
|
|
1428
|
+
fullstack: opts.fullstack,
|
|
1429
|
+
backendFramework: opts.backend
|
|
1270
1430
|
});
|
|
1271
1431
|
const context = answersToContext(answers);
|
|
1272
1432
|
await scaffold(context, {
|
|
@@ -1275,7 +1435,7 @@ program.command("create [project-name]", { isDefault: true }).description("Scaff
|
|
|
1275
1435
|
skipPrompts: opts.yes
|
|
1276
1436
|
});
|
|
1277
1437
|
} catch (err) {
|
|
1278
|
-
|
|
1438
|
+
p11.cancel(chalk9.red(err.message || "Something went wrong."));
|
|
1279
1439
|
process.exit(1);
|
|
1280
1440
|
}
|
|
1281
1441
|
});
|