create-scaffauth 0.1.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.
- package/LICENSE +21 -0
- package/README.md +82 -0
- package/dist/index.js +1050 -0
- package/dist/index.js.map +1 -0
- package/dist/templates/express/drizzle-mysql/.env.example.hbs +39 -0
- package/dist/templates/express/drizzle-mysql/README.md.hbs +78 -0
- package/dist/templates/express/drizzle-mysql/drizzle/db.ts +8 -0
- package/dist/templates/express/drizzle-mysql/drizzle/migrate.ts +22 -0
- package/dist/templates/express/drizzle-mysql/drizzle/schema.ts +58 -0
- package/dist/templates/express/drizzle-mysql/drizzle.config.ts +10 -0
- package/dist/templates/express/drizzle-mysql/package.json.hbs +36 -0
- package/dist/templates/express/drizzle-mysql/src/auth.ts.hbs +114 -0
- package/dist/templates/express/drizzle-mysql/src/index.ts +28 -0
- package/dist/templates/express/drizzle-mysql/tsconfig.json +19 -0
- package/dist/templates/express/drizzle-postgres/.env.example.hbs +39 -0
- package/dist/templates/express/drizzle-postgres/README.md.hbs +78 -0
- package/dist/templates/express/drizzle-postgres/drizzle/db.ts +8 -0
- package/dist/templates/express/drizzle-postgres/drizzle/migrate.ts +22 -0
- package/dist/templates/express/drizzle-postgres/drizzle/schema.ts +57 -0
- package/dist/templates/express/drizzle-postgres/drizzle.config.ts +10 -0
- package/dist/templates/express/drizzle-postgres/package.json.hbs +37 -0
- package/dist/templates/express/drizzle-postgres/src/auth.ts.hbs +114 -0
- package/dist/templates/express/drizzle-postgres/src/index.ts +28 -0
- package/dist/templates/express/drizzle-postgres/tsconfig.json +19 -0
- package/dist/templates/express/drizzle-sqlite/.env.example.hbs +39 -0
- package/dist/templates/express/drizzle-sqlite/README.md.hbs +78 -0
- package/dist/templates/express/drizzle-sqlite/drizzle/db.ts +6 -0
- package/dist/templates/express/drizzle-sqlite/drizzle/migrate.ts +12 -0
- package/dist/templates/express/drizzle-sqlite/drizzle/schema.ts +55 -0
- package/dist/templates/express/drizzle-sqlite/drizzle.config.ts +10 -0
- package/dist/templates/express/drizzle-sqlite/package.json.hbs +37 -0
- package/dist/templates/express/drizzle-sqlite/src/auth.ts.hbs +114 -0
- package/dist/templates/express/drizzle-sqlite/src/index.ts +28 -0
- package/dist/templates/express/drizzle-sqlite/tsconfig.json +19 -0
- package/dist/templates/express/kysely-mysql/.env.example.hbs +39 -0
- package/dist/templates/express/kysely-mysql/README.md.hbs +75 -0
- package/dist/templates/express/kysely-mysql/db/db.ts +11 -0
- package/dist/templates/express/kysely-mysql/db/migrate.ts +77 -0
- package/dist/templates/express/kysely-mysql/db/types.ts +54 -0
- package/dist/templates/express/kysely-mysql/package.json.hbs +32 -0
- package/dist/templates/express/kysely-mysql/src/auth.ts.hbs +114 -0
- package/dist/templates/express/kysely-mysql/src/index.ts +28 -0
- package/dist/templates/express/kysely-mysql/tsconfig.json +19 -0
- package/dist/templates/express/kysely-postgres/.env.example.hbs +39 -0
- package/dist/templates/express/kysely-postgres/README.md.hbs +75 -0
- package/dist/templates/express/kysely-postgres/db/db.ts +11 -0
- package/dist/templates/express/kysely-postgres/db/migrate.ts +77 -0
- package/dist/templates/express/kysely-postgres/db/types.ts +54 -0
- package/dist/templates/express/kysely-postgres/package.json.hbs +33 -0
- package/dist/templates/express/kysely-postgres/src/auth.ts.hbs +114 -0
- package/dist/templates/express/kysely-postgres/src/index.ts +28 -0
- package/dist/templates/express/kysely-postgres/tsconfig.json +19 -0
- package/dist/templates/express/kysely-sqlite/.env.example.hbs +39 -0
- package/dist/templates/express/kysely-sqlite/README.md.hbs +75 -0
- package/dist/templates/express/kysely-sqlite/db/db.ts +9 -0
- package/dist/templates/express/kysely-sqlite/db/migrate.ts +75 -0
- package/dist/templates/express/kysely-sqlite/db/types.ts +54 -0
- package/dist/templates/express/kysely-sqlite/package.json.hbs +33 -0
- package/dist/templates/express/kysely-sqlite/src/auth.ts.hbs +114 -0
- package/dist/templates/express/kysely-sqlite/src/index.ts +28 -0
- package/dist/templates/express/kysely-sqlite/tsconfig.json +19 -0
- package/dist/templates/express/prisma-mysql/.env.example.hbs +39 -0
- package/dist/templates/express/prisma-mysql/README.md.hbs +79 -0
- package/dist/templates/express/prisma-mysql/package.json.hbs +35 -0
- package/dist/templates/express/prisma-mysql/prisma/schema.prisma +66 -0
- package/dist/templates/express/prisma-mysql/src/auth.ts.hbs +114 -0
- package/dist/templates/express/prisma-mysql/src/db.ts +3 -0
- package/dist/templates/express/prisma-mysql/src/index.ts +28 -0
- package/dist/templates/express/prisma-mysql/tsconfig.json +19 -0
- package/dist/templates/express/prisma-postgres/.env.example.hbs +39 -0
- package/dist/templates/express/prisma-postgres/README.md.hbs +79 -0
- package/dist/templates/express/prisma-postgres/package.json.hbs +35 -0
- package/dist/templates/express/prisma-postgres/prisma/schema.prisma +66 -0
- package/dist/templates/express/prisma-postgres/src/auth.ts.hbs +114 -0
- package/dist/templates/express/prisma-postgres/src/db.ts +3 -0
- package/dist/templates/express/prisma-postgres/src/index.ts +28 -0
- package/dist/templates/express/prisma-postgres/tsconfig.json +19 -0
- package/dist/templates/express/prisma-sqlite/.env.example.hbs +39 -0
- package/dist/templates/express/prisma-sqlite/README.md.hbs +79 -0
- package/dist/templates/express/prisma-sqlite/package.json.hbs +35 -0
- package/dist/templates/express/prisma-sqlite/prisma/schema.prisma +66 -0
- package/dist/templates/express/prisma-sqlite/src/auth.ts.hbs +114 -0
- package/dist/templates/express/prisma-sqlite/src/db.ts +3 -0
- package/dist/templates/express/prisma-sqlite/src/index.ts +28 -0
- package/dist/templates/express/prisma-sqlite/tsconfig.json +19 -0
- package/dist/templates/fastify/drizzle-mysql/.env.example.hbs +39 -0
- package/dist/templates/fastify/drizzle-mysql/README.md.hbs +78 -0
- package/dist/templates/fastify/drizzle-mysql/drizzle/db.ts +8 -0
- package/dist/templates/fastify/drizzle-mysql/drizzle/migrate.ts +22 -0
- package/dist/templates/fastify/drizzle-mysql/drizzle/schema.ts +58 -0
- package/dist/templates/fastify/drizzle-mysql/drizzle.config.ts +10 -0
- package/dist/templates/fastify/drizzle-mysql/package.json.hbs +34 -0
- package/dist/templates/fastify/drizzle-mysql/src/auth.ts.hbs +114 -0
- package/dist/templates/fastify/drizzle-mysql/src/index.ts +34 -0
- package/dist/templates/fastify/drizzle-mysql/tsconfig.json +19 -0
- package/dist/templates/fastify/drizzle-postgres/.env.example.hbs +39 -0
- package/dist/templates/fastify/drizzle-postgres/README.md.hbs +78 -0
- package/dist/templates/fastify/drizzle-postgres/drizzle/db.ts +8 -0
- package/dist/templates/fastify/drizzle-postgres/drizzle/migrate.ts +22 -0
- package/dist/templates/fastify/drizzle-postgres/drizzle/schema.ts +57 -0
- package/dist/templates/fastify/drizzle-postgres/drizzle.config.ts +10 -0
- package/dist/templates/fastify/drizzle-postgres/package.json.hbs +35 -0
- package/dist/templates/fastify/drizzle-postgres/src/auth.ts.hbs +114 -0
- package/dist/templates/fastify/drizzle-postgres/src/index.ts +34 -0
- package/dist/templates/fastify/drizzle-postgres/tsconfig.json +19 -0
- package/dist/templates/fastify/drizzle-sqlite/.env.example.hbs +39 -0
- package/dist/templates/fastify/drizzle-sqlite/README.md.hbs +78 -0
- package/dist/templates/fastify/drizzle-sqlite/drizzle/db.ts +6 -0
- package/dist/templates/fastify/drizzle-sqlite/drizzle/migrate.ts +12 -0
- package/dist/templates/fastify/drizzle-sqlite/drizzle/schema.ts +55 -0
- package/dist/templates/fastify/drizzle-sqlite/drizzle.config.ts +10 -0
- package/dist/templates/fastify/drizzle-sqlite/package.json.hbs +35 -0
- package/dist/templates/fastify/drizzle-sqlite/src/auth.ts.hbs +114 -0
- package/dist/templates/fastify/drizzle-sqlite/src/index.ts +34 -0
- package/dist/templates/fastify/drizzle-sqlite/tsconfig.json +19 -0
- package/dist/templates/fastify/kysely-mysql/.env.example.hbs +39 -0
- package/dist/templates/fastify/kysely-mysql/README.md.hbs +75 -0
- package/dist/templates/fastify/kysely-mysql/db/db.ts +11 -0
- package/dist/templates/fastify/kysely-mysql/db/migrate.ts +77 -0
- package/dist/templates/fastify/kysely-mysql/db/types.ts +54 -0
- package/dist/templates/fastify/kysely-mysql/package.json.hbs +30 -0
- package/dist/templates/fastify/kysely-mysql/src/auth.ts.hbs +114 -0
- package/dist/templates/fastify/kysely-mysql/src/index.ts +34 -0
- package/dist/templates/fastify/kysely-mysql/tsconfig.json +19 -0
- package/dist/templates/fastify/kysely-postgres/.env.example.hbs +39 -0
- package/dist/templates/fastify/kysely-postgres/README.md.hbs +75 -0
- package/dist/templates/fastify/kysely-postgres/db/db.ts +11 -0
- package/dist/templates/fastify/kysely-postgres/db/migrate.ts +77 -0
- package/dist/templates/fastify/kysely-postgres/db/types.ts +54 -0
- package/dist/templates/fastify/kysely-postgres/package.json.hbs +31 -0
- package/dist/templates/fastify/kysely-postgres/src/auth.ts.hbs +114 -0
- package/dist/templates/fastify/kysely-postgres/src/index.ts +34 -0
- package/dist/templates/fastify/kysely-postgres/tsconfig.json +19 -0
- package/dist/templates/fastify/kysely-sqlite/.env.example.hbs +39 -0
- package/dist/templates/fastify/kysely-sqlite/README.md.hbs +75 -0
- package/dist/templates/fastify/kysely-sqlite/db/db.ts +9 -0
- package/dist/templates/fastify/kysely-sqlite/db/migrate.ts +75 -0
- package/dist/templates/fastify/kysely-sqlite/db/types.ts +54 -0
- package/dist/templates/fastify/kysely-sqlite/package.json.hbs +31 -0
- package/dist/templates/fastify/kysely-sqlite/src/auth.ts.hbs +114 -0
- package/dist/templates/fastify/kysely-sqlite/src/index.ts +34 -0
- package/dist/templates/fastify/kysely-sqlite/tsconfig.json +19 -0
- package/dist/templates/fastify/prisma-mysql/.env.example.hbs +39 -0
- package/dist/templates/fastify/prisma-mysql/README.md.hbs +79 -0
- package/dist/templates/fastify/prisma-mysql/package.json.hbs +33 -0
- package/dist/templates/fastify/prisma-mysql/prisma/schema.prisma +66 -0
- package/dist/templates/fastify/prisma-mysql/src/auth.ts.hbs +114 -0
- package/dist/templates/fastify/prisma-mysql/src/db.ts +3 -0
- package/dist/templates/fastify/prisma-mysql/src/index.ts +34 -0
- package/dist/templates/fastify/prisma-mysql/tsconfig.json +19 -0
- package/dist/templates/fastify/prisma-postgres/.env.example.hbs +39 -0
- package/dist/templates/fastify/prisma-postgres/README.md.hbs +79 -0
- package/dist/templates/fastify/prisma-postgres/package.json.hbs +33 -0
- package/dist/templates/fastify/prisma-postgres/prisma/schema.prisma +66 -0
- package/dist/templates/fastify/prisma-postgres/src/auth.ts.hbs +114 -0
- package/dist/templates/fastify/prisma-postgres/src/db.ts +3 -0
- package/dist/templates/fastify/prisma-postgres/src/index.ts +34 -0
- package/dist/templates/fastify/prisma-postgres/tsconfig.json +19 -0
- package/dist/templates/fastify/prisma-sqlite/.env.example.hbs +39 -0
- package/dist/templates/fastify/prisma-sqlite/README.md.hbs +79 -0
- package/dist/templates/fastify/prisma-sqlite/package.json.hbs +33 -0
- package/dist/templates/fastify/prisma-sqlite/prisma/schema.prisma +66 -0
- package/dist/templates/fastify/prisma-sqlite/src/auth.ts.hbs +114 -0
- package/dist/templates/fastify/prisma-sqlite/src/db.ts +3 -0
- package/dist/templates/fastify/prisma-sqlite/src/index.ts +34 -0
- package/dist/templates/fastify/prisma-sqlite/tsconfig.json +19 -0
- package/dist/templates/hono/drizzle-mysql/.env.example.hbs +39 -0
- package/dist/templates/hono/drizzle-mysql/README.md.hbs +85 -0
- package/dist/templates/hono/drizzle-mysql/drizzle/db.ts +8 -0
- package/dist/templates/hono/drizzle-mysql/drizzle/migrate.ts +22 -0
- package/dist/templates/hono/drizzle-mysql/drizzle/schema.ts +58 -0
- package/dist/templates/hono/drizzle-mysql/drizzle.config.ts +10 -0
- package/dist/templates/hono/drizzle-mysql/package.json.hbs +33 -0
- package/dist/templates/hono/drizzle-mysql/src/auth.ts.hbs +114 -0
- package/dist/templates/hono/drizzle-mysql/src/index.ts +35 -0
- package/dist/templates/hono/drizzle-mysql/tsconfig.json +19 -0
- package/dist/templates/hono/drizzle-postgres/.env.example.hbs +39 -0
- package/dist/templates/hono/drizzle-postgres/README.md.hbs +85 -0
- package/dist/templates/hono/drizzle-postgres/drizzle/db.ts +8 -0
- package/dist/templates/hono/drizzle-postgres/drizzle/migrate.ts +22 -0
- package/dist/templates/hono/drizzle-postgres/drizzle/schema.ts +57 -0
- package/dist/templates/hono/drizzle-postgres/drizzle.config.ts +10 -0
- package/dist/templates/hono/drizzle-postgres/package.json.hbs +34 -0
- package/dist/templates/hono/drizzle-postgres/src/auth.ts.hbs +114 -0
- package/dist/templates/hono/drizzle-postgres/src/index.ts +35 -0
- package/dist/templates/hono/drizzle-postgres/tsconfig.json +19 -0
- package/dist/templates/hono/drizzle-sqlite/.env.example.hbs +39 -0
- package/dist/templates/hono/drizzle-sqlite/README.md.hbs +85 -0
- package/dist/templates/hono/drizzle-sqlite/drizzle/db.ts +6 -0
- package/dist/templates/hono/drizzle-sqlite/drizzle/migrate.ts +12 -0
- package/dist/templates/hono/drizzle-sqlite/drizzle/schema.ts +55 -0
- package/dist/templates/hono/drizzle-sqlite/drizzle.config.ts +10 -0
- package/dist/templates/hono/drizzle-sqlite/package.json.hbs +34 -0
- package/dist/templates/hono/drizzle-sqlite/src/auth.ts.hbs +114 -0
- package/dist/templates/hono/drizzle-sqlite/src/index.ts +35 -0
- package/dist/templates/hono/drizzle-sqlite/tsconfig.json +19 -0
- package/dist/templates/hono/kysely-mysql/.env.example.hbs +39 -0
- package/dist/templates/hono/kysely-mysql/README.md.hbs +75 -0
- package/dist/templates/hono/kysely-mysql/db/db.ts +11 -0
- package/dist/templates/hono/kysely-mysql/db/migrate.ts +77 -0
- package/dist/templates/hono/kysely-mysql/db/types.ts +54 -0
- package/dist/templates/hono/kysely-mysql/package.json.hbs +29 -0
- package/dist/templates/hono/kysely-mysql/src/auth.ts.hbs +114 -0
- package/dist/templates/hono/kysely-mysql/src/index.ts +35 -0
- package/dist/templates/hono/kysely-mysql/tsconfig.json +19 -0
- package/dist/templates/hono/kysely-postgres/.env.example.hbs +39 -0
- package/dist/templates/hono/kysely-postgres/README.md.hbs +75 -0
- package/dist/templates/hono/kysely-postgres/db/db.ts +11 -0
- package/dist/templates/hono/kysely-postgres/db/migrate.ts +77 -0
- package/dist/templates/hono/kysely-postgres/db/types.ts +54 -0
- package/dist/templates/hono/kysely-postgres/package.json.hbs +30 -0
- package/dist/templates/hono/kysely-postgres/src/auth.ts.hbs +114 -0
- package/dist/templates/hono/kysely-postgres/src/index.ts +35 -0
- package/dist/templates/hono/kysely-postgres/tsconfig.json +19 -0
- package/dist/templates/hono/kysely-sqlite/.env.example.hbs +39 -0
- package/dist/templates/hono/kysely-sqlite/README.md.hbs +75 -0
- package/dist/templates/hono/kysely-sqlite/db/db.ts +9 -0
- package/dist/templates/hono/kysely-sqlite/db/migrate.ts +75 -0
- package/dist/templates/hono/kysely-sqlite/db/types.ts +54 -0
- package/dist/templates/hono/kysely-sqlite/package.json.hbs +30 -0
- package/dist/templates/hono/kysely-sqlite/src/auth.ts.hbs +114 -0
- package/dist/templates/hono/kysely-sqlite/src/index.ts +35 -0
- package/dist/templates/hono/kysely-sqlite/tsconfig.json +19 -0
- package/dist/templates/hono/prisma-mysql/.env.example.hbs +39 -0
- package/dist/templates/hono/prisma-mysql/README.md.hbs +79 -0
- package/dist/templates/hono/prisma-mysql/package.json.hbs +32 -0
- package/dist/templates/hono/prisma-mysql/prisma/schema.prisma +66 -0
- package/dist/templates/hono/prisma-mysql/src/auth.ts.hbs +114 -0
- package/dist/templates/hono/prisma-mysql/src/db.ts +3 -0
- package/dist/templates/hono/prisma-mysql/src/index.ts +35 -0
- package/dist/templates/hono/prisma-mysql/tsconfig.json +19 -0
- package/dist/templates/hono/prisma-postgres/.env.example.hbs +39 -0
- package/dist/templates/hono/prisma-postgres/README.md.hbs +79 -0
- package/dist/templates/hono/prisma-postgres/package.json.hbs +32 -0
- package/dist/templates/hono/prisma-postgres/prisma/schema.prisma +66 -0
- package/dist/templates/hono/prisma-postgres/src/auth.ts.hbs +114 -0
- package/dist/templates/hono/prisma-postgres/src/db.ts +3 -0
- package/dist/templates/hono/prisma-postgres/src/index.ts +35 -0
- package/dist/templates/hono/prisma-postgres/tsconfig.json +19 -0
- package/dist/templates/hono/prisma-sqlite/.env.example.hbs +39 -0
- package/dist/templates/hono/prisma-sqlite/README.md.hbs +79 -0
- package/dist/templates/hono/prisma-sqlite/package.json.hbs +32 -0
- package/dist/templates/hono/prisma-sqlite/prisma/schema.prisma +66 -0
- package/dist/templates/hono/prisma-sqlite/src/auth.ts.hbs +114 -0
- package/dist/templates/hono/prisma-sqlite/src/db.ts +3 -0
- package/dist/templates/hono/prisma-sqlite/src/index.ts +35 -0
- package/dist/templates/hono/prisma-sqlite/tsconfig.json +19 -0
- package/package.json +71 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1050 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/cli/commands/init.ts
|
|
7
|
+
import path4 from "path";
|
|
8
|
+
import * as p5 from "@clack/prompts";
|
|
9
|
+
|
|
10
|
+
// src/cli/prompts.ts
|
|
11
|
+
import * as p from "@clack/prompts";
|
|
12
|
+
|
|
13
|
+
// src/utils/validators.ts
|
|
14
|
+
function validateProjectName(name) {
|
|
15
|
+
if (!name || name.trim().length === 0) {
|
|
16
|
+
return "Project name is required";
|
|
17
|
+
}
|
|
18
|
+
if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(name)) {
|
|
19
|
+
return "Project name must be lowercase alphanumeric with hyphens (e.g. my-auth-api)";
|
|
20
|
+
}
|
|
21
|
+
if (name.length > 100) {
|
|
22
|
+
return "Project name must be 100 characters or less";
|
|
23
|
+
}
|
|
24
|
+
if (name.includes("--")) {
|
|
25
|
+
return "Project name cannot contain consecutive hyphens";
|
|
26
|
+
}
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
var VALID_FRAMEWORKS = ["hono", "fastify", "express"];
|
|
30
|
+
var VALID_ORMS = ["drizzle", "prisma", "kysely"];
|
|
31
|
+
var VALID_DATABASES = ["postgres", "mysql", "sqlite"];
|
|
32
|
+
function parseTemplateOption(template) {
|
|
33
|
+
const value = template.trim().toLowerCase();
|
|
34
|
+
const [frameworkPart, ormDatabasePart] = value.split("/");
|
|
35
|
+
if (!frameworkPart || !ormDatabasePart || value.split("/").length !== 2) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Invalid template "${template}". Expected format: framework/orm-database (e.g. hono/drizzle-postgres).`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const [ormPart, databasePart] = ormDatabasePart.split("-");
|
|
41
|
+
if (!ormPart || !databasePart || ormDatabasePart.split("-").length !== 2) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Invalid template "${template}". Expected format: framework/orm-database (e.g. hono/drizzle-postgres).`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (!VALID_FRAMEWORKS.includes(frameworkPart)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Unknown framework "${frameworkPart}". Supported: ${VALID_FRAMEWORKS.join(", ")}.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (!VALID_ORMS.includes(ormPart)) {
|
|
52
|
+
throw new Error(`Unknown ORM "${ormPart}". Supported: ${VALID_ORMS.join(", ")}.`);
|
|
53
|
+
}
|
|
54
|
+
if (!VALID_DATABASES.includes(databasePart)) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Unknown database "${databasePart}". Supported: ${VALID_DATABASES.join(", ")}.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
framework: frameworkPart,
|
|
61
|
+
orm: ormPart,
|
|
62
|
+
database: databasePart
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/cli/prompts.ts
|
|
67
|
+
function parsePositiveInteger(value) {
|
|
68
|
+
return Number.parseInt(value.trim(), 10);
|
|
69
|
+
}
|
|
70
|
+
async function gatherConfig() {
|
|
71
|
+
p.intro("Welcome to Scaffauth!");
|
|
72
|
+
const projectName = await p.text({
|
|
73
|
+
message: "What is your project name?",
|
|
74
|
+
placeholder: "my-auth-backend",
|
|
75
|
+
validate: validateProjectName
|
|
76
|
+
});
|
|
77
|
+
if (p.isCancel(projectName)) {
|
|
78
|
+
p.cancel("Operation cancelled.");
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
const framework = await p.select({
|
|
82
|
+
message: "Which backend framework?",
|
|
83
|
+
options: [
|
|
84
|
+
{
|
|
85
|
+
value: "hono",
|
|
86
|
+
label: "Hono",
|
|
87
|
+
hint: "Modern, fast, edge-compatible (recommended)"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
value: "fastify",
|
|
91
|
+
label: "Fastify",
|
|
92
|
+
hint: "Battle-tested, huge ecosystem"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
value: "express",
|
|
96
|
+
label: "Express",
|
|
97
|
+
hint: "Most familiar, largest community"
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
});
|
|
101
|
+
if (p.isCancel(framework)) {
|
|
102
|
+
p.cancel("Operation cancelled.");
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
const database = await p.select({
|
|
106
|
+
message: "Which database?",
|
|
107
|
+
options: [
|
|
108
|
+
{
|
|
109
|
+
value: "postgres",
|
|
110
|
+
label: "PostgreSQL",
|
|
111
|
+
hint: "Production-grade (recommended)"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
value: "mysql",
|
|
115
|
+
label: "MySQL",
|
|
116
|
+
hint: "Popular alternative"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
value: "sqlite",
|
|
120
|
+
label: "SQLite",
|
|
121
|
+
hint: "Quick local development"
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
});
|
|
125
|
+
if (p.isCancel(database)) {
|
|
126
|
+
p.cancel("Operation cancelled.");
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
const orm = await p.select({
|
|
130
|
+
message: "Which ORM / query builder?",
|
|
131
|
+
options: [
|
|
132
|
+
{
|
|
133
|
+
value: "drizzle",
|
|
134
|
+
label: "Drizzle",
|
|
135
|
+
hint: "Best TypeScript DX (recommended)"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
value: "prisma",
|
|
139
|
+
label: "Prisma",
|
|
140
|
+
hint: "Most popular, great tooling"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
value: "kysely",
|
|
144
|
+
label: "Kysely",
|
|
145
|
+
hint: "Type-safe SQL query builder"
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
});
|
|
149
|
+
if (p.isCancel(orm)) {
|
|
150
|
+
p.cancel("Operation cancelled.");
|
|
151
|
+
process.exit(0);
|
|
152
|
+
}
|
|
153
|
+
const providers = await p.multiselect({
|
|
154
|
+
message: "Which OAuth providers? (Space to select, Enter to confirm)",
|
|
155
|
+
options: [
|
|
156
|
+
{ value: "github", label: "GitHub" },
|
|
157
|
+
{ value: "google", label: "Google" },
|
|
158
|
+
{ value: "discord", label: "Discord" },
|
|
159
|
+
{ value: "twitter", label: "Twitter/X" },
|
|
160
|
+
{ value: "apple", label: "Apple" },
|
|
161
|
+
{ value: "microsoft", label: "Microsoft" }
|
|
162
|
+
],
|
|
163
|
+
required: false
|
|
164
|
+
});
|
|
165
|
+
if (p.isCancel(providers)) {
|
|
166
|
+
p.cancel("Operation cancelled.");
|
|
167
|
+
process.exit(0);
|
|
168
|
+
}
|
|
169
|
+
const twoFactor = await p.confirm({
|
|
170
|
+
message: "Enable two-factor authentication (2FA)?",
|
|
171
|
+
initialValue: false
|
|
172
|
+
});
|
|
173
|
+
if (p.isCancel(twoFactor)) {
|
|
174
|
+
p.cancel("Operation cancelled.");
|
|
175
|
+
process.exit(0);
|
|
176
|
+
}
|
|
177
|
+
const useEmail = await p.confirm({
|
|
178
|
+
message: "Set up email verification & password reset?",
|
|
179
|
+
initialValue: false
|
|
180
|
+
});
|
|
181
|
+
if (p.isCancel(useEmail)) {
|
|
182
|
+
p.cancel("Operation cancelled.");
|
|
183
|
+
process.exit(0);
|
|
184
|
+
}
|
|
185
|
+
let emailProvider;
|
|
186
|
+
if (useEmail) {
|
|
187
|
+
const provider = await p.select({
|
|
188
|
+
message: "Which email provider?",
|
|
189
|
+
options: [
|
|
190
|
+
{
|
|
191
|
+
value: "resend",
|
|
192
|
+
label: "Resend",
|
|
193
|
+
hint: "Modern email API (recommended)"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
value: "sendgrid",
|
|
197
|
+
label: "SendGrid",
|
|
198
|
+
hint: "Enterprise-grade email"
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
value: "smtp",
|
|
202
|
+
label: "SMTP",
|
|
203
|
+
hint: "Any SMTP server"
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
});
|
|
207
|
+
if (p.isCancel(provider)) {
|
|
208
|
+
p.cancel("Operation cancelled.");
|
|
209
|
+
process.exit(0);
|
|
210
|
+
}
|
|
211
|
+
emailProvider = provider;
|
|
212
|
+
}
|
|
213
|
+
const rbac = await p.confirm({
|
|
214
|
+
message: "Enable role-based access control (RBAC)?",
|
|
215
|
+
initialValue: false
|
|
216
|
+
});
|
|
217
|
+
if (p.isCancel(rbac)) {
|
|
218
|
+
p.cancel("Operation cancelled.");
|
|
219
|
+
process.exit(0);
|
|
220
|
+
}
|
|
221
|
+
const sessionStrategy = await p.select({
|
|
222
|
+
message: "Session strategy?",
|
|
223
|
+
options: [
|
|
224
|
+
{
|
|
225
|
+
value: "database",
|
|
226
|
+
label: "Database sessions",
|
|
227
|
+
hint: "Store and validate sessions via database (recommended)"
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
value: "database-cookie-cache",
|
|
231
|
+
label: "Database + cookie cache",
|
|
232
|
+
hint: "Add short-lived cookie cache for fewer DB reads"
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
});
|
|
236
|
+
if (p.isCancel(sessionStrategy)) {
|
|
237
|
+
p.cancel("Operation cancelled.");
|
|
238
|
+
process.exit(0);
|
|
239
|
+
}
|
|
240
|
+
const sessionLifetimeDays = await p.text({
|
|
241
|
+
message: "Session lifetime (days)",
|
|
242
|
+
placeholder: "7",
|
|
243
|
+
initialValue: "7",
|
|
244
|
+
validate: (value) => {
|
|
245
|
+
const parsed = parsePositiveInteger(value);
|
|
246
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
247
|
+
return "Enter a positive integer number of days.";
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
if (p.isCancel(sessionLifetimeDays)) {
|
|
252
|
+
p.cancel("Operation cancelled.");
|
|
253
|
+
process.exit(0);
|
|
254
|
+
}
|
|
255
|
+
const sessionRefreshHours = await p.text({
|
|
256
|
+
message: "Session refresh/update age (hours)",
|
|
257
|
+
placeholder: "24",
|
|
258
|
+
initialValue: "24",
|
|
259
|
+
validate: (value) => {
|
|
260
|
+
const parsed = parsePositiveInteger(value);
|
|
261
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
262
|
+
return "Enter a positive integer number of hours.";
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
if (p.isCancel(sessionRefreshHours)) {
|
|
267
|
+
p.cancel("Operation cancelled.");
|
|
268
|
+
process.exit(0);
|
|
269
|
+
}
|
|
270
|
+
let cookieCacheMaxAgeSeconds = 300;
|
|
271
|
+
if (sessionStrategy === "database-cookie-cache") {
|
|
272
|
+
const cookieCacheMaxAge = await p.text({
|
|
273
|
+
message: "Cookie cache max age (seconds)",
|
|
274
|
+
placeholder: "300",
|
|
275
|
+
initialValue: "300",
|
|
276
|
+
validate: (value) => {
|
|
277
|
+
const parsed = parsePositiveInteger(value);
|
|
278
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
279
|
+
return "Enter a positive integer number of seconds.";
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
if (p.isCancel(cookieCacheMaxAge)) {
|
|
284
|
+
p.cancel("Operation cancelled.");
|
|
285
|
+
process.exit(0);
|
|
286
|
+
}
|
|
287
|
+
cookieCacheMaxAgeSeconds = parsePositiveInteger(cookieCacheMaxAge);
|
|
288
|
+
}
|
|
289
|
+
const createGitHubRepo = await p.confirm({
|
|
290
|
+
message: "Create a GitHub repository?",
|
|
291
|
+
initialValue: false
|
|
292
|
+
});
|
|
293
|
+
if (p.isCancel(createGitHubRepo)) {
|
|
294
|
+
p.cancel("Operation cancelled.");
|
|
295
|
+
process.exit(0);
|
|
296
|
+
}
|
|
297
|
+
let githubConfig;
|
|
298
|
+
if (createGitHubRepo) {
|
|
299
|
+
const repoPrivate = await p.confirm({
|
|
300
|
+
message: "Make the repository private?",
|
|
301
|
+
initialValue: false
|
|
302
|
+
});
|
|
303
|
+
if (p.isCancel(repoPrivate)) {
|
|
304
|
+
p.cancel("Operation cancelled.");
|
|
305
|
+
process.exit(0);
|
|
306
|
+
}
|
|
307
|
+
githubConfig = {
|
|
308
|
+
createRepo: true,
|
|
309
|
+
repoName: projectName,
|
|
310
|
+
private: repoPrivate
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
const deploy = await p.confirm({
|
|
314
|
+
message: "Deploy now?",
|
|
315
|
+
initialValue: false
|
|
316
|
+
});
|
|
317
|
+
if (p.isCancel(deploy)) {
|
|
318
|
+
p.cancel("Operation cancelled.");
|
|
319
|
+
process.exit(0);
|
|
320
|
+
}
|
|
321
|
+
let deploymentConfig;
|
|
322
|
+
if (deploy) {
|
|
323
|
+
const platform = await p.select({
|
|
324
|
+
message: "Which platform?",
|
|
325
|
+
options: [
|
|
326
|
+
{
|
|
327
|
+
value: "vercel",
|
|
328
|
+
label: "Vercel",
|
|
329
|
+
hint: "Serverless, edge-optimized"
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
value: "railway",
|
|
333
|
+
label: "Railway",
|
|
334
|
+
hint: "Full-stack platform"
|
|
335
|
+
}
|
|
336
|
+
]
|
|
337
|
+
});
|
|
338
|
+
if (p.isCancel(platform)) {
|
|
339
|
+
p.cancel("Operation cancelled.");
|
|
340
|
+
process.exit(0);
|
|
341
|
+
}
|
|
342
|
+
deploymentConfig = {
|
|
343
|
+
platform,
|
|
344
|
+
environmentVars: {}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
projectName,
|
|
349
|
+
framework,
|
|
350
|
+
database,
|
|
351
|
+
orm,
|
|
352
|
+
authConfig: {
|
|
353
|
+
emailPassword: true,
|
|
354
|
+
providers,
|
|
355
|
+
twoFactor,
|
|
356
|
+
emailProvider,
|
|
357
|
+
rbac,
|
|
358
|
+
session: {
|
|
359
|
+
strategy: sessionStrategy,
|
|
360
|
+
expiresIn: parsePositiveInteger(sessionLifetimeDays) * 24 * 60 * 60,
|
|
361
|
+
updateAge: parsePositiveInteger(sessionRefreshHours) * 60 * 60,
|
|
362
|
+
cookieCacheEnabled: sessionStrategy === "database-cookie-cache",
|
|
363
|
+
cookieCacheMaxAge: cookieCacheMaxAgeSeconds
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
deployment: deploymentConfig,
|
|
367
|
+
github: githubConfig
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function getDefaultConfig(projectName = "scaffauth-project") {
|
|
371
|
+
return {
|
|
372
|
+
projectName,
|
|
373
|
+
framework: "hono",
|
|
374
|
+
database: "postgres",
|
|
375
|
+
orm: "drizzle",
|
|
376
|
+
authConfig: {
|
|
377
|
+
emailPassword: true,
|
|
378
|
+
providers: ["github"],
|
|
379
|
+
twoFactor: false,
|
|
380
|
+
rbac: false,
|
|
381
|
+
session: {
|
|
382
|
+
strategy: "database",
|
|
383
|
+
expiresIn: 7 * 24 * 60 * 60,
|
|
384
|
+
updateAge: 24 * 60 * 60,
|
|
385
|
+
cookieCacheEnabled: false,
|
|
386
|
+
cookieCacheMaxAge: 300
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/generators/template.ts
|
|
393
|
+
import path2 from "path";
|
|
394
|
+
import { fileURLToPath } from "url";
|
|
395
|
+
import crypto from "crypto";
|
|
396
|
+
import Handlebars2 from "handlebars";
|
|
397
|
+
|
|
398
|
+
// src/utils/fileOperations.ts
|
|
399
|
+
import fs from "fs-extra";
|
|
400
|
+
import path from "path";
|
|
401
|
+
import Handlebars from "handlebars";
|
|
402
|
+
async function copyTemplate(templateDir, targetDir, context) {
|
|
403
|
+
await fs.ensureDir(targetDir);
|
|
404
|
+
const entries = await fs.readdir(templateDir, { withFileTypes: true });
|
|
405
|
+
for (const entry of entries) {
|
|
406
|
+
const srcPath = path.join(templateDir, entry.name);
|
|
407
|
+
let destName = entry.name;
|
|
408
|
+
if (destName.endsWith(".hbs")) {
|
|
409
|
+
destName = destName.slice(0, -4);
|
|
410
|
+
}
|
|
411
|
+
const destPath = path.join(targetDir, destName);
|
|
412
|
+
if (entry.isDirectory()) {
|
|
413
|
+
await copyTemplate(srcPath, destPath, context);
|
|
414
|
+
} else if (entry.name.endsWith(".hbs")) {
|
|
415
|
+
const content = await fs.readFile(srcPath, "utf-8");
|
|
416
|
+
const template = Handlebars.compile(content);
|
|
417
|
+
const rendered = template(context);
|
|
418
|
+
await fs.writeFile(destPath, rendered, "utf-8");
|
|
419
|
+
} else {
|
|
420
|
+
await fs.copy(srcPath, destPath);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async function directoryExists(dir) {
|
|
425
|
+
try {
|
|
426
|
+
const stat = await fs.stat(dir);
|
|
427
|
+
if (!stat.isDirectory()) return false;
|
|
428
|
+
const files = await fs.readdir(dir);
|
|
429
|
+
return files.length > 0;
|
|
430
|
+
} catch {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// src/utils/logger.ts
|
|
436
|
+
import chalk from "chalk";
|
|
437
|
+
var logger = {
|
|
438
|
+
info(message) {
|
|
439
|
+
console.log(chalk.blue("i"), message);
|
|
440
|
+
},
|
|
441
|
+
success(message) {
|
|
442
|
+
console.log(chalk.green("\u2713"), message);
|
|
443
|
+
},
|
|
444
|
+
warning(message) {
|
|
445
|
+
console.log(chalk.yellow("\u26A0"), message);
|
|
446
|
+
},
|
|
447
|
+
error(message) {
|
|
448
|
+
console.log(chalk.red("\u2717"), message);
|
|
449
|
+
},
|
|
450
|
+
step(message) {
|
|
451
|
+
console.log(chalk.cyan("\u2192"), message);
|
|
452
|
+
},
|
|
453
|
+
debug(message) {
|
|
454
|
+
if (process.env.SCAFFAUTH_DEBUG) {
|
|
455
|
+
console.log(chalk.gray("[debug]"), message);
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
blank() {
|
|
459
|
+
console.log();
|
|
460
|
+
},
|
|
461
|
+
box(title, content) {
|
|
462
|
+
console.log();
|
|
463
|
+
console.log(chalk.bold(title));
|
|
464
|
+
console.log(content);
|
|
465
|
+
console.log();
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
// src/generators/template.ts
|
|
470
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
471
|
+
var __dirname = path2.dirname(__filename);
|
|
472
|
+
Handlebars2.registerHelper("uppercase", (str) => str.toUpperCase());
|
|
473
|
+
Handlebars2.registerHelper(
|
|
474
|
+
"eq",
|
|
475
|
+
(a, b) => a === b
|
|
476
|
+
);
|
|
477
|
+
Handlebars2.registerHelper(
|
|
478
|
+
"or",
|
|
479
|
+
(...args) => {
|
|
480
|
+
const values = args.slice(0, -1);
|
|
481
|
+
return values.some(Boolean);
|
|
482
|
+
}
|
|
483
|
+
);
|
|
484
|
+
function getTemplatePath(config) {
|
|
485
|
+
const { framework, orm, database } = config;
|
|
486
|
+
const templateName = `${orm}-${database}`;
|
|
487
|
+
const templateDir = path2.resolve(__dirname, "templates", framework, templateName);
|
|
488
|
+
logger.debug(`Template path: ${templateDir}`);
|
|
489
|
+
return templateDir;
|
|
490
|
+
}
|
|
491
|
+
function buildTemplateContext(config) {
|
|
492
|
+
return {
|
|
493
|
+
projectName: config.projectName,
|
|
494
|
+
framework: config.framework,
|
|
495
|
+
database: config.database,
|
|
496
|
+
orm: config.orm,
|
|
497
|
+
providers: config.authConfig.providers,
|
|
498
|
+
twoFactor: config.authConfig.twoFactor,
|
|
499
|
+
rbac: config.authConfig.rbac,
|
|
500
|
+
emailProvider: config.authConfig.emailProvider,
|
|
501
|
+
sessionExpiresIn: config.authConfig.session.expiresIn,
|
|
502
|
+
sessionUpdateAge: config.authConfig.session.updateAge,
|
|
503
|
+
sessionCookieCacheEnabled: config.authConfig.session.cookieCacheEnabled,
|
|
504
|
+
sessionCookieCacheMaxAge: config.authConfig.session.cookieCacheMaxAge,
|
|
505
|
+
// Generate a random auth secret for the .env.example
|
|
506
|
+
authSecret: crypto.randomBytes(32).toString("hex")
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
async function generateProject(config, targetDir) {
|
|
510
|
+
const templatePath = getTemplatePath(config);
|
|
511
|
+
const context = buildTemplateContext(config);
|
|
512
|
+
logger.debug(`Generating project at: ${targetDir}`);
|
|
513
|
+
logger.debug(`Using template: ${templatePath}`);
|
|
514
|
+
await copyTemplate(templatePath, targetDir, context);
|
|
515
|
+
logger.debug("Project generation complete");
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/generators/install.ts
|
|
519
|
+
import { execa } from "execa";
|
|
520
|
+
|
|
521
|
+
// src/utils/packageManager.ts
|
|
522
|
+
function detectPackageManager() {
|
|
523
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
524
|
+
if (userAgent) {
|
|
525
|
+
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
526
|
+
if (userAgent.startsWith("yarn")) return "yarn";
|
|
527
|
+
if (userAgent.startsWith("bun")) return "bun";
|
|
528
|
+
}
|
|
529
|
+
return "npm";
|
|
530
|
+
}
|
|
531
|
+
function getInstallCommand(pm) {
|
|
532
|
+
switch (pm) {
|
|
533
|
+
case "pnpm":
|
|
534
|
+
return "pnpm install";
|
|
535
|
+
case "yarn":
|
|
536
|
+
return "yarn";
|
|
537
|
+
case "bun":
|
|
538
|
+
return "bun install";
|
|
539
|
+
default:
|
|
540
|
+
return "npm install";
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function getRunCommand(pm, script) {
|
|
544
|
+
switch (pm) {
|
|
545
|
+
case "pnpm":
|
|
546
|
+
return `pnpm ${script}`;
|
|
547
|
+
case "yarn":
|
|
548
|
+
return `yarn ${script}`;
|
|
549
|
+
case "bun":
|
|
550
|
+
return `bun run ${script}`;
|
|
551
|
+
default:
|
|
552
|
+
return `npm run ${script}`;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/generators/install.ts
|
|
557
|
+
async function installDependencies(projectDir, pm) {
|
|
558
|
+
const command = getInstallCommand(pm);
|
|
559
|
+
const [cmd, ...args] = command.split(" ");
|
|
560
|
+
logger.debug(`Running: ${command} in ${projectDir}`);
|
|
561
|
+
await execa(cmd, args, {
|
|
562
|
+
cwd: projectDir,
|
|
563
|
+
stdio: "pipe"
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// src/utils/gitOperations.ts
|
|
568
|
+
import { execa as execa2 } from "execa";
|
|
569
|
+
async function isGitInstalled() {
|
|
570
|
+
try {
|
|
571
|
+
await execa2("git", ["--version"]);
|
|
572
|
+
return true;
|
|
573
|
+
} catch {
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
async function initGitRepo(projectDir) {
|
|
578
|
+
logger.debug(`Initializing git repo in ${projectDir}`);
|
|
579
|
+
await execa2("git", ["init"], { cwd: projectDir });
|
|
580
|
+
await execa2("git", ["add", "-A"], { cwd: projectDir });
|
|
581
|
+
await execa2("git", ["commit", "-m", "Initial commit from Scaffauth"], {
|
|
582
|
+
cwd: projectDir
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
async function pushToRemote(projectDir, remoteUrl) {
|
|
586
|
+
logger.debug(`Adding remote: ${remoteUrl}`);
|
|
587
|
+
await execa2("git", ["remote", "add", "origin", remoteUrl], {
|
|
588
|
+
cwd: projectDir
|
|
589
|
+
});
|
|
590
|
+
await execa2("git", ["branch", "-M", "main"], { cwd: projectDir });
|
|
591
|
+
await execa2("git", ["push", "-u", "origin", "main"], { cwd: projectDir });
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// src/utils/github.ts
|
|
595
|
+
import { Octokit } from "@octokit/rest";
|
|
596
|
+
import { execa as execa3 } from "execa";
|
|
597
|
+
import * as p2 from "@clack/prompts";
|
|
598
|
+
async function getGitHubToken() {
|
|
599
|
+
try {
|
|
600
|
+
const { stdout } = await execa3("gh", ["auth", "token"]);
|
|
601
|
+
const token2 = stdout.trim();
|
|
602
|
+
if (token2) {
|
|
603
|
+
logger.debug("Using token from gh CLI");
|
|
604
|
+
return token2;
|
|
605
|
+
}
|
|
606
|
+
} catch {
|
|
607
|
+
logger.debug("gh CLI not available or not authenticated");
|
|
608
|
+
}
|
|
609
|
+
const token = await p2.password({
|
|
610
|
+
message: "Enter your GitHub personal access token:",
|
|
611
|
+
validate: (value) => {
|
|
612
|
+
if (!value || value.trim().length === 0) {
|
|
613
|
+
return "Token is required";
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
if (p2.isCancel(token)) {
|
|
618
|
+
throw new Error("GitHub authentication cancelled");
|
|
619
|
+
}
|
|
620
|
+
return token;
|
|
621
|
+
}
|
|
622
|
+
async function setupGitHub(config, projectDir) {
|
|
623
|
+
const spinner5 = p2.spinner();
|
|
624
|
+
spinner5.start("Authenticating with GitHub");
|
|
625
|
+
const token = await getGitHubToken();
|
|
626
|
+
const octokit = new Octokit({ auth: token });
|
|
627
|
+
let username;
|
|
628
|
+
try {
|
|
629
|
+
const { data: user } = await octokit.users.getAuthenticated();
|
|
630
|
+
username = user.login;
|
|
631
|
+
spinner5.stop(`Authenticated as ${username}`);
|
|
632
|
+
} catch {
|
|
633
|
+
spinner5.stop("GitHub authentication failed");
|
|
634
|
+
throw new Error(
|
|
635
|
+
"Invalid GitHub token. Make sure it has the 'repo' scope."
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
spinner5.start("Creating GitHub repository");
|
|
639
|
+
let repoUrl;
|
|
640
|
+
let cloneUrl;
|
|
641
|
+
try {
|
|
642
|
+
const { data: repo } = await octokit.repos.createForAuthenticatedUser({
|
|
643
|
+
name: config.repoName,
|
|
644
|
+
private: config.private,
|
|
645
|
+
description: config.description || "Auth backend powered by Better Auth",
|
|
646
|
+
auto_init: false
|
|
647
|
+
});
|
|
648
|
+
repoUrl = repo.html_url;
|
|
649
|
+
cloneUrl = repo.clone_url;
|
|
650
|
+
spinner5.stop(`Repository created: ${repoUrl}`);
|
|
651
|
+
} catch (err) {
|
|
652
|
+
spinner5.stop("Failed to create repository");
|
|
653
|
+
if (err instanceof Error && err.message.includes("name already exists")) {
|
|
654
|
+
throw new Error(
|
|
655
|
+
`Repository "${config.repoName}" already exists on GitHub.`
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
throw err;
|
|
659
|
+
}
|
|
660
|
+
spinner5.start("Pushing code to GitHub");
|
|
661
|
+
try {
|
|
662
|
+
await initGitRepo(projectDir);
|
|
663
|
+
await pushToRemote(projectDir, cloneUrl);
|
|
664
|
+
spinner5.stop("Code pushed to GitHub");
|
|
665
|
+
} catch (err) {
|
|
666
|
+
spinner5.stop("Failed to push to GitHub");
|
|
667
|
+
logger.debug(err instanceof Error ? err.message : String(err));
|
|
668
|
+
throw new Error(
|
|
669
|
+
"Failed to push to GitHub. Make sure git is installed and configured."
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
return { repoUrl, cloneUrl };
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// src/deployers/vercel.ts
|
|
676
|
+
import path3 from "path";
|
|
677
|
+
import { execa as execa4 } from "execa";
|
|
678
|
+
import * as p3 from "@clack/prompts";
|
|
679
|
+
import fs2 from "fs-extra";
|
|
680
|
+
async function checkVercelCLI() {
|
|
681
|
+
try {
|
|
682
|
+
await execa4("vercel", ["--version"]);
|
|
683
|
+
return true;
|
|
684
|
+
} catch {
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function generateVercelConfig(_config) {
|
|
689
|
+
return {
|
|
690
|
+
version: 2,
|
|
691
|
+
builds: [
|
|
692
|
+
{
|
|
693
|
+
src: "src/index.ts",
|
|
694
|
+
use: "@vercel/node"
|
|
695
|
+
}
|
|
696
|
+
],
|
|
697
|
+
routes: [
|
|
698
|
+
{
|
|
699
|
+
src: "/(.*)",
|
|
700
|
+
dest: "src/index.ts"
|
|
701
|
+
}
|
|
702
|
+
]
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
async function deployToVercel(config, projectDir) {
|
|
706
|
+
const spinner5 = p3.spinner();
|
|
707
|
+
spinner5.start("Checking Vercel CLI");
|
|
708
|
+
const hasVercel = await checkVercelCLI();
|
|
709
|
+
if (!hasVercel) {
|
|
710
|
+
spinner5.stop("Vercel CLI not found");
|
|
711
|
+
p3.note("Install with: npm i -g vercel", "Missing dependency");
|
|
712
|
+
throw new Error(
|
|
713
|
+
"Vercel CLI is not installed. Run 'npm i -g vercel' and try again."
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
spinner5.stop("Vercel CLI found");
|
|
717
|
+
spinner5.start("Configuring Vercel project");
|
|
718
|
+
const vercelConfig = generateVercelConfig(config);
|
|
719
|
+
await fs2.writeFile(
|
|
720
|
+
path3.join(projectDir, "vercel.json"),
|
|
721
|
+
JSON.stringify(vercelConfig, null, 2) + "\n"
|
|
722
|
+
);
|
|
723
|
+
spinner5.stop("Vercel configuration written");
|
|
724
|
+
spinner5.start("Deploying to Vercel");
|
|
725
|
+
try {
|
|
726
|
+
const { stdout } = await execa4("vercel", ["--prod", "--yes"], {
|
|
727
|
+
cwd: projectDir,
|
|
728
|
+
// Vercel CLI may need terminal interaction for first-time login
|
|
729
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
730
|
+
});
|
|
731
|
+
const urlMatch = stdout.match(/https:\/\/[^\s]+/);
|
|
732
|
+
const deploymentUrl = urlMatch ? urlMatch[0] : "deployment URL unavailable";
|
|
733
|
+
spinner5.stop(`Deployed to Vercel: ${deploymentUrl}`);
|
|
734
|
+
return { url: deploymentUrl, platform: "vercel" };
|
|
735
|
+
} catch (err) {
|
|
736
|
+
spinner5.stop("Vercel deployment failed");
|
|
737
|
+
logger.debug(err instanceof Error ? err.message : String(err));
|
|
738
|
+
throw new Error(
|
|
739
|
+
"Vercel deployment failed. Run 'vercel login' first, then try 'vercel --prod' in the project directory."
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// src/deployers/railway.ts
|
|
745
|
+
import { execa as execa5 } from "execa";
|
|
746
|
+
import * as p4 from "@clack/prompts";
|
|
747
|
+
async function checkRailwayCLI() {
|
|
748
|
+
try {
|
|
749
|
+
await execa5("railway", ["--version"]);
|
|
750
|
+
return true;
|
|
751
|
+
} catch {
|
|
752
|
+
return false;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
async function deployToRailway(config, projectDir) {
|
|
756
|
+
const spinner5 = p4.spinner();
|
|
757
|
+
spinner5.start("Checking Railway CLI");
|
|
758
|
+
const hasRailway = await checkRailwayCLI();
|
|
759
|
+
if (!hasRailway) {
|
|
760
|
+
spinner5.stop("Railway CLI not found");
|
|
761
|
+
p4.note("Install with: npm i -g @railway/cli", "Missing dependency");
|
|
762
|
+
throw new Error(
|
|
763
|
+
"Railway CLI is not installed. Run 'npm i -g @railway/cli' and try again."
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
spinner5.stop("Railway CLI found");
|
|
767
|
+
spinner5.start("Creating Railway project");
|
|
768
|
+
try {
|
|
769
|
+
await execa5("railway", ["init"], {
|
|
770
|
+
cwd: projectDir,
|
|
771
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
772
|
+
});
|
|
773
|
+
spinner5.stop("Railway project created");
|
|
774
|
+
} catch (err) {
|
|
775
|
+
spinner5.stop("Failed to create Railway project");
|
|
776
|
+
logger.debug(err instanceof Error ? err.message : String(err));
|
|
777
|
+
throw new Error(
|
|
778
|
+
"Failed to initialize Railway project. Run 'railway login' first."
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
if (config.database === "postgres") {
|
|
782
|
+
spinner5.start("Provisioning PostgreSQL database");
|
|
783
|
+
try {
|
|
784
|
+
await execa5("railway", ["add", "--plugin", "postgresql"], {
|
|
785
|
+
cwd: projectDir,
|
|
786
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
787
|
+
});
|
|
788
|
+
spinner5.stop("PostgreSQL database provisioned");
|
|
789
|
+
} catch (err) {
|
|
790
|
+
spinner5.stop("Failed to provision database");
|
|
791
|
+
logger.warning(
|
|
792
|
+
"You may need to add a database manually in the Railway dashboard."
|
|
793
|
+
);
|
|
794
|
+
logger.debug(err instanceof Error ? err.message : String(err));
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
spinner5.start("Deploying to Railway");
|
|
798
|
+
try {
|
|
799
|
+
await execa5("railway", ["up", "--detach"], {
|
|
800
|
+
cwd: projectDir,
|
|
801
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
802
|
+
});
|
|
803
|
+
spinner5.stop("Deployed to Railway");
|
|
804
|
+
} catch (err) {
|
|
805
|
+
spinner5.stop("Railway deployment failed");
|
|
806
|
+
logger.debug(err instanceof Error ? err.message : String(err));
|
|
807
|
+
throw new Error(
|
|
808
|
+
"Railway deployment failed. Try running 'railway up' in the project directory."
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
let deploymentUrl = "check Railway dashboard for URL";
|
|
812
|
+
try {
|
|
813
|
+
const { stdout } = await execa5("railway", ["domain"], {
|
|
814
|
+
cwd: projectDir
|
|
815
|
+
});
|
|
816
|
+
if (stdout.trim()) {
|
|
817
|
+
deploymentUrl = stdout.trim();
|
|
818
|
+
}
|
|
819
|
+
} catch {
|
|
820
|
+
logger.debug("Could not retrieve Railway domain automatically");
|
|
821
|
+
}
|
|
822
|
+
return { url: deploymentUrl, platform: "railway" };
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// src/cli/commands/init.ts
|
|
826
|
+
function getErrorMessage(err) {
|
|
827
|
+
if (err instanceof Error && err.message.trim().length > 0) {
|
|
828
|
+
return err.message;
|
|
829
|
+
}
|
|
830
|
+
return "Unknown error occurred";
|
|
831
|
+
}
|
|
832
|
+
function logRecoverySuggestion(context, err) {
|
|
833
|
+
const message = getErrorMessage(err).toLowerCase();
|
|
834
|
+
if (context === "config") {
|
|
835
|
+
if (message.includes("tty")) {
|
|
836
|
+
logger.info("Run in an interactive terminal, or use --yes / --template.");
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
if (message.includes("template")) {
|
|
840
|
+
logger.info(
|
|
841
|
+
"Use --template in this format: framework/orm-database (e.g. hono/drizzle-postgres)."
|
|
842
|
+
);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (context === "generation") {
|
|
847
|
+
if (message.includes("enoent") || message.includes("no such file") || message.includes("template")) {
|
|
848
|
+
logger.info(
|
|
849
|
+
"Template files were not found. Run npm run build, then try again."
|
|
850
|
+
);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (message.includes("eacces") || message.includes("permission")) {
|
|
854
|
+
logger.info(
|
|
855
|
+
"Check write permissions for the target directory and retry."
|
|
856
|
+
);
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
if (context === "install") {
|
|
861
|
+
logger.info(
|
|
862
|
+
"You can continue setup manually by installing dependencies in the generated project."
|
|
863
|
+
);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
if (context === "github") {
|
|
867
|
+
logger.info(
|
|
868
|
+
"You can continue locally and connect GitHub later with git init/add/commit/remote/push."
|
|
869
|
+
);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
if (context === "deployment") {
|
|
873
|
+
logger.info(
|
|
874
|
+
"You can deploy manually later from the generated project directory."
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
async function resolveConfig(options) {
|
|
879
|
+
if (!process.stdin.isTTY && !options.yes && !options.template) {
|
|
880
|
+
throw new Error(
|
|
881
|
+
"Interactive prompts require a TTY, but none was detected in this environment."
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
if (options.template) {
|
|
885
|
+
const parsedTemplate = parseTemplateOption(options.template);
|
|
886
|
+
const config = getDefaultConfig();
|
|
887
|
+
config.framework = parsedTemplate.framework;
|
|
888
|
+
config.orm = parsedTemplate.orm;
|
|
889
|
+
config.database = parsedTemplate.database;
|
|
890
|
+
logger.info(
|
|
891
|
+
`Using template ${parsedTemplate.framework}/${parsedTemplate.orm}-${parsedTemplate.database}`
|
|
892
|
+
);
|
|
893
|
+
logger.info(
|
|
894
|
+
`Skipping prompts. Using default project name "${config.projectName}".`
|
|
895
|
+
);
|
|
896
|
+
return config;
|
|
897
|
+
}
|
|
898
|
+
if (options.yes) {
|
|
899
|
+
const config = getDefaultConfig();
|
|
900
|
+
logger.info("Using default configuration (Hono + Drizzle + PostgreSQL)");
|
|
901
|
+
return config;
|
|
902
|
+
}
|
|
903
|
+
return gatherConfig();
|
|
904
|
+
}
|
|
905
|
+
async function runInit(options) {
|
|
906
|
+
if (options.debug) {
|
|
907
|
+
process.env.SCAFFAUTH_DEBUG = "1";
|
|
908
|
+
}
|
|
909
|
+
let config;
|
|
910
|
+
try {
|
|
911
|
+
config = await resolveConfig(options);
|
|
912
|
+
} catch (err) {
|
|
913
|
+
logger.error(getErrorMessage(err));
|
|
914
|
+
logRecoverySuggestion("config", err);
|
|
915
|
+
process.exit(1);
|
|
916
|
+
}
|
|
917
|
+
const targetDir = path4.resolve(process.cwd(), config.projectName);
|
|
918
|
+
logger.step(
|
|
919
|
+
`Stack: ${config.framework} + ${config.orm} + ${config.database}`
|
|
920
|
+
);
|
|
921
|
+
logger.step(`Target: ${targetDir}`);
|
|
922
|
+
if (await directoryExists(targetDir)) {
|
|
923
|
+
if (!process.stdin.isTTY) {
|
|
924
|
+
logger.error(
|
|
925
|
+
`Directory "${config.projectName}" already exists and cannot be confirmed in non-interactive mode.`
|
|
926
|
+
);
|
|
927
|
+
logger.info(
|
|
928
|
+
"Use a different working directory or remove the existing directory before rerunning."
|
|
929
|
+
);
|
|
930
|
+
process.exit(1);
|
|
931
|
+
}
|
|
932
|
+
const overwrite = await p5.confirm({
|
|
933
|
+
message: `Directory "${config.projectName}" already exists. Overwrite?`,
|
|
934
|
+
initialValue: false
|
|
935
|
+
});
|
|
936
|
+
if (p5.isCancel(overwrite) || !overwrite) {
|
|
937
|
+
p5.cancel("Operation cancelled.");
|
|
938
|
+
process.exit(0);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
const spinner5 = p5.spinner();
|
|
942
|
+
spinner5.start("Generating project files");
|
|
943
|
+
try {
|
|
944
|
+
await generateProject(config, targetDir);
|
|
945
|
+
spinner5.stop("Project files generated");
|
|
946
|
+
} catch (err) {
|
|
947
|
+
spinner5.stop("Failed to generate project files");
|
|
948
|
+
logger.error(getErrorMessage(err));
|
|
949
|
+
logRecoverySuggestion("generation", err);
|
|
950
|
+
process.exit(1);
|
|
951
|
+
}
|
|
952
|
+
if (options.install !== false) {
|
|
953
|
+
const pm2 = detectPackageManager();
|
|
954
|
+
spinner5.start(`Installing dependencies with ${pm2}`);
|
|
955
|
+
try {
|
|
956
|
+
await installDependencies(targetDir, pm2);
|
|
957
|
+
spinner5.stop("Dependencies installed");
|
|
958
|
+
} catch (err) {
|
|
959
|
+
spinner5.stop("Failed to install dependencies");
|
|
960
|
+
logger.warning("Dependency installation failed.");
|
|
961
|
+
logger.info(`Manual command: ${getInstallCommand(pm2)}`);
|
|
962
|
+
logRecoverySuggestion("install", err);
|
|
963
|
+
logger.debug(getErrorMessage(err));
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
let githubResult;
|
|
967
|
+
const shouldGit = options.git !== false;
|
|
968
|
+
if (shouldGit && config.github?.createRepo) {
|
|
969
|
+
try {
|
|
970
|
+
githubResult = await setupGitHub(config.github, targetDir);
|
|
971
|
+
} catch (err) {
|
|
972
|
+
logger.error(getErrorMessage(err));
|
|
973
|
+
logRecoverySuggestion("github", err);
|
|
974
|
+
}
|
|
975
|
+
} else if (shouldGit) {
|
|
976
|
+
const gitAvailable = await isGitInstalled();
|
|
977
|
+
if (gitAvailable) {
|
|
978
|
+
spinner5.start("Initializing git repository");
|
|
979
|
+
try {
|
|
980
|
+
await initGitRepo(targetDir);
|
|
981
|
+
spinner5.stop("Git repository initialized");
|
|
982
|
+
} catch (err) {
|
|
983
|
+
spinner5.stop("Failed to initialize git");
|
|
984
|
+
logger.warning("Git initialization failed. Skipping git setup.");
|
|
985
|
+
logger.debug(getErrorMessage(err));
|
|
986
|
+
}
|
|
987
|
+
} else {
|
|
988
|
+
logger.warning("Git is not installed. Skipping git initialization.");
|
|
989
|
+
logger.info('Install git and run: git init && git add -A && git commit -m "Initial commit"');
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
let deployResult;
|
|
993
|
+
if (config.deployment) {
|
|
994
|
+
try {
|
|
995
|
+
if (config.deployment.platform === "vercel") {
|
|
996
|
+
deployResult = await deployToVercel(config, targetDir);
|
|
997
|
+
} else if (config.deployment.platform === "railway") {
|
|
998
|
+
deployResult = await deployToRailway(config, targetDir);
|
|
999
|
+
}
|
|
1000
|
+
} catch (err) {
|
|
1001
|
+
logger.error(getErrorMessage(err));
|
|
1002
|
+
logRecoverySuggestion("deployment", err);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
const pm = detectPackageManager();
|
|
1006
|
+
logger.blank();
|
|
1007
|
+
p5.outro("Your auth backend is ready!");
|
|
1008
|
+
logger.blank();
|
|
1009
|
+
logger.success(`Project created at ./${config.projectName}`);
|
|
1010
|
+
logger.info(
|
|
1011
|
+
`Configuration: ${config.framework}/${config.orm}-${config.database}`
|
|
1012
|
+
);
|
|
1013
|
+
if (githubResult) {
|
|
1014
|
+
logger.success(`GitHub repository: ${githubResult.repoUrl}`);
|
|
1015
|
+
}
|
|
1016
|
+
if (deployResult) {
|
|
1017
|
+
logger.success(`Deployed to ${deployResult.platform}: ${deployResult.url}`);
|
|
1018
|
+
}
|
|
1019
|
+
logger.blank();
|
|
1020
|
+
if (deployResult) {
|
|
1021
|
+
logger.box(
|
|
1022
|
+
"Next steps:",
|
|
1023
|
+
[
|
|
1024
|
+
` 1. Configure OAuth credentials in your ${deployResult.platform} dashboard`,
|
|
1025
|
+
` 2. Test auth endpoints at ${deployResult.url}/api/auth`,
|
|
1026
|
+
` 3. Integrate with your frontend`
|
|
1027
|
+
].join("\n")
|
|
1028
|
+
);
|
|
1029
|
+
} else {
|
|
1030
|
+
logger.box(
|
|
1031
|
+
"Next steps:",
|
|
1032
|
+
[
|
|
1033
|
+
` 1. cd ${config.projectName}`,
|
|
1034
|
+
` 2. cp .env.example .env`,
|
|
1035
|
+
` 3. Update DATABASE_URL in .env`,
|
|
1036
|
+
` 4. ${getRunCommand(pm, "db:push")}`,
|
|
1037
|
+
` 5. ${getRunCommand(pm, "dev")}`,
|
|
1038
|
+
"",
|
|
1039
|
+
` Your auth backend will be running at http://localhost:3000`
|
|
1040
|
+
].join("\n")
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// src/cli/index.ts
|
|
1046
|
+
var program = new Command();
|
|
1047
|
+
program.name("create-scaffauth").description("Scaffold production-ready Better Auth backends").version("0.1.0");
|
|
1048
|
+
program.command("init", { isDefault: true }).description("Initialize a new auth backend project").option("-y, --yes", "Skip prompts and use defaults").option("--no-install", "Skip package installation").option("--no-git", "Skip git initialization").option("-t, --template <template>", "Use a specific template").option("-d, --debug", "Enable debug mode").action(runInit);
|
|
1049
|
+
program.parse();
|
|
1050
|
+
//# sourceMappingURL=index.js.map
|