create-stackkit-app 0.4.3 → 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/bin/create-stackkit.js +10 -1
- package/dist/index.js +1 -0
- package/dist/lib/create-project.js +79 -539
- 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/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/module.json +115 -0
- package/modules/database/mongoose-mongodb/files/lib/db.ts +44 -6
- 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 +7 -3
- package/templates/express/.env.example +2 -10
- package/templates/express/package.json +12 -18
- package/templates/express/src/app.ts +9 -29
- package/templates/express/src/config/env.ts +3 -14
- 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 +2 -2
- package/templates/express/src/server.ts +1 -1
- package/templates/express/template.json +1 -5
- package/templates/express/tsconfig.json +0 -1
- 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/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/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
- /package/modules/auth/{better-auth-nextjs → better-auth}/files/api/auth/[...all]/route.ts +0 -0
- /package/modules/auth/{clerk-express/files/lib → clerk/files/express}/auth.ts +0 -0
- /package/modules/auth/{clerk-nextjs/files/lib → clerk/files/nextjs}/auth-provider.tsx +0 -0
- /package/modules/auth/{clerk-nextjs/files → clerk/files/nextjs}/middleware.ts +0 -0
- /package/modules/auth/{clerk-react/files/lib → clerk/files/react}/auth-provider.tsx +0 -0
|
@@ -11,24 +11,28 @@ 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) {
|
|
20
|
+
// eslint-disable-next-line no-console
|
|
15
21
|
console.log(chalk_1.default.bold.cyan("\n Create StackKit App\n"));
|
|
16
|
-
// Get project configuration through wizard
|
|
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`));
|
|
27
|
+
// eslint-disable-next-line no-console
|
|
22
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
37
|
type: "input",
|
|
34
38
|
name: "projectName",
|
|
@@ -62,37 +66,49 @@ async function getProjectConfig(projectName) {
|
|
|
62
66
|
message: "Select database/ORM:",
|
|
63
67
|
when: (answers) => answers.framework !== "react-vite",
|
|
64
68
|
choices: [
|
|
65
|
-
{ name: "Prisma
|
|
66
|
-
{ name: "Prisma + MongoDB", value: "prisma-mongodb" },
|
|
69
|
+
{ name: "Prisma", value: "prisma" },
|
|
67
70
|
{ name: "Mongoose + MongoDB", value: "mongoose-mongodb" },
|
|
68
71
|
{ name: "None", value: "none" },
|
|
69
72
|
],
|
|
70
73
|
},
|
|
74
|
+
{
|
|
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
|
+
},
|
|
71
86
|
{
|
|
72
87
|
type: "list",
|
|
73
88
|
name: "auth",
|
|
74
89
|
message: "Select authentication:",
|
|
90
|
+
when: (answers) => answers.database !== "none" || answers.framework === "react-vite",
|
|
75
91
|
choices: (answers) => {
|
|
76
92
|
if (answers.framework === "react-vite") {
|
|
77
93
|
return [
|
|
78
|
-
{ name: "Better Auth", value: "better-auth
|
|
79
|
-
{ name: "Clerk", value: "clerk
|
|
94
|
+
{ name: "Better Auth", value: "better-auth" },
|
|
95
|
+
{ name: "Clerk", value: "clerk" },
|
|
80
96
|
{ name: "None", value: "none" },
|
|
81
97
|
];
|
|
82
98
|
}
|
|
83
99
|
// Next.js apps
|
|
84
100
|
if (answers.framework === "nextjs") {
|
|
85
101
|
return [
|
|
86
|
-
{ name: "Better Auth", value: "better-auth
|
|
87
|
-
{ name: "Clerk", value: "clerk
|
|
102
|
+
{ name: "Better Auth", value: "better-auth" },
|
|
103
|
+
{ name: "Clerk", value: "clerk" },
|
|
88
104
|
{ name: "None", value: "none" },
|
|
89
105
|
];
|
|
90
106
|
}
|
|
91
107
|
// Express apps
|
|
92
108
|
if (answers.framework === "express") {
|
|
93
109
|
return [
|
|
94
|
-
{ name: "Better Auth", value: "better-auth
|
|
95
|
-
{ name: "Clerk", value: "clerk
|
|
110
|
+
{ name: "Better Auth", value: "better-auth" },
|
|
111
|
+
{ name: "Clerk", value: "clerk" },
|
|
96
112
|
{ name: "None", value: "none" },
|
|
97
113
|
];
|
|
98
114
|
}
|
|
@@ -122,22 +138,24 @@ async function getProjectConfig(projectName) {
|
|
|
122
138
|
],
|
|
123
139
|
default: "pnpm",
|
|
124
140
|
},
|
|
125
|
-
]);
|
|
141
|
+
]));
|
|
126
142
|
return {
|
|
127
|
-
projectName: projectName || answers.projectName,
|
|
143
|
+
projectName: (projectName || answers.projectName),
|
|
128
144
|
framework: answers.framework,
|
|
129
|
-
database: answers.framework === "react-vite"
|
|
130
|
-
|
|
145
|
+
database: (answers.framework === "react-vite"
|
|
146
|
+
? "none"
|
|
147
|
+
: answers.database),
|
|
148
|
+
dbProvider: answers.dbProvider,
|
|
149
|
+
auth: answers.auth || "none",
|
|
131
150
|
language: answers.language,
|
|
132
151
|
packageManager: answers.packageManager,
|
|
133
152
|
};
|
|
134
153
|
}
|
|
135
154
|
async function generateProject(config, targetDir) {
|
|
136
|
-
console.log();
|
|
137
|
-
// Copy and compose template
|
|
138
155
|
const copySpinner = (0, ora_1.default)("Creating project files...").start();
|
|
156
|
+
let postInstallCommands = [];
|
|
139
157
|
try {
|
|
140
|
-
await composeTemplate(config, targetDir);
|
|
158
|
+
postInstallCommands = await composeTemplate(config, targetDir);
|
|
141
159
|
copySpinner.succeed("Project files created");
|
|
142
160
|
}
|
|
143
161
|
catch (error) {
|
|
@@ -147,557 +165,79 @@ async function generateProject(config, targetDir) {
|
|
|
147
165
|
// Install dependencies
|
|
148
166
|
const installSpinner = (0, ora_1.default)("Installing dependencies...").start();
|
|
149
167
|
try {
|
|
150
|
-
await installDependencies(targetDir, config.packageManager);
|
|
168
|
+
await (0, package_utils_1.installDependencies)(targetDir, config.packageManager);
|
|
151
169
|
installSpinner.succeed("Dependencies installed");
|
|
152
170
|
}
|
|
153
171
|
catch (error) {
|
|
154
172
|
installSpinner.fail("Failed to install dependencies");
|
|
155
173
|
throw error;
|
|
156
174
|
}
|
|
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" });
|
|
181
|
+
}
|
|
182
|
+
postInstallSpinner.succeed("Post-install commands completed");
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
postInstallSpinner.fail("Failed to run post-install commands");
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
157
189
|
// Initialize git
|
|
158
190
|
const gitSpinner = (0, ora_1.default)("Initializing git repository...").start();
|
|
159
191
|
try {
|
|
160
|
-
await initGit(targetDir);
|
|
192
|
+
await (0, git_utils_1.initGit)(targetDir);
|
|
161
193
|
gitSpinner.succeed("Git repository initialized");
|
|
162
194
|
}
|
|
163
|
-
catch
|
|
195
|
+
catch {
|
|
164
196
|
gitSpinner.warn("Failed to initialize git repository");
|
|
165
197
|
}
|
|
166
198
|
}
|
|
167
199
|
async function composeTemplate(config, targetDir) {
|
|
168
200
|
const templatesDir = path_1.default.join(__dirname, "..", "..", "templates");
|
|
169
201
|
await fs_extra_1.default.ensureDir(targetDir);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// non-fatal
|
|
214
|
+
}
|
|
215
|
+
const postInstallCommands = [];
|
|
173
216
|
if (config.database !== "none") {
|
|
174
|
-
await mergeDatabaseConfig(templatesDir, targetDir, config.database, config.framework);
|
|
217
|
+
const dbPostInstall = await (0, module_utils_1.mergeDatabaseConfig)(templatesDir, targetDir, config.database, config.framework, config.dbProvider);
|
|
218
|
+
postInstallCommands.push(...dbPostInstall);
|
|
175
219
|
}
|
|
176
|
-
// 3. Merge auth configuration
|
|
177
220
|
if (config.auth !== "none") {
|
|
178
|
-
await mergeAuthConfig(templatesDir, targetDir, config.framework, config.auth, config.database);
|
|
221
|
+
await (0, module_utils_1.mergeAuthConfig)(templatesDir, targetDir, config.framework, config.auth, config.database, config.dbProvider);
|
|
179
222
|
}
|
|
180
|
-
// 4. Update package.json with project name
|
|
181
223
|
const packageJsonPath = path_1.default.join(targetDir, "package.json");
|
|
182
224
|
if (await fs_extra_1.default.pathExists(packageJsonPath)) {
|
|
183
225
|
const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
|
|
184
226
|
packageJson.name = config.projectName;
|
|
185
227
|
await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
186
228
|
}
|
|
187
|
-
// 5. Convert to JavaScript if selected
|
|
188
229
|
if (config.language === "javascript") {
|
|
189
|
-
await convertToJavaScript(targetDir, config.framework);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
async function copyBaseFramework(templatesDir, targetDir, framework) {
|
|
193
|
-
const baseDir = path_1.default.join(templatesDir, framework);
|
|
194
|
-
if (!(await fs_extra_1.default.pathExists(baseDir))) {
|
|
195
|
-
throw new Error(`Base template not found for framework: ${framework}\n` + `Expected at: ${baseDir}`);
|
|
196
|
-
}
|
|
197
|
-
await fs_extra_1.default.copy(baseDir, targetDir, {
|
|
198
|
-
filter: (src) => {
|
|
199
|
-
const basename = path_1.default.basename(src);
|
|
200
|
-
return !["template.json", "config.json", "node_modules", ".git"].includes(basename);
|
|
201
|
-
},
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
async function mergeDatabaseConfig(templatesDir, targetDir, database, framework) {
|
|
205
|
-
// Use modules directory (sibling to templates)
|
|
206
|
-
const modulesDir = path_1.default.join(templatesDir, "..", "modules");
|
|
207
|
-
const dbModulePath = path_1.default.join(modulesDir, "database", database);
|
|
208
|
-
if (!(await fs_extra_1.default.pathExists(dbModulePath))) {
|
|
209
|
-
console.warn(`Database module not found: ${database}`);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
// Read module.json
|
|
213
|
-
const moduleJsonPath = path_1.default.join(dbModulePath, "module.json");
|
|
214
|
-
if (!(await fs_extra_1.default.pathExists(moduleJsonPath))) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
const moduleData = await fs_extra_1.default.readJson(moduleJsonPath);
|
|
218
|
-
// Copy files from module
|
|
219
|
-
const filesDir = path_1.default.join(dbModulePath, "files");
|
|
220
|
-
if (await fs_extra_1.default.pathExists(filesDir)) {
|
|
221
|
-
// Copy files based on patches in module.json
|
|
222
|
-
for (const patch of moduleData.patches || []) {
|
|
223
|
-
if (patch.type === "create-file") {
|
|
224
|
-
const sourceFile = path_1.default.join(filesDir, patch.source);
|
|
225
|
-
let destFile = path_1.default.join(targetDir, patch.destination);
|
|
226
|
-
// Simple placeholder replacement for lib
|
|
227
|
-
destFile = destFile.replace("{{lib}}", "lib").replace("{{src}}", "src");
|
|
228
|
-
if (await fs_extra_1.default.pathExists(sourceFile)) {
|
|
229
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(destFile));
|
|
230
|
-
await fs_extra_1.default.copy(sourceFile, destFile, { overwrite: false });
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
// Merge package.json with module dependencies
|
|
236
|
-
await mergePackageJson(targetDir, {
|
|
237
|
-
dependencies: moduleData.dependencies,
|
|
238
|
-
devDependencies: moduleData.devDependencies,
|
|
239
|
-
});
|
|
240
|
-
// Merge .env with module envVars
|
|
241
|
-
const envVars = {};
|
|
242
|
-
for (const envVar of moduleData.envVars || []) {
|
|
243
|
-
envVars[envVar.key] = envVar.value;
|
|
244
|
-
}
|
|
245
|
-
await mergeEnvFile(targetDir, envVars);
|
|
246
|
-
// Apply framework-specific patches from database module
|
|
247
|
-
if (moduleData.frameworkPatches) {
|
|
248
|
-
const frameworkKey = framework === "react-vite" ? "react" : framework;
|
|
249
|
-
const patches = moduleData.frameworkPatches[frameworkKey];
|
|
250
|
-
if (patches) {
|
|
251
|
-
await applyFrameworkPatches(targetDir, patches);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
async function mergeAuthConfig(templatesDir, targetDir, framework, auth, database = "none") {
|
|
256
|
-
// Use modules directory (sibling to templates)
|
|
257
|
-
const modulesDir = path_1.default.join(templatesDir, "..", "modules");
|
|
258
|
-
// Auth modules are now named with framework suffix
|
|
259
|
-
// e.g., better-auth-nextjs, authjs-express, better-auth-react
|
|
260
|
-
// If auth already has framework suffix, use it directly
|
|
261
|
-
// Otherwise, map old names to new ones
|
|
262
|
-
const authMap = {
|
|
263
|
-
nextauth: "nextauth",
|
|
264
|
-
"better-auth": framework === "nextjs" ? "better-auth-nextjs" : "better-auth-express",
|
|
265
|
-
clerk: framework === "nextjs"
|
|
266
|
-
? "clerk-nextjs"
|
|
267
|
-
: framework === "react-vite"
|
|
268
|
-
? "clerk-react"
|
|
269
|
-
: "clerk-express",
|
|
270
|
-
};
|
|
271
|
-
const authKey = auth.includes("-") ? auth : authMap[auth] || auth;
|
|
272
|
-
const authModulePath = path_1.default.join(modulesDir, "auth", authKey);
|
|
273
|
-
if (!(await fs_extra_1.default.pathExists(authModulePath))) {
|
|
274
|
-
console.warn(`Auth module not found: ${authKey}`);
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
// Read module.json
|
|
278
|
-
const moduleJsonPath = path_1.default.join(authModulePath, "module.json");
|
|
279
|
-
if (!(await fs_extra_1.default.pathExists(moduleJsonPath))) {
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
const moduleData = await fs_extra_1.default.readJson(moduleJsonPath);
|
|
283
|
-
// Copy files from module
|
|
284
|
-
const filesDir = path_1.default.join(authModulePath, "files");
|
|
285
|
-
if (await fs_extra_1.default.pathExists(filesDir)) {
|
|
286
|
-
// Determine path replacements based on framework
|
|
287
|
-
const getReplacements = () => {
|
|
288
|
-
if (framework === "nextjs") {
|
|
289
|
-
return { lib: "lib", router: "app" };
|
|
290
|
-
}
|
|
291
|
-
else if (framework === "express") {
|
|
292
|
-
return { lib: "src", router: "src" };
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
return { lib: "src", router: "src" };
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
const replacements = getReplacements();
|
|
299
|
-
// Copy files based on patches in module.json
|
|
300
|
-
for (const patch of moduleData.patches || []) {
|
|
301
|
-
if (patch.type === "create-file") {
|
|
302
|
-
const sourceFile = path_1.default.join(filesDir, patch.source);
|
|
303
|
-
let destFile = path_1.default.join(targetDir, patch.destination);
|
|
304
|
-
// Replace placeholders
|
|
305
|
-
destFile = destFile
|
|
306
|
-
.replace("{{lib}}", replacements.lib)
|
|
307
|
-
.replace("{{router}}", replacements.router);
|
|
308
|
-
if (await fs_extra_1.default.pathExists(sourceFile)) {
|
|
309
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(destFile));
|
|
310
|
-
await fs_extra_1.default.copy(sourceFile, destFile, { overwrite: false });
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
// Handle database-specific adapters and schemas
|
|
316
|
-
if (database !== "none" && moduleData.databaseAdapters) {
|
|
317
|
-
const adapterConfig = moduleData.databaseAdapters[database];
|
|
318
|
-
if (adapterConfig) {
|
|
319
|
-
// Copy adapter file
|
|
320
|
-
if (adapterConfig.adapter) {
|
|
321
|
-
const adapterSource = path_1.default.join(authModulePath, adapterConfig.adapter);
|
|
322
|
-
const adapterFileName = path_1.default.basename(adapterConfig.adapter);
|
|
323
|
-
// Determine destination based on framework
|
|
324
|
-
let adapterDest;
|
|
325
|
-
if (framework === "nextjs") {
|
|
326
|
-
adapterDest = path_1.default.join(targetDir, "lib", "auth.ts");
|
|
327
|
-
}
|
|
328
|
-
else if (framework === "express") {
|
|
329
|
-
adapterDest = path_1.default.join(targetDir, "src", "auth.ts");
|
|
330
|
-
}
|
|
331
|
-
else {
|
|
332
|
-
adapterDest = path_1.default.join(targetDir, "src", "lib", "auth.ts");
|
|
333
|
-
}
|
|
334
|
-
if (await fs_extra_1.default.pathExists(adapterSource)) {
|
|
335
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(adapterDest));
|
|
336
|
-
await fs_extra_1.default.copy(adapterSource, adapterDest, { overwrite: true });
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
// Copy schema file if it exists
|
|
340
|
-
if (adapterConfig.schema && adapterConfig.schemaDestination) {
|
|
341
|
-
const schemaSource = path_1.default.join(authModulePath, adapterConfig.schema);
|
|
342
|
-
const schemaDest = path_1.default.join(targetDir, adapterConfig.schemaDestination);
|
|
343
|
-
if (await fs_extra_1.default.pathExists(schemaSource)) {
|
|
344
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(schemaDest));
|
|
345
|
-
await fs_extra_1.default.copy(schemaSource, schemaDest, { overwrite: true });
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
// Merge adapter-specific dependencies
|
|
349
|
-
if (adapterConfig.dependencies) {
|
|
350
|
-
await mergePackageJson(targetDir, {
|
|
351
|
-
dependencies: adapterConfig.dependencies,
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
// Merge package.json with module dependencies
|
|
357
|
-
await mergePackageJson(targetDir, {
|
|
358
|
-
dependencies: moduleData.dependencies,
|
|
359
|
-
devDependencies: moduleData.devDependencies,
|
|
360
|
-
});
|
|
361
|
-
// Merge .env with module envVars
|
|
362
|
-
const envVars = {};
|
|
363
|
-
for (const envVar of moduleData.envVars || []) {
|
|
364
|
-
envVars[envVar.key] = envVar.value;
|
|
365
|
-
}
|
|
366
|
-
await mergeEnvFile(targetDir, envVars);
|
|
367
|
-
}
|
|
368
|
-
async function mergePackageJson(targetDir, config) {
|
|
369
|
-
const pkgPath = path_1.default.join(targetDir, "package.json");
|
|
370
|
-
if (!(await fs_extra_1.default.pathExists(pkgPath))) {
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
const pkg = await fs_extra_1.default.readJson(pkgPath);
|
|
374
|
-
if (config.dependencies) {
|
|
375
|
-
pkg.dependencies = { ...pkg.dependencies, ...config.dependencies };
|
|
376
|
-
}
|
|
377
|
-
if (config.devDependencies) {
|
|
378
|
-
pkg.devDependencies = { ...pkg.devDependencies, ...config.devDependencies };
|
|
379
|
-
}
|
|
380
|
-
if (config.scripts) {
|
|
381
|
-
pkg.scripts = { ...pkg.scripts, ...config.scripts };
|
|
382
|
-
}
|
|
383
|
-
await fs_extra_1.default.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
384
|
-
}
|
|
385
|
-
async function mergeEnvFile(targetDir, envVars) {
|
|
386
|
-
if (Object.keys(envVars).length === 0) {
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
const envExamplePath = path_1.default.join(targetDir, ".env.example");
|
|
390
|
-
const envPath = path_1.default.join(targetDir, ".env");
|
|
391
|
-
const envContent = Object.entries(envVars)
|
|
392
|
-
.map(([key, value]) => `${key}="${value}"`)
|
|
393
|
-
.join("\n") + "\n";
|
|
394
|
-
// Update .env.example
|
|
395
|
-
if (await fs_extra_1.default.pathExists(envExamplePath)) {
|
|
396
|
-
const existing = await fs_extra_1.default.readFile(envExamplePath, "utf-8");
|
|
397
|
-
const existingKeys = existing.split("\n").map((line) => line.split("=")[0]);
|
|
398
|
-
const newVars = Object.keys(envVars).filter((key) => !existingKeys.includes(key));
|
|
399
|
-
if (newVars.length > 0) {
|
|
400
|
-
const newContent = newVars.map((key) => `${key}="${envVars[key]}"`).join("\n");
|
|
401
|
-
await fs_extra_1.default.appendFile(envExamplePath, "\n" + newContent + "\n");
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
else {
|
|
405
|
-
await fs_extra_1.default.writeFile(envExamplePath, envContent);
|
|
406
|
-
}
|
|
407
|
-
// Create .env if doesn't exist
|
|
408
|
-
if (!(await fs_extra_1.default.pathExists(envPath))) {
|
|
409
|
-
await fs_extra_1.default.writeFile(envPath, envContent);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
async function convertToJavaScript(targetDir, framework) {
|
|
413
|
-
// Remove TS config and declaration files
|
|
414
|
-
const tsFiles = [
|
|
415
|
-
"tsconfig.json",
|
|
416
|
-
"tsconfig.app.json",
|
|
417
|
-
"tsconfig.node.json",
|
|
418
|
-
"next-env.d.ts",
|
|
419
|
-
"vite-env.d.ts",
|
|
420
|
-
];
|
|
421
|
-
for (const file of tsFiles) {
|
|
422
|
-
const filePath = path_1.default.join(targetDir, file);
|
|
423
|
-
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
424
|
-
await fs_extra_1.default.remove(filePath);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
const removeDtsFiles = async (dir) => {
|
|
428
|
-
const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
|
|
429
|
-
for (const entry of entries) {
|
|
430
|
-
const fullPath = path_1.default.join(dir, entry.name);
|
|
431
|
-
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
432
|
-
await removeDtsFiles(fullPath);
|
|
433
|
-
}
|
|
434
|
-
else if (entry.isFile() && entry.name.endsWith(".d.ts")) {
|
|
435
|
-
await fs_extra_1.default.remove(fullPath);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
};
|
|
439
|
-
await removeDtsFiles(targetDir);
|
|
440
|
-
// Use Babel to strip types only, preserving exact formatting/comments/blank lines, producing clean production-ready code
|
|
441
|
-
const babel = require("@babel/core");
|
|
442
|
-
const transpileAllTsFiles = async (dir) => {
|
|
443
|
-
const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
|
|
444
|
-
for (const entry of entries) {
|
|
445
|
-
const fullPath = path_1.default.join(dir, entry.name);
|
|
446
|
-
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
447
|
-
await transpileAllTsFiles(fullPath);
|
|
448
|
-
}
|
|
449
|
-
else if (entry.isFile()) {
|
|
450
|
-
if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
|
|
451
|
-
const code = await fs_extra_1.default.readFile(fullPath, "utf8");
|
|
452
|
-
const isTsx = entry.name.endsWith(".tsx");
|
|
453
|
-
const outFile = fullPath.replace(/\.tsx$/, ".jsx").replace(/\.ts$/, ".js");
|
|
454
|
-
const presets = [
|
|
455
|
-
[
|
|
456
|
-
require.resolve("@babel/preset-typescript"),
|
|
457
|
-
{
|
|
458
|
-
onlyRemoveTypeImports: true,
|
|
459
|
-
allowDeclareFields: true,
|
|
460
|
-
allowNamespaces: true,
|
|
461
|
-
optimizeForSpeed: true,
|
|
462
|
-
allExtensions: true,
|
|
463
|
-
isTSX: isTsx,
|
|
464
|
-
},
|
|
465
|
-
],
|
|
466
|
-
[
|
|
467
|
-
require.resolve("@babel/preset-env"),
|
|
468
|
-
{
|
|
469
|
-
targets: { node: "18" },
|
|
470
|
-
modules: false,
|
|
471
|
-
},
|
|
472
|
-
],
|
|
473
|
-
];
|
|
474
|
-
if (isTsx) {
|
|
475
|
-
presets.push([
|
|
476
|
-
require.resolve("@babel/preset-react"),
|
|
477
|
-
{
|
|
478
|
-
runtime: "automatic",
|
|
479
|
-
},
|
|
480
|
-
]);
|
|
481
|
-
}
|
|
482
|
-
// Use recast + Babel AST transform (same approach as transform.tools)
|
|
483
|
-
try {
|
|
484
|
-
const recast = require("recast");
|
|
485
|
-
const { transformFromAstSync } = require("@babel/core");
|
|
486
|
-
const transformTypescript = require("@babel/plugin-transform-typescript");
|
|
487
|
-
// getBabelOptions may be exported as default or directly
|
|
488
|
-
let getBabelOptions = require("recast/parsers/_babel_options");
|
|
489
|
-
if (getBabelOptions && getBabelOptions.default)
|
|
490
|
-
getBabelOptions = getBabelOptions.default;
|
|
491
|
-
const babelParser = require("recast/parsers/babel").parser;
|
|
492
|
-
const ast = recast.parse(code, {
|
|
493
|
-
parser: {
|
|
494
|
-
parse: (source, options) => {
|
|
495
|
-
const babelOptions = getBabelOptions(options || {});
|
|
496
|
-
// ensure typescript and jsx handling
|
|
497
|
-
if (isTsx) {
|
|
498
|
-
babelOptions.plugins.push("typescript", "jsx");
|
|
499
|
-
}
|
|
500
|
-
else {
|
|
501
|
-
babelOptions.plugins.push("typescript");
|
|
502
|
-
}
|
|
503
|
-
return babelParser.parse(source, babelOptions);
|
|
504
|
-
},
|
|
505
|
-
},
|
|
506
|
-
});
|
|
507
|
-
const opts = {
|
|
508
|
-
cloneInputAst: false,
|
|
509
|
-
code: false,
|
|
510
|
-
ast: true,
|
|
511
|
-
plugins: [transformTypescript],
|
|
512
|
-
configFile: false,
|
|
513
|
-
};
|
|
514
|
-
const { ast: transformedAST } = transformFromAstSync(ast, code, opts);
|
|
515
|
-
const resultCode = recast.print(transformedAST).code;
|
|
516
|
-
await fs_extra_1.default.writeFile(outFile, resultCode, "utf8");
|
|
517
|
-
await fs_extra_1.default.remove(fullPath);
|
|
518
|
-
continue;
|
|
519
|
-
}
|
|
520
|
-
catch (e) {
|
|
521
|
-
// fallback to previous Babel pipeline if anything fails
|
|
522
|
-
}
|
|
523
|
-
const result = await babel.transformAsync(code, {
|
|
524
|
-
filename: entry.name,
|
|
525
|
-
presets,
|
|
526
|
-
comments: true,
|
|
527
|
-
retainLines: true,
|
|
528
|
-
compact: false,
|
|
529
|
-
babelrc: false,
|
|
530
|
-
configFile: false,
|
|
531
|
-
});
|
|
532
|
-
await fs_extra_1.default.writeFile(outFile, result.code, "utf8");
|
|
533
|
-
await fs_extra_1.default.remove(fullPath);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
};
|
|
538
|
-
await transpileAllTsFiles(targetDir);
|
|
539
|
-
const templatesRoot = path_1.default.join(__dirname, "..", "..", "templates");
|
|
540
|
-
const templateName = framework;
|
|
541
|
-
let fileReplacements = [];
|
|
542
|
-
let jsScripts = null;
|
|
543
|
-
if (templateName) {
|
|
544
|
-
const templateJsonPath = path_1.default.join(templatesRoot, templateName, "template.json");
|
|
545
|
-
if (await fs_extra_1.default.pathExists(templateJsonPath)) {
|
|
546
|
-
try {
|
|
547
|
-
const templateJson = await fs_extra_1.default.readJson(templateJsonPath);
|
|
548
|
-
if (Array.isArray(templateJson.fileReplacements)) {
|
|
549
|
-
fileReplacements = templateJson.fileReplacements;
|
|
550
|
-
}
|
|
551
|
-
if (templateJson.jsScripts) {
|
|
552
|
-
jsScripts = templateJson.jsScripts;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
catch { }
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
for (const rep of fileReplacements) {
|
|
559
|
-
const filePath = path_1.default.join(targetDir, rep.file);
|
|
560
|
-
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
561
|
-
let content = await fs_extra_1.default.readFile(filePath, "utf8");
|
|
562
|
-
if (rep.from && rep.to) {
|
|
563
|
-
content = content.replace(rep.from, rep.to);
|
|
564
|
-
await fs_extra_1.default.writeFile(filePath, content, "utf8");
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
if (jsScripts) {
|
|
569
|
-
const packageJsonPath = path_1.default.join(targetDir, "package.json");
|
|
570
|
-
if (await fs_extra_1.default.pathExists(packageJsonPath)) {
|
|
571
|
-
const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
|
|
572
|
-
packageJson.scripts = { ...packageJson.scripts, ...jsScripts };
|
|
573
|
-
await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
const jsconfig = path_1.default.join(targetDir, "jsconfig.json");
|
|
577
|
-
if (!(await fs_extra_1.default.pathExists(jsconfig))) {
|
|
578
|
-
for (const tmpl of await fs_extra_1.default.readdir(templatesRoot, { withFileTypes: true })) {
|
|
579
|
-
if (tmpl.isDirectory()) {
|
|
580
|
-
const templateJsconfig = path_1.default.join(templatesRoot, tmpl.name, "jsconfig.json");
|
|
581
|
-
if (await fs_extra_1.default.pathExists(templateJsconfig)) {
|
|
582
|
-
await fs_extra_1.default.copy(templateJsconfig, jsconfig);
|
|
583
|
-
break;
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
const srcDir = path_1.default.join(targetDir, "src");
|
|
589
|
-
if (await fs_extra_1.default.pathExists(srcDir)) {
|
|
590
|
-
const srcFiles = await fs_extra_1.default.readdir(srcDir);
|
|
591
|
-
for (const file of srcFiles) {
|
|
592
|
-
if ((file.endsWith(".js") || file.endsWith(".jsx")) &&
|
|
593
|
-
file.replace(/\.(js|jsx)$/, ".ts") &&
|
|
594
|
-
srcFiles.includes(file.replace(/\.(js|jsx)$/, ".ts"))) {
|
|
595
|
-
await fs_extra_1.default.remove(path_1.default.join(srcDir, file.replace(/\.(js|jsx)$/, ".ts")));
|
|
596
|
-
}
|
|
597
|
-
if (file.endsWith(".jsx") && srcFiles.includes(file.replace(/\.jsx$/, ".tsx"))) {
|
|
598
|
-
await fs_extra_1.default.remove(path_1.default.join(srcDir, file.replace(/\.jsx$/, ".tsx")));
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
const packageJsonPath = path_1.default.join(targetDir, "package.json");
|
|
603
|
-
if (await fs_extra_1.default.pathExists(packageJsonPath)) {
|
|
604
|
-
const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
|
|
605
|
-
if (packageJson.devDependencies) {
|
|
606
|
-
delete packageJson.devDependencies["typescript"];
|
|
607
|
-
delete packageJson.devDependencies["@types/node"];
|
|
608
|
-
delete packageJson.devDependencies["@types/react"];
|
|
609
|
-
delete packageJson.devDependencies["@types/react-dom"];
|
|
610
|
-
}
|
|
611
|
-
await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
async function installDependencies(cwd, packageManager) {
|
|
615
|
-
const commands = {
|
|
616
|
-
npm: "npm install",
|
|
617
|
-
yarn: "yarn install",
|
|
618
|
-
pnpm: "pnpm install",
|
|
619
|
-
bun: "bun install",
|
|
620
|
-
};
|
|
621
|
-
const isAvailable = (cmd) => {
|
|
622
|
-
try {
|
|
623
|
-
(0, child_process_1.execSync)(`command -v ${cmd}`, { stdio: "ignore" });
|
|
624
|
-
return true;
|
|
625
|
-
}
|
|
626
|
-
catch {
|
|
627
|
-
return false;
|
|
628
|
-
}
|
|
629
|
-
};
|
|
630
|
-
let chosen = packageManager;
|
|
631
|
-
// If requested package manager is not available, try to fall back to a common one
|
|
632
|
-
if (!isAvailable(chosen)) {
|
|
633
|
-
const fallbacks = ["pnpm", "npm", "yarn", "bun"];
|
|
634
|
-
const found = fallbacks.find((p) => isAvailable(p));
|
|
635
|
-
if (found) {
|
|
636
|
-
console.warn(`Selected package manager '${chosen}' was not found. Falling back to '${found}'.`);
|
|
637
|
-
chosen = found;
|
|
638
|
-
}
|
|
639
|
-
else {
|
|
640
|
-
throw new Error(`Selected package manager '${packageManager}' was not found and no fallback package manager is available. Please install '${packageManager}' or use a different package manager.`);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
const command = commands[chosen];
|
|
644
|
-
(0, child_process_1.execSync)(command, {
|
|
645
|
-
cwd,
|
|
646
|
-
stdio: "pipe",
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
async function initGit(cwd) {
|
|
650
|
-
try {
|
|
651
|
-
(0, child_process_1.execSync)("git --version", { stdio: "pipe" });
|
|
652
|
-
(0, child_process_1.execSync)("git init", { cwd, stdio: "pipe" });
|
|
653
|
-
(0, child_process_1.execSync)("git add -A", { cwd, stdio: "pipe" });
|
|
654
|
-
(0, child_process_1.execSync)('git commit -m "Initial commit from create-stackkit-app"', {
|
|
655
|
-
cwd,
|
|
656
|
-
stdio: "pipe",
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
catch (error) {
|
|
660
|
-
throw new Error("Git initialization failed");
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
async function applyFrameworkPatches(targetDir, patches) {
|
|
664
|
-
for (const [filename, patchConfig] of Object.entries(patches)) {
|
|
665
|
-
const filePath = path_1.default.join(targetDir, filename);
|
|
666
|
-
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
667
|
-
const fileContent = await fs_extra_1.default.readJson(filePath);
|
|
668
|
-
if (patchConfig.merge) {
|
|
669
|
-
// Deep merge configuration
|
|
670
|
-
const merged = deepMerge(fileContent, patchConfig.merge);
|
|
671
|
-
await fs_extra_1.default.writeJson(filePath, merged, { spaces: 2 });
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
function deepMerge(target, source) {
|
|
677
|
-
const output = { ...target };
|
|
678
|
-
for (const key in source) {
|
|
679
|
-
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
|
|
680
|
-
if (target[key]) {
|
|
681
|
-
output[key] = deepMerge(target[key], source[key]);
|
|
682
|
-
}
|
|
683
|
-
else {
|
|
684
|
-
output[key] = source[key];
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
else if (Array.isArray(source[key])) {
|
|
688
|
-
// For arrays, merge uniquely
|
|
689
|
-
output[key] = Array.from(new Set([...(target[key] || []), ...source[key]]));
|
|
690
|
-
}
|
|
691
|
-
else {
|
|
692
|
-
output[key] = source[key];
|
|
693
|
-
}
|
|
230
|
+
await (0, js_conversion_1.convertToJavaScript)(targetDir, config.framework);
|
|
694
231
|
}
|
|
695
|
-
return
|
|
232
|
+
return postInstallCommands;
|
|
696
233
|
}
|
|
697
234
|
function showNextSteps(config) {
|
|
235
|
+
// eslint-disable-next-line no-console
|
|
698
236
|
console.log(chalk_1.default.green.bold(`\n✓ Created ${config.projectName}\n`));
|
|
237
|
+
// eslint-disable-next-line no-console
|
|
699
238
|
console.log(chalk_1.default.bold("Next steps:"));
|
|
239
|
+
// eslint-disable-next-line no-console
|
|
700
240
|
console.log(chalk_1.default.cyan(` cd ${config.projectName}`));
|
|
701
|
-
//
|
|
702
|
-
console.log(chalk_1.default.cyan(
|
|
241
|
+
// eslint-disable-next-line no-console
|
|
242
|
+
console.log(chalk_1.default.cyan(` ${config.packageManager} run dev\n`));
|
|
703
243
|
}
|