create-stackkit-app 0.4.2 → 0.4.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/README.md +1 -0
- package/bin/create-stackkit.js +10 -1
- package/dist/index.js +2 -1
- package/dist/lib/create-project.js +138 -412
- package/dist/lib/utils/config-utils.d.ts +2 -0
- package/dist/lib/utils/config-utils.js +33 -0
- package/dist/lib/utils/file-utils.d.ts +8 -0
- package/dist/lib/utils/file-utils.js +75 -0
- package/dist/lib/utils/git-utils.d.ts +1 -0
- package/dist/lib/utils/git-utils.js +9 -0
- package/dist/lib/utils/js-conversion.d.ts +1 -0
- package/dist/lib/utils/js-conversion.js +244 -0
- package/dist/lib/utils/module-utils.d.ts +2 -0
- package/dist/lib/utils/module-utils.js +311 -0
- package/dist/lib/utils/package-utils.d.ts +1 -0
- package/dist/lib/utils/package-utils.js +39 -0
- package/modules/auth/better-auth/files/api/auth/[...all]/route.ts +4 -0
- package/modules/auth/better-auth/files/lib/auth.ts +13 -0
- package/modules/auth/better-auth/files/schemas/prisma-schema.prisma +63 -0
- package/modules/auth/better-auth/module.json +54 -0
- package/modules/auth/{clerk-express/files/lib → clerk/files/express}/auth.ts +1 -1
- package/modules/auth/{clerk-nextjs/files/lib → clerk/files/nextjs}/auth-provider.tsx +1 -1
- package/modules/auth/clerk/files/nextjs/middleware.ts +9 -0
- package/modules/auth/{clerk-react/files/lib → clerk/files/react}/auth-provider.tsx +2 -2
- package/modules/auth/clerk/module.json +115 -0
- package/modules/database/mongoose-mongodb/files/lib/db.ts +45 -7
- package/modules/database/mongoose-mongodb/files/models/User.ts +39 -0
- package/modules/database/mongoose-mongodb/module.json +27 -12
- package/modules/database/prisma/files/lib/prisma.ts +6 -0
- package/modules/database/prisma/files/prisma/schema.prisma +8 -0
- package/modules/database/prisma/files/prisma.config.ts +12 -0
- package/modules/database/prisma/module.json +140 -0
- package/package.json +12 -3
- package/templates/express/.env.example +2 -10
- package/templates/express/package.json +13 -18
- package/templates/express/src/app.ts +21 -39
- package/templates/express/src/config/env.ts +6 -17
- package/templates/express/src/features/auth/auth.controller.ts +48 -0
- package/templates/express/src/features/auth/auth.route.ts +10 -0
- package/templates/express/src/features/auth/auth.service.ts +21 -0
- package/templates/express/src/middlewares/error.middleware.ts +5 -5
- package/templates/express/src/server.ts +3 -3
- package/templates/express/template.json +34 -1
- package/templates/express/tsconfig.json +17 -1
- package/templates/nextjs/app/layout.tsx +1 -5
- package/templates/nextjs/app/page.tsx +26 -34
- package/templates/nextjs/package.json +2 -1
- package/templates/nextjs/template.json +13 -1
- package/templates/react-vite/eslint.config.js +9 -9
- package/templates/react-vite/package.json +1 -2
- package/templates/react-vite/src/api/client.ts +16 -16
- package/templates/react-vite/src/api/services/user.service.ts +2 -10
- package/templates/react-vite/src/components/ErrorBoundary.tsx +4 -4
- package/templates/react-vite/src/components/Layout.tsx +1 -1
- package/templates/react-vite/src/components/Loading.tsx +1 -1
- package/templates/react-vite/src/components/SEO.tsx +5 -5
- package/templates/react-vite/src/config/constants.ts +3 -3
- package/templates/react-vite/src/hooks/index.ts +5 -5
- package/templates/react-vite/src/lib/queryClient.ts +2 -2
- package/templates/react-vite/src/main.tsx +12 -12
- package/templates/react-vite/src/pages/About.tsx +6 -2
- package/templates/react-vite/src/pages/Home.tsx +8 -4
- package/templates/react-vite/src/pages/NotFound.tsx +2 -2
- package/templates/react-vite/src/pages/UserProfile.tsx +6 -6
- package/templates/react-vite/src/router.tsx +13 -13
- package/templates/react-vite/src/types/{api.ts → api.d.ts} +6 -6
- package/templates/react-vite/src/types/user.d.ts +6 -0
- package/templates/react-vite/src/utils/helpers.ts +11 -11
- package/templates/react-vite/src/utils/storage.ts +4 -4
- package/templates/react-vite/template.json +26 -0
- package/templates/react-vite/tsconfig.json +1 -4
- package/templates/react-vite/vite.config.ts +5 -5
- package/dist/lib/template-composer.d.ts +0 -16
- package/dist/lib/template-composer.js +0 -197
- package/modules/auth/better-auth-express/adapters/mongoose-mongodb.ts +0 -13
- package/modules/auth/better-auth-express/adapters/prisma-mongodb.ts +0 -15
- package/modules/auth/better-auth-express/adapters/prisma-postgresql.ts +0 -15
- package/modules/auth/better-auth-express/files/lib/auth.ts +0 -16
- package/modules/auth/better-auth-express/files/routes/auth.ts +0 -12
- package/modules/auth/better-auth-express/files/schemas/prisma-mongodb-schema.prisma +0 -72
- package/modules/auth/better-auth-express/files/schemas/prisma-postgresql-schema.prisma +0 -72
- package/modules/auth/better-auth-express/module.json +0 -61
- package/modules/auth/better-auth-nextjs/adapters/mongoose-mongodb.ts +0 -24
- package/modules/auth/better-auth-nextjs/adapters/prisma-mongodb.ts +0 -26
- package/modules/auth/better-auth-nextjs/adapters/prisma-postgresql.ts +0 -26
- package/modules/auth/better-auth-nextjs/files/api/auth/[...all]/route.ts +0 -5
- package/modules/auth/better-auth-nextjs/files/lib/auth.ts +0 -26
- package/modules/auth/better-auth-nextjs/files/schemas/prisma-mongodb-schema.prisma +0 -72
- package/modules/auth/better-auth-nextjs/files/schemas/prisma-postgresql-schema.prisma +0 -72
- package/modules/auth/better-auth-nextjs/module.json +0 -62
- package/modules/auth/better-auth-react/files/lib/auth-client.ts +0 -9
- package/modules/auth/better-auth-react/module.json +0 -28
- package/modules/auth/clerk-express/module.json +0 -34
- package/modules/auth/clerk-nextjs/files/middleware.ts +0 -9
- package/modules/auth/clerk-nextjs/module.json +0 -64
- package/modules/auth/clerk-react/module.json +0 -28
- package/modules/database/prisma-mongodb/files/lib/db.ts +0 -9
- package/modules/database/prisma-mongodb/files/prisma/schema.prisma +0 -17
- package/modules/database/prisma-mongodb/module.json +0 -60
- package/modules/database/prisma-postgresql/files/lib/db.ts +0 -9
- package/modules/database/prisma-postgresql/files/prisma/schema.prisma +0 -17
- package/modules/database/prisma-postgresql/module.json +0 -60
|
@@ -11,507 +11,233 @@ const inquirer_1 = __importDefault(require("inquirer"));
|
|
|
11
11
|
const ora_1 = __importDefault(require("ora"));
|
|
12
12
|
const path_1 = __importDefault(require("path"));
|
|
13
13
|
const validate_npm_package_name_1 = __importDefault(require("validate-npm-package-name"));
|
|
14
|
+
const file_utils_1 = require("./utils/file-utils");
|
|
15
|
+
const git_utils_1 = require("./utils/git-utils");
|
|
16
|
+
const js_conversion_1 = require("./utils/js-conversion");
|
|
17
|
+
const module_utils_1 = require("./utils/module-utils");
|
|
18
|
+
const package_utils_1 = require("./utils/package-utils");
|
|
14
19
|
async function createProject(projectName) {
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
// eslint-disable-next-line no-console
|
|
21
|
+
console.log(chalk_1.default.bold.cyan("\n Create StackKit App\n"));
|
|
17
22
|
const config = await getProjectConfig(projectName);
|
|
18
|
-
// Validate target directory
|
|
19
23
|
const targetDir = path_1.default.join(process.cwd(), config.projectName);
|
|
20
24
|
if (await fs_extra_1.default.pathExists(targetDir)) {
|
|
25
|
+
// eslint-disable-next-line no-console
|
|
21
26
|
console.log(chalk_1.default.red(`\n✖ Directory "${config.projectName}" already exists`));
|
|
22
|
-
|
|
27
|
+
// eslint-disable-next-line no-console
|
|
28
|
+
console.log(chalk_1.default.gray("Please choose a different name or remove the existing directory.\n"));
|
|
23
29
|
process.exit(1);
|
|
24
30
|
}
|
|
25
|
-
// Create project
|
|
26
31
|
await generateProject(config, targetDir);
|
|
27
|
-
// Show next steps
|
|
28
32
|
showNextSteps(config);
|
|
29
33
|
}
|
|
30
34
|
async function getProjectConfig(projectName) {
|
|
31
|
-
const answers = await inquirer_1.default.prompt([
|
|
35
|
+
const answers = (await inquirer_1.default.prompt([
|
|
32
36
|
{
|
|
33
|
-
type:
|
|
34
|
-
name:
|
|
35
|
-
message:
|
|
36
|
-
default: projectName ||
|
|
37
|
+
type: "input",
|
|
38
|
+
name: "projectName",
|
|
39
|
+
message: "Project name:",
|
|
40
|
+
default: projectName || "my-app",
|
|
37
41
|
when: !projectName,
|
|
38
42
|
validate: (input) => {
|
|
39
43
|
const validation = (0, validate_npm_package_name_1.default)(input);
|
|
40
44
|
if (!validation.validForNewPackages) {
|
|
41
|
-
return validation.errors?.[0] ||
|
|
45
|
+
return validation.errors?.[0] || "Invalid package name";
|
|
42
46
|
}
|
|
43
47
|
if (fs_extra_1.default.existsSync(path_1.default.join(process.cwd(), input))) {
|
|
44
|
-
return
|
|
48
|
+
return "Directory already exists";
|
|
45
49
|
}
|
|
46
50
|
return true;
|
|
47
51
|
},
|
|
48
52
|
},
|
|
49
53
|
{
|
|
50
|
-
type:
|
|
51
|
-
name:
|
|
52
|
-
message:
|
|
54
|
+
type: "list",
|
|
55
|
+
name: "framework",
|
|
56
|
+
message: "Select framework:",
|
|
53
57
|
choices: [
|
|
54
|
-
{ name:
|
|
55
|
-
{ name:
|
|
56
|
-
{ name:
|
|
58
|
+
{ name: "Next.js", value: "nextjs" },
|
|
59
|
+
{ name: "Express.js", value: "express" },
|
|
60
|
+
{ name: "React (Vite)", value: "react-vite" },
|
|
57
61
|
],
|
|
58
62
|
},
|
|
59
63
|
{
|
|
60
|
-
type:
|
|
61
|
-
name:
|
|
62
|
-
message:
|
|
63
|
-
when: (answers) => answers.framework !==
|
|
64
|
+
type: "list",
|
|
65
|
+
name: "database",
|
|
66
|
+
message: "Select database/ORM:",
|
|
67
|
+
when: (answers) => answers.framework !== "react-vite",
|
|
64
68
|
choices: [
|
|
65
|
-
{ name:
|
|
66
|
-
{ name:
|
|
67
|
-
{ name:
|
|
68
|
-
{ name: 'Drizzle + PostgreSQL', value: 'drizzle-postgresql' },
|
|
69
|
-
{ name: 'None', value: 'none' },
|
|
69
|
+
{ name: "Prisma", value: "prisma" },
|
|
70
|
+
{ name: "Mongoose + MongoDB", value: "mongoose-mongodb" },
|
|
71
|
+
{ name: "None", value: "none" },
|
|
70
72
|
],
|
|
71
73
|
},
|
|
72
74
|
{
|
|
73
|
-
type:
|
|
74
|
-
name:
|
|
75
|
-
message:
|
|
75
|
+
type: "list",
|
|
76
|
+
name: "dbProvider",
|
|
77
|
+
message: "Select database provider for Prisma:",
|
|
78
|
+
when: (answers) => answers.database === "prisma",
|
|
79
|
+
choices: [
|
|
80
|
+
{ name: "PostgreSQL", value: "postgresql" },
|
|
81
|
+
{ name: "MongoDB", value: "mongodb" },
|
|
82
|
+
{ name: "MySQL", value: "mysql" },
|
|
83
|
+
{ name: "SQLite", value: "sqlite" },
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
type: "list",
|
|
88
|
+
name: "auth",
|
|
89
|
+
message: "Select authentication:",
|
|
90
|
+
when: (answers) => answers.database !== "none" || answers.framework === "react-vite",
|
|
76
91
|
choices: (answers) => {
|
|
77
|
-
if (answers.framework ===
|
|
92
|
+
if (answers.framework === "react-vite") {
|
|
78
93
|
return [
|
|
79
|
-
{ name:
|
|
80
|
-
{ name:
|
|
81
|
-
{ name:
|
|
94
|
+
{ name: "Better Auth", value: "better-auth" },
|
|
95
|
+
{ name: "Clerk", value: "clerk" },
|
|
96
|
+
{ name: "None", value: "none" },
|
|
82
97
|
];
|
|
83
98
|
}
|
|
84
99
|
// Next.js apps
|
|
85
|
-
if (answers.framework ===
|
|
100
|
+
if (answers.framework === "nextjs") {
|
|
86
101
|
return [
|
|
87
|
-
{ name:
|
|
88
|
-
{ name:
|
|
89
|
-
{ name:
|
|
102
|
+
{ name: "Better Auth", value: "better-auth" },
|
|
103
|
+
{ name: "Clerk", value: "clerk" },
|
|
104
|
+
{ name: "None", value: "none" },
|
|
90
105
|
];
|
|
91
106
|
}
|
|
92
107
|
// Express apps
|
|
93
|
-
if (answers.framework ===
|
|
108
|
+
if (answers.framework === "express") {
|
|
94
109
|
return [
|
|
95
|
-
{ name:
|
|
96
|
-
{ name:
|
|
97
|
-
{ name:
|
|
110
|
+
{ name: "Better Auth", value: "better-auth" },
|
|
111
|
+
{ name: "Clerk", value: "clerk" },
|
|
112
|
+
{ name: "None", value: "none" },
|
|
98
113
|
];
|
|
99
114
|
}
|
|
100
115
|
// Default - no auth
|
|
101
|
-
return [{ name:
|
|
116
|
+
return [{ name: "None", value: "none" }];
|
|
102
117
|
},
|
|
103
118
|
},
|
|
104
119
|
{
|
|
105
|
-
type:
|
|
106
|
-
name:
|
|
107
|
-
message:
|
|
120
|
+
type: "list",
|
|
121
|
+
name: "language",
|
|
122
|
+
message: "Language:",
|
|
108
123
|
choices: [
|
|
109
|
-
{ name:
|
|
110
|
-
{ name:
|
|
124
|
+
{ name: "TypeScript", value: "typescript" },
|
|
125
|
+
{ name: "JavaScript", value: "javascript" },
|
|
111
126
|
],
|
|
112
|
-
default:
|
|
127
|
+
default: "typescript",
|
|
113
128
|
},
|
|
114
129
|
{
|
|
115
|
-
type:
|
|
116
|
-
name:
|
|
117
|
-
message:
|
|
118
|
-
choices: [
|
|
119
|
-
|
|
130
|
+
type: "list",
|
|
131
|
+
name: "packageManager",
|
|
132
|
+
message: "Package manager:",
|
|
133
|
+
choices: [
|
|
134
|
+
{ name: "pnpm (recommended)", value: "pnpm" },
|
|
135
|
+
{ name: "npm", value: "npm" },
|
|
136
|
+
{ name: "yarn", value: "yarn" },
|
|
137
|
+
{ name: "bun", value: "bun" },
|
|
138
|
+
],
|
|
139
|
+
default: "pnpm",
|
|
120
140
|
},
|
|
121
|
-
]);
|
|
141
|
+
]));
|
|
122
142
|
return {
|
|
123
|
-
projectName: projectName || answers.projectName,
|
|
143
|
+
projectName: (projectName || answers.projectName),
|
|
124
144
|
framework: answers.framework,
|
|
125
|
-
database: answers.framework ===
|
|
126
|
-
|
|
145
|
+
database: (answers.framework === "react-vite"
|
|
146
|
+
? "none"
|
|
147
|
+
: answers.database),
|
|
148
|
+
dbProvider: answers.dbProvider,
|
|
149
|
+
auth: answers.auth || "none",
|
|
127
150
|
language: answers.language,
|
|
128
151
|
packageManager: answers.packageManager,
|
|
129
152
|
};
|
|
130
153
|
}
|
|
131
154
|
async function generateProject(config, targetDir) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const copySpinner = (0, ora_1.default)('Creating project files...').start();
|
|
155
|
+
const copySpinner = (0, ora_1.default)("Creating project files...").start();
|
|
156
|
+
let postInstallCommands = [];
|
|
135
157
|
try {
|
|
136
|
-
await composeTemplate(config, targetDir);
|
|
137
|
-
copySpinner.succeed(
|
|
158
|
+
postInstallCommands = await composeTemplate(config, targetDir);
|
|
159
|
+
copySpinner.succeed("Project files created");
|
|
138
160
|
}
|
|
139
161
|
catch (error) {
|
|
140
|
-
copySpinner.fail(
|
|
162
|
+
copySpinner.fail("Failed to create project files");
|
|
141
163
|
throw error;
|
|
142
164
|
}
|
|
143
165
|
// Install dependencies
|
|
144
|
-
const installSpinner = (0, ora_1.default)(
|
|
166
|
+
const installSpinner = (0, ora_1.default)("Installing dependencies...").start();
|
|
145
167
|
try {
|
|
146
|
-
await installDependencies(targetDir, config.packageManager);
|
|
147
|
-
installSpinner.succeed(
|
|
168
|
+
await (0, package_utils_1.installDependencies)(targetDir, config.packageManager);
|
|
169
|
+
installSpinner.succeed("Dependencies installed");
|
|
148
170
|
}
|
|
149
171
|
catch (error) {
|
|
150
|
-
installSpinner.fail(
|
|
172
|
+
installSpinner.fail("Failed to install dependencies");
|
|
151
173
|
throw error;
|
|
152
174
|
}
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
catch (error) {
|
|
160
|
-
gitSpinner.warn('Failed to initialize git repository');
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
async function composeTemplate(config, targetDir) {
|
|
164
|
-
const templatesDir = path_1.default.join(__dirname, '..', '..', 'templates');
|
|
165
|
-
await fs_extra_1.default.ensureDir(targetDir);
|
|
166
|
-
// 1. Copy base framework template
|
|
167
|
-
await copyBaseFramework(templatesDir, targetDir, config.framework);
|
|
168
|
-
// 2. Merge database configuration
|
|
169
|
-
if (config.database !== 'none') {
|
|
170
|
-
await mergeDatabaseConfig(templatesDir, targetDir, config.database, config.framework);
|
|
171
|
-
}
|
|
172
|
-
// 3. Merge auth configuration
|
|
173
|
-
if (config.auth !== 'none') {
|
|
174
|
-
await mergeAuthConfig(templatesDir, targetDir, config.framework, config.auth, config.database);
|
|
175
|
-
}
|
|
176
|
-
// 4. Update package.json with project name
|
|
177
|
-
const packageJsonPath = path_1.default.join(targetDir, 'package.json');
|
|
178
|
-
if (await fs_extra_1.default.pathExists(packageJsonPath)) {
|
|
179
|
-
const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
|
|
180
|
-
packageJson.name = config.projectName;
|
|
181
|
-
await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
182
|
-
}
|
|
183
|
-
// 5. Convert to JavaScript if selected
|
|
184
|
-
if (config.language === 'javascript') {
|
|
185
|
-
await convertToJavaScript(targetDir);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
async function copyBaseFramework(templatesDir, targetDir, framework) {
|
|
189
|
-
const baseDir = path_1.default.join(templatesDir, framework);
|
|
190
|
-
if (!(await fs_extra_1.default.pathExists(baseDir))) {
|
|
191
|
-
throw new Error(`Base template not found for framework: ${framework}\n` + `Expected at: ${baseDir}`);
|
|
192
|
-
}
|
|
193
|
-
await fs_extra_1.default.copy(baseDir, targetDir, {
|
|
194
|
-
filter: (src) => {
|
|
195
|
-
const basename = path_1.default.basename(src);
|
|
196
|
-
return !['template.json', 'config.json', 'node_modules', '.git'].includes(basename);
|
|
197
|
-
},
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
async function mergeDatabaseConfig(templatesDir, targetDir, database, framework) {
|
|
201
|
-
// Use modules directory (sibling to templates)
|
|
202
|
-
const modulesDir = path_1.default.join(templatesDir, '..', 'modules');
|
|
203
|
-
const dbModulePath = path_1.default.join(modulesDir, 'database', database);
|
|
204
|
-
if (!(await fs_extra_1.default.pathExists(dbModulePath))) {
|
|
205
|
-
console.warn(`Database module not found: ${database}`);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
// Read module.json
|
|
209
|
-
const moduleJsonPath = path_1.default.join(dbModulePath, 'module.json');
|
|
210
|
-
if (!(await fs_extra_1.default.pathExists(moduleJsonPath))) {
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
const moduleData = await fs_extra_1.default.readJson(moduleJsonPath);
|
|
214
|
-
// Copy files from module
|
|
215
|
-
const filesDir = path_1.default.join(dbModulePath, 'files');
|
|
216
|
-
if (await fs_extra_1.default.pathExists(filesDir)) {
|
|
217
|
-
// Copy files based on patches in module.json
|
|
218
|
-
for (const patch of moduleData.patches || []) {
|
|
219
|
-
if (patch.type === 'create-file') {
|
|
220
|
-
const sourceFile = path_1.default.join(filesDir, patch.source);
|
|
221
|
-
let destFile = path_1.default.join(targetDir, patch.destination);
|
|
222
|
-
// Simple placeholder replacement for lib
|
|
223
|
-
destFile = destFile.replace('{{lib}}', 'lib').replace('{{src}}', 'src');
|
|
224
|
-
if (await fs_extra_1.default.pathExists(sourceFile)) {
|
|
225
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(destFile));
|
|
226
|
-
await fs_extra_1.default.copy(sourceFile, destFile, { overwrite: false });
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// Merge package.json with module dependencies
|
|
232
|
-
await mergePackageJson(targetDir, {
|
|
233
|
-
dependencies: moduleData.dependencies,
|
|
234
|
-
devDependencies: moduleData.devDependencies,
|
|
235
|
-
});
|
|
236
|
-
// Merge .env with module envVars
|
|
237
|
-
const envVars = {};
|
|
238
|
-
for (const envVar of moduleData.envVars || []) {
|
|
239
|
-
envVars[envVar.key] = envVar.value;
|
|
240
|
-
}
|
|
241
|
-
await mergeEnvFile(targetDir, envVars);
|
|
242
|
-
// Apply framework-specific patches from database module
|
|
243
|
-
if (moduleData.frameworkPatches) {
|
|
244
|
-
const frameworkKey = framework === 'react-vite' ? 'react' : framework;
|
|
245
|
-
const patches = moduleData.frameworkPatches[frameworkKey];
|
|
246
|
-
if (patches) {
|
|
247
|
-
await applyFrameworkPatches(targetDir, patches);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
async function mergeAuthConfig(templatesDir, targetDir, framework, auth, database = 'none') {
|
|
252
|
-
// Use modules directory (sibling to templates)
|
|
253
|
-
const modulesDir = path_1.default.join(templatesDir, '..', 'modules');
|
|
254
|
-
// Auth modules are now named with framework suffix
|
|
255
|
-
// e.g., better-auth-nextjs, authjs-express, better-auth-react
|
|
256
|
-
// If auth already has framework suffix, use it directly
|
|
257
|
-
// Otherwise, map old names to new ones
|
|
258
|
-
const authMap = {
|
|
259
|
-
nextauth: 'nextauth',
|
|
260
|
-
'better-auth': framework === 'nextjs' ? 'better-auth-nextjs' : 'better-auth-express',
|
|
261
|
-
clerk: framework === 'nextjs'
|
|
262
|
-
? 'clerk-nextjs'
|
|
263
|
-
: framework === 'react-vite'
|
|
264
|
-
? 'clerk-react'
|
|
265
|
-
: 'clerk-express',
|
|
266
|
-
};
|
|
267
|
-
const authKey = auth.includes('-') ? auth : authMap[auth] || auth;
|
|
268
|
-
const authModulePath = path_1.default.join(modulesDir, 'auth', authKey);
|
|
269
|
-
if (!(await fs_extra_1.default.pathExists(authModulePath))) {
|
|
270
|
-
console.warn(`Auth module not found: ${authKey}`);
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
// Read module.json
|
|
274
|
-
const moduleJsonPath = path_1.default.join(authModulePath, 'module.json');
|
|
275
|
-
if (!(await fs_extra_1.default.pathExists(moduleJsonPath))) {
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
const moduleData = await fs_extra_1.default.readJson(moduleJsonPath);
|
|
279
|
-
// Copy files from module
|
|
280
|
-
const filesDir = path_1.default.join(authModulePath, 'files');
|
|
281
|
-
if (await fs_extra_1.default.pathExists(filesDir)) {
|
|
282
|
-
// Determine path replacements based on framework
|
|
283
|
-
const getReplacements = () => {
|
|
284
|
-
if (framework === 'nextjs') {
|
|
285
|
-
return { lib: 'lib', router: 'app' };
|
|
286
|
-
}
|
|
287
|
-
else if (framework === 'express') {
|
|
288
|
-
return { lib: 'src', router: 'src' };
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
return { lib: 'src', router: 'src' };
|
|
292
|
-
}
|
|
293
|
-
};
|
|
294
|
-
const replacements = getReplacements();
|
|
295
|
-
// Copy files based on patches in module.json
|
|
296
|
-
for (const patch of moduleData.patches || []) {
|
|
297
|
-
if (patch.type === 'create-file') {
|
|
298
|
-
const sourceFile = path_1.default.join(filesDir, patch.source);
|
|
299
|
-
let destFile = path_1.default.join(targetDir, patch.destination);
|
|
300
|
-
// Replace placeholders
|
|
301
|
-
destFile = destFile
|
|
302
|
-
.replace('{{lib}}', replacements.lib)
|
|
303
|
-
.replace('{{router}}', replacements.router);
|
|
304
|
-
if (await fs_extra_1.default.pathExists(sourceFile)) {
|
|
305
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(destFile));
|
|
306
|
-
await fs_extra_1.default.copy(sourceFile, destFile, { overwrite: false });
|
|
307
|
-
}
|
|
175
|
+
// Run post-install commands
|
|
176
|
+
if (postInstallCommands.length > 0) {
|
|
177
|
+
const postInstallSpinner = (0, ora_1.default)("Running post-install commands...").start();
|
|
178
|
+
try {
|
|
179
|
+
for (const command of postInstallCommands) {
|
|
180
|
+
(0, child_process_1.execSync)(command, { cwd: targetDir, stdio: "pipe" });
|
|
308
181
|
}
|
|
182
|
+
postInstallSpinner.succeed("Post-install commands completed");
|
|
309
183
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const adapterConfig = moduleData.databaseAdapters[database];
|
|
314
|
-
if (adapterConfig) {
|
|
315
|
-
// Copy adapter file
|
|
316
|
-
if (adapterConfig.adapter) {
|
|
317
|
-
const adapterSource = path_1.default.join(authModulePath, adapterConfig.adapter);
|
|
318
|
-
const adapterFileName = path_1.default.basename(adapterConfig.adapter);
|
|
319
|
-
// Determine destination based on framework
|
|
320
|
-
let adapterDest;
|
|
321
|
-
if (framework === 'nextjs') {
|
|
322
|
-
adapterDest = path_1.default.join(targetDir, 'lib', 'auth.ts');
|
|
323
|
-
}
|
|
324
|
-
else if (framework === 'express') {
|
|
325
|
-
adapterDest = path_1.default.join(targetDir, 'src', 'auth.ts');
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
adapterDest = path_1.default.join(targetDir, 'src', 'lib', 'auth.ts');
|
|
329
|
-
}
|
|
330
|
-
if (await fs_extra_1.default.pathExists(adapterSource)) {
|
|
331
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(adapterDest));
|
|
332
|
-
await fs_extra_1.default.copy(adapterSource, adapterDest, { overwrite: true });
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
// Copy schema file if it exists
|
|
336
|
-
if (adapterConfig.schema && adapterConfig.schemaDestination) {
|
|
337
|
-
const schemaSource = path_1.default.join(authModulePath, adapterConfig.schema);
|
|
338
|
-
const schemaDest = path_1.default.join(targetDir, adapterConfig.schemaDestination);
|
|
339
|
-
if (await fs_extra_1.default.pathExists(schemaSource)) {
|
|
340
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(schemaDest));
|
|
341
|
-
await fs_extra_1.default.copy(schemaSource, schemaDest, { overwrite: true });
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
// Merge adapter-specific dependencies
|
|
345
|
-
if (adapterConfig.dependencies) {
|
|
346
|
-
await mergePackageJson(targetDir, {
|
|
347
|
-
dependencies: adapterConfig.dependencies,
|
|
348
|
-
});
|
|
349
|
-
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
postInstallSpinner.fail("Failed to run post-install commands");
|
|
186
|
+
throw error;
|
|
350
187
|
}
|
|
351
188
|
}
|
|
352
|
-
//
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
// Merge .env with module envVars
|
|
358
|
-
const envVars = {};
|
|
359
|
-
for (const envVar of moduleData.envVars || []) {
|
|
360
|
-
envVars[envVar.key] = envVar.value;
|
|
361
|
-
}
|
|
362
|
-
await mergeEnvFile(targetDir, envVars);
|
|
363
|
-
}
|
|
364
|
-
async function mergePackageJson(targetDir, config) {
|
|
365
|
-
const pkgPath = path_1.default.join(targetDir, 'package.json');
|
|
366
|
-
if (!(await fs_extra_1.default.pathExists(pkgPath))) {
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
const pkg = await fs_extra_1.default.readJson(pkgPath);
|
|
370
|
-
if (config.dependencies) {
|
|
371
|
-
pkg.dependencies = { ...pkg.dependencies, ...config.dependencies };
|
|
372
|
-
}
|
|
373
|
-
if (config.devDependencies) {
|
|
374
|
-
pkg.devDependencies = { ...pkg.devDependencies, ...config.devDependencies };
|
|
189
|
+
// Initialize git
|
|
190
|
+
const gitSpinner = (0, ora_1.default)("Initializing git repository...").start();
|
|
191
|
+
try {
|
|
192
|
+
await (0, git_utils_1.initGit)(targetDir);
|
|
193
|
+
gitSpinner.succeed("Git repository initialized");
|
|
375
194
|
}
|
|
376
|
-
|
|
377
|
-
|
|
195
|
+
catch {
|
|
196
|
+
gitSpinner.warn("Failed to initialize git repository");
|
|
378
197
|
}
|
|
379
|
-
await fs_extra_1.default.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
380
198
|
}
|
|
381
|
-
async function
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
.
|
|
389
|
-
.
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const existing = await fs_extra_1.default.readFile(envExamplePath, 'utf-8');
|
|
393
|
-
const existingKeys = existing.split('\n').map((line) => line.split('=')[0]);
|
|
394
|
-
const newVars = Object.keys(envVars).filter((key) => !existingKeys.includes(key));
|
|
395
|
-
if (newVars.length > 0) {
|
|
396
|
-
const newContent = newVars.map((key) => `${key}="${envVars[key]}"`).join('\n');
|
|
397
|
-
await fs_extra_1.default.appendFile(envExamplePath, '\n' + newContent + '\n');
|
|
199
|
+
async function composeTemplate(config, targetDir) {
|
|
200
|
+
const templatesDir = path_1.default.join(__dirname, "..", "..", "templates");
|
|
201
|
+
await fs_extra_1.default.ensureDir(targetDir);
|
|
202
|
+
await (0, file_utils_1.copyBaseFramework)(templatesDir, targetDir, config.framework);
|
|
203
|
+
// Ensure .env exists: if .env.example was copied from the template, create .env from it
|
|
204
|
+
try {
|
|
205
|
+
const envExamplePath = path_1.default.join(targetDir, ".env.example");
|
|
206
|
+
const envPath = path_1.default.join(targetDir, ".env");
|
|
207
|
+
if ((await fs_extra_1.default.pathExists(envExamplePath)) && !(await fs_extra_1.default.pathExists(envPath))) {
|
|
208
|
+
const envContent = await fs_extra_1.default.readFile(envExamplePath, "utf-8");
|
|
209
|
+
await fs_extra_1.default.writeFile(envPath, envContent);
|
|
398
210
|
}
|
|
399
211
|
}
|
|
400
|
-
|
|
401
|
-
|
|
212
|
+
catch {
|
|
213
|
+
// non-fatal
|
|
402
214
|
}
|
|
403
|
-
|
|
404
|
-
if (
|
|
405
|
-
await
|
|
215
|
+
const postInstallCommands = [];
|
|
216
|
+
if (config.database !== "none") {
|
|
217
|
+
const dbPostInstall = await (0, module_utils_1.mergeDatabaseConfig)(templatesDir, targetDir, config.database, config.framework, config.dbProvider);
|
|
218
|
+
postInstallCommands.push(...dbPostInstall);
|
|
406
219
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
// Remove TypeScript config files
|
|
410
|
-
const tsFiles = ['tsconfig.json', 'next-env.d.ts'];
|
|
411
|
-
for (const file of tsFiles) {
|
|
412
|
-
const filePath = path_1.default.join(targetDir, file);
|
|
413
|
-
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
414
|
-
await fs_extra_1.default.remove(filePath);
|
|
415
|
-
}
|
|
220
|
+
if (config.auth !== "none") {
|
|
221
|
+
await (0, module_utils_1.mergeAuthConfig)(templatesDir, targetDir, config.framework, config.auth, config.database, config.dbProvider);
|
|
416
222
|
}
|
|
417
|
-
|
|
418
|
-
const renameExtensions = async (dir) => {
|
|
419
|
-
const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
|
|
420
|
-
for (const entry of entries) {
|
|
421
|
-
const fullPath = path_1.default.join(dir, entry.name);
|
|
422
|
-
if (entry.isDirectory() && entry.name !== 'node_modules') {
|
|
423
|
-
await renameExtensions(fullPath);
|
|
424
|
-
}
|
|
425
|
-
else if (entry.isFile()) {
|
|
426
|
-
if (entry.name.endsWith('.ts')) {
|
|
427
|
-
await fs_extra_1.default.rename(fullPath, fullPath.replace(/\.ts$/, '.js'));
|
|
428
|
-
}
|
|
429
|
-
else if (entry.name.endsWith('.tsx')) {
|
|
430
|
-
await fs_extra_1.default.rename(fullPath, fullPath.replace(/\.tsx$/, '.jsx'));
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
};
|
|
435
|
-
await renameExtensions(targetDir);
|
|
436
|
-
// Update package.json to remove TypeScript dependencies
|
|
437
|
-
const packageJsonPath = path_1.default.join(targetDir, 'package.json');
|
|
223
|
+
const packageJsonPath = path_1.default.join(targetDir, "package.json");
|
|
438
224
|
if (await fs_extra_1.default.pathExists(packageJsonPath)) {
|
|
439
225
|
const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
|
|
440
|
-
|
|
441
|
-
delete packageJson.devDependencies['typescript'];
|
|
442
|
-
delete packageJson.devDependencies['@types/node'];
|
|
443
|
-
delete packageJson.devDependencies['@types/react'];
|
|
444
|
-
delete packageJson.devDependencies['@types/react-dom'];
|
|
445
|
-
}
|
|
226
|
+
packageJson.name = config.projectName;
|
|
446
227
|
await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
447
228
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const commands = {
|
|
451
|
-
npm: 'npm install',
|
|
452
|
-
yarn: 'yarn install',
|
|
453
|
-
pnpm: 'pnpm install',
|
|
454
|
-
};
|
|
455
|
-
const command = commands[packageManager];
|
|
456
|
-
if (!command) {
|
|
457
|
-
throw new Error(`Unsupported package manager: ${packageManager}`);
|
|
458
|
-
}
|
|
459
|
-
(0, child_process_1.execSync)(command, {
|
|
460
|
-
cwd,
|
|
461
|
-
stdio: 'pipe',
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
async function initGit(cwd) {
|
|
465
|
-
try {
|
|
466
|
-
(0, child_process_1.execSync)('git --version', { stdio: 'pipe' });
|
|
467
|
-
(0, child_process_1.execSync)('git init', { cwd, stdio: 'pipe' });
|
|
468
|
-
(0, child_process_1.execSync)('git add -A', { cwd, stdio: 'pipe' });
|
|
469
|
-
(0, child_process_1.execSync)('git commit -m "Initial commit from create-stackkit-app"', {
|
|
470
|
-
cwd,
|
|
471
|
-
stdio: 'pipe',
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
catch (error) {
|
|
475
|
-
throw new Error('Git initialization failed');
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
async function applyFrameworkPatches(targetDir, patches) {
|
|
479
|
-
for (const [filename, patchConfig] of Object.entries(patches)) {
|
|
480
|
-
const filePath = path_1.default.join(targetDir, filename);
|
|
481
|
-
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
482
|
-
const fileContent = await fs_extra_1.default.readJson(filePath);
|
|
483
|
-
if (patchConfig.merge) {
|
|
484
|
-
// Deep merge configuration
|
|
485
|
-
const merged = deepMerge(fileContent, patchConfig.merge);
|
|
486
|
-
await fs_extra_1.default.writeJson(filePath, merged, { spaces: 2 });
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
function deepMerge(target, source) {
|
|
492
|
-
const output = { ...target };
|
|
493
|
-
for (const key in source) {
|
|
494
|
-
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
495
|
-
if (target[key]) {
|
|
496
|
-
output[key] = deepMerge(target[key], source[key]);
|
|
497
|
-
}
|
|
498
|
-
else {
|
|
499
|
-
output[key] = source[key];
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
else if (Array.isArray(source[key])) {
|
|
503
|
-
// For arrays, merge uniquely
|
|
504
|
-
output[key] = Array.from(new Set([...(target[key] || []), ...source[key]]));
|
|
505
|
-
}
|
|
506
|
-
else {
|
|
507
|
-
output[key] = source[key];
|
|
508
|
-
}
|
|
229
|
+
if (config.language === "javascript") {
|
|
230
|
+
await (0, js_conversion_1.convertToJavaScript)(targetDir, config.framework);
|
|
509
231
|
}
|
|
510
|
-
return
|
|
232
|
+
return postInstallCommands;
|
|
511
233
|
}
|
|
512
234
|
function showNextSteps(config) {
|
|
235
|
+
// eslint-disable-next-line no-console
|
|
513
236
|
console.log(chalk_1.default.green.bold(`\n✓ Created ${config.projectName}\n`));
|
|
514
|
-
console
|
|
237
|
+
// eslint-disable-next-line no-console
|
|
238
|
+
console.log(chalk_1.default.bold("Next steps:"));
|
|
239
|
+
// eslint-disable-next-line no-console
|
|
515
240
|
console.log(chalk_1.default.cyan(` cd ${config.projectName}`));
|
|
516
|
-
|
|
241
|
+
// eslint-disable-next-line no-console
|
|
242
|
+
console.log(chalk_1.default.cyan(` ${config.packageManager} run dev\n`));
|
|
517
243
|
}
|