create-githat-app 1.0.2 → 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.
- package/dist/cli.js +316 -99
- 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 +11 -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 +9 -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/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 = "0.
|
|
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"];
|
|
@@ -21,7 +21,7 @@ var DEPS = {
|
|
|
21
21
|
next: "^16.0.0",
|
|
22
22
|
react: "^19.0.0",
|
|
23
23
|
"react-dom": "^19.0.0",
|
|
24
|
-
"@githat/nextjs": "^0.
|
|
24
|
+
"@githat/nextjs": "^0.2.2"
|
|
25
25
|
},
|
|
26
26
|
devDependencies: {
|
|
27
27
|
typescript: "^5.9.0",
|
|
@@ -35,7 +35,7 @@ var DEPS = {
|
|
|
35
35
|
react: "^19.0.0",
|
|
36
36
|
"react-dom": "^19.0.0",
|
|
37
37
|
"react-router-dom": "^7.0.0",
|
|
38
|
-
"@githat/nextjs": "^0.
|
|
38
|
+
"@githat/nextjs": "^0.2.2"
|
|
39
39
|
},
|
|
40
40
|
devDependencies: {
|
|
41
41
|
vite: "^7.0.0",
|
|
@@ -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,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
|
|
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
|
|
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 (
|
|
284
|
-
|
|
420
|
+
if (p5.isCancel(connectChoice)) {
|
|
421
|
+
p5.cancel("Setup cancelled.");
|
|
285
422
|
process.exit(0);
|
|
286
423
|
}
|
|
287
424
|
if (connectChoice === "browser") {
|
|
288
|
-
|
|
425
|
+
p5.log.step("Opening githat.io in your browser...");
|
|
289
426
|
openBrowser("https://githat.io/sign-up");
|
|
290
|
-
|
|
291
|
-
const pastedKey = await
|
|
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 (
|
|
297
|
-
|
|
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
|
|
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 (
|
|
308
|
-
|
|
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
|
-
|
|
314
|
-
|
|
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
|
|
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 (
|
|
330
|
-
|
|
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
|
|
478
|
+
import * as p6 from "@clack/prompts";
|
|
342
479
|
async function promptFeatures() {
|
|
343
|
-
const answers = await
|
|
480
|
+
const answers = await p6.group(
|
|
344
481
|
{
|
|
345
|
-
databaseChoice: () =>
|
|
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: () =>
|
|
356
|
-
includeDashboard: () =>
|
|
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
|
-
|
|
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
|
|
511
|
+
import * as p7 from "@clack/prompts";
|
|
375
512
|
async function promptFinalize() {
|
|
376
|
-
const installDeps = await
|
|
513
|
+
const installDeps = await p7.confirm({
|
|
377
514
|
message: "Install dependencies?",
|
|
378
515
|
initialValue: true
|
|
379
516
|
});
|
|
380
|
-
if (
|
|
381
|
-
|
|
517
|
+
if (p7.isCancel(installDeps)) {
|
|
518
|
+
p7.cancel("Setup cancelled.");
|
|
382
519
|
process.exit(0);
|
|
383
520
|
}
|
|
384
521
|
return {
|
|
@@ -388,19 +525,52 @@ async function promptFinalize() {
|
|
|
388
525
|
}
|
|
389
526
|
|
|
390
527
|
// src/prompts/index.ts
|
|
528
|
+
function toDisplayName(projectName) {
|
|
529
|
+
return projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
530
|
+
}
|
|
531
|
+
function getDefaults(projectName, publishableKey, typescript) {
|
|
532
|
+
const displayName = toDisplayName(projectName);
|
|
533
|
+
return {
|
|
534
|
+
projectName,
|
|
535
|
+
businessName: displayName,
|
|
536
|
+
description: `${displayName} \u2014 Built with GitHat`,
|
|
537
|
+
projectType: "frontend",
|
|
538
|
+
framework: "nextjs",
|
|
539
|
+
typescript: typescript ?? true,
|
|
540
|
+
packageManager: detectPackageManager(),
|
|
541
|
+
publishableKey: publishableKey || "",
|
|
542
|
+
apiUrl: DEFAULT_API_URL,
|
|
543
|
+
authFeatures: ["forgot-password"],
|
|
544
|
+
databaseChoice: "none",
|
|
545
|
+
useTailwind: true,
|
|
546
|
+
includeDashboard: true,
|
|
547
|
+
includeGithatFolder: true,
|
|
548
|
+
initGit: true,
|
|
549
|
+
installDeps: true
|
|
550
|
+
};
|
|
551
|
+
}
|
|
391
552
|
async function runPrompts(args) {
|
|
392
|
-
|
|
553
|
+
if (args.yes && args.initialName) {
|
|
554
|
+
p8.log.info("Using defaults (--yes flag)");
|
|
555
|
+
return getDefaults(args.initialName, args.publishableKey, args.typescript);
|
|
556
|
+
}
|
|
557
|
+
p8.intro("Let's set up your GitHat app");
|
|
393
558
|
sectionHeader("Project");
|
|
394
559
|
const project = await promptProject(args.initialName);
|
|
560
|
+
const projectType = await promptProjectType();
|
|
395
561
|
sectionHeader("Stack");
|
|
396
562
|
const framework = await promptFramework(args.typescript);
|
|
563
|
+
let backend = {};
|
|
564
|
+
if (projectType.projectType === "fullstack") {
|
|
565
|
+
backend = await promptBackend();
|
|
566
|
+
}
|
|
397
567
|
sectionHeader("Connect");
|
|
398
568
|
const githat = await promptGitHat(args.publishableKey);
|
|
399
569
|
sectionHeader("Features");
|
|
400
570
|
const features = await promptFeatures();
|
|
401
571
|
sectionHeader("Finish");
|
|
402
572
|
const finalize = await promptFinalize();
|
|
403
|
-
return { ...project, ...framework, ...githat, ...features, ...finalize };
|
|
573
|
+
return { ...project, ...projectType, ...framework, ...backend, ...githat, ...features, ...finalize };
|
|
404
574
|
}
|
|
405
575
|
function answersToContext(answers) {
|
|
406
576
|
return {
|
|
@@ -412,6 +582,9 @@ function answersToContext(answers) {
|
|
|
412
582
|
packageManager: answers.packageManager,
|
|
413
583
|
publishableKey: answers.publishableKey,
|
|
414
584
|
apiUrl: answers.apiUrl,
|
|
585
|
+
// Project type
|
|
586
|
+
projectType: answers.projectType,
|
|
587
|
+
backendFramework: answers.backendFramework,
|
|
415
588
|
useDatabase: answers.databaseChoice !== "none",
|
|
416
589
|
databaseChoice: answers.databaseChoice,
|
|
417
590
|
useTailwind: answers.useTailwind,
|
|
@@ -431,7 +604,7 @@ function answersToContext(answers) {
|
|
|
431
604
|
import fs3 from "fs-extra";
|
|
432
605
|
import path3 from "path";
|
|
433
606
|
import { execSync as execSync3 } from "child_process";
|
|
434
|
-
import * as
|
|
607
|
+
import * as p9 from "@clack/prompts";
|
|
435
608
|
import chalk2 from "chalk";
|
|
436
609
|
|
|
437
610
|
// src/utils/template-engine.ts
|
|
@@ -584,26 +757,33 @@ function initGit(cwd) {
|
|
|
584
757
|
async function scaffold(context, options) {
|
|
585
758
|
const root = path3.resolve(process.cwd(), context.projectName);
|
|
586
759
|
if (fs3.existsSync(root)) {
|
|
587
|
-
|
|
760
|
+
p9.cancel(`Directory "${context.projectName}" already exists.`);
|
|
588
761
|
process.exit(1);
|
|
589
762
|
}
|
|
763
|
+
const isFullstack = context.projectType === "fullstack";
|
|
590
764
|
await withSpinner("Creating project structure...", async () => {
|
|
591
765
|
fs3.ensureDirSync(root);
|
|
592
766
|
const templatesRoot = getTemplatesRoot();
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
renderTemplateDirectory(
|
|
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
|
+
}
|
|
601
779
|
}
|
|
602
780
|
}, "Project structure created");
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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
|
+
}
|
|
607
787
|
if (options.initGit) {
|
|
608
788
|
const gitSpinner = createSpinner("Initializing git repository...");
|
|
609
789
|
gitSpinner.start();
|
|
@@ -624,30 +804,61 @@ async function scaffold(context, options) {
|
|
|
624
804
|
} catch (err) {
|
|
625
805
|
const msg = err.message || "";
|
|
626
806
|
if (msg.includes("TIMEOUT")) {
|
|
627
|
-
|
|
807
|
+
p9.log.warn(`Install timed out. Run ${chalk2.cyan(installCmd)} manually.`);
|
|
628
808
|
} else {
|
|
629
|
-
|
|
809
|
+
p9.log.warn(`Could not auto-install. Run ${chalk2.cyan(installCmd)} manually.`);
|
|
630
810
|
}
|
|
631
811
|
}
|
|
632
812
|
},
|
|
633
813
|
"Dependencies installed"
|
|
634
814
|
);
|
|
635
815
|
}
|
|
636
|
-
|
|
637
|
-
displaySuccess(context.projectName, context.packageManager, context.framework, !!context.publishableKey);
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
816
|
+
p9.outro("Setup complete!");
|
|
817
|
+
displaySuccess(context.projectName, context.packageManager, context.framework, !!context.publishableKey, isFullstack);
|
|
818
|
+
if (!options.skipPrompts) {
|
|
819
|
+
const starPrompt = await p9.confirm({
|
|
820
|
+
message: "Star GitHat on GitHub? (helps us grow!)",
|
|
821
|
+
initialValue: false
|
|
822
|
+
});
|
|
823
|
+
if (!p9.isCancel(starPrompt) && starPrompt) {
|
|
824
|
+
try {
|
|
825
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
826
|
+
execSync3(`${cmd} "https://github.com/GitHat-IO/githat"`, { stdio: "ignore" });
|
|
827
|
+
} catch {
|
|
828
|
+
p9.log.info("Visit https://github.com/GitHat-IO/githat to star us!");
|
|
829
|
+
}
|
|
648
830
|
}
|
|
649
831
|
}
|
|
650
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
|
+
}
|
|
651
862
|
|
|
652
863
|
// src/commands/skills/index.ts
|
|
653
864
|
import { Command as Command6 } from "commander";
|
|
@@ -967,7 +1178,7 @@ import { Command as Command5 } from "commander";
|
|
|
967
1178
|
import chalk7 from "chalk";
|
|
968
1179
|
import * as fs6 from "fs";
|
|
969
1180
|
import * as path6 from "path";
|
|
970
|
-
import * as
|
|
1181
|
+
import * as p10 from "@clack/prompts";
|
|
971
1182
|
var SKILL_TYPES = ["template", "integration", "ui", "ai", "workflow"];
|
|
972
1183
|
function generateReadme(manifest) {
|
|
973
1184
|
return `# ${manifest.name}
|
|
@@ -1115,7 +1326,7 @@ var initCommand = new Command5("init").description("Initialize a new skill packa
|
|
|
1115
1326
|
if (options.type && SKILL_TYPES.includes(options.type)) {
|
|
1116
1327
|
type = options.type;
|
|
1117
1328
|
} else {
|
|
1118
|
-
const result = await
|
|
1329
|
+
const result = await p10.select({
|
|
1119
1330
|
message: "What type of skill are you creating?",
|
|
1120
1331
|
options: [
|
|
1121
1332
|
{ value: "integration", label: "Integration", hint: "Connect to external services (Stripe, SendGrid, etc.)" },
|
|
@@ -1125,19 +1336,19 @@ var initCommand = new Command5("init").description("Initialize a new skill packa
|
|
|
1125
1336
|
{ value: "workflow", label: "Workflow", hint: "Automation recipes" }
|
|
1126
1337
|
]
|
|
1127
1338
|
});
|
|
1128
|
-
if (
|
|
1129
|
-
|
|
1339
|
+
if (p10.isCancel(result)) {
|
|
1340
|
+
p10.cancel("Operation cancelled");
|
|
1130
1341
|
process.exit(0);
|
|
1131
1342
|
}
|
|
1132
1343
|
type = result;
|
|
1133
1344
|
}
|
|
1134
|
-
const description = await
|
|
1345
|
+
const description = await p10.text({
|
|
1135
1346
|
message: "Short description:",
|
|
1136
1347
|
placeholder: `A ${type} skill for...`,
|
|
1137
1348
|
validate: (v) => v.length < 10 ? "Description must be at least 10 characters" : void 0
|
|
1138
1349
|
});
|
|
1139
|
-
if (
|
|
1140
|
-
|
|
1350
|
+
if (p10.isCancel(description)) {
|
|
1351
|
+
p10.cancel("Operation cancelled");
|
|
1141
1352
|
process.exit(0);
|
|
1142
1353
|
}
|
|
1143
1354
|
const manifest = {
|
|
@@ -1225,22 +1436,28 @@ skillsCommand.action(() => {
|
|
|
1225
1436
|
// src/cli.ts
|
|
1226
1437
|
var program = new Command7();
|
|
1227
1438
|
program.name("githat").description("GitHat CLI - Scaffold apps and manage skills").version(VERSION);
|
|
1228
|
-
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").action(async (projectName, opts) => {
|
|
1439
|
+
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) => {
|
|
1229
1440
|
try {
|
|
1230
1441
|
displayBanner();
|
|
1231
1442
|
const typescript = opts.js ? false : opts.ts ? true : void 0;
|
|
1443
|
+
if (opts.yes && !projectName) {
|
|
1444
|
+
p11.cancel(chalk9.red("Project name is required when using --yes flag"));
|
|
1445
|
+
process.exit(1);
|
|
1446
|
+
}
|
|
1232
1447
|
const answers = await runPrompts({
|
|
1233
1448
|
initialName: projectName,
|
|
1234
1449
|
publishableKey: opts.key,
|
|
1235
|
-
typescript
|
|
1450
|
+
typescript,
|
|
1451
|
+
yes: opts.yes
|
|
1236
1452
|
});
|
|
1237
1453
|
const context = answersToContext(answers);
|
|
1238
1454
|
await scaffold(context, {
|
|
1239
1455
|
installDeps: answers.installDeps,
|
|
1240
|
-
initGit: answers.initGit
|
|
1456
|
+
initGit: answers.initGit,
|
|
1457
|
+
skipPrompts: opts.yes
|
|
1241
1458
|
});
|
|
1242
1459
|
} catch (err) {
|
|
1243
|
-
|
|
1460
|
+
p11.cancel(chalk9.red(err.message || "Something went wrong."));
|
|
1244
1461
|
process.exit(1);
|
|
1245
1462
|
}
|
|
1246
1463
|
});
|