create-stackkit-app 0.4.2 ā 0.4.3
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 +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/create-project.js +329 -143
- package/dist/lib/template-composer.js +21 -21
- package/modules/auth/better-auth-express/adapters/mongoose-mongodb.ts +3 -3
- package/modules/auth/better-auth-express/adapters/prisma-mongodb.ts +4 -4
- package/modules/auth/better-auth-express/adapters/prisma-postgresql.ts +4 -4
- package/modules/auth/better-auth-express/files/lib/auth.ts +1 -1
- package/modules/auth/better-auth-express/files/routes/auth.ts +3 -3
- package/modules/auth/better-auth-nextjs/adapters/mongoose-mongodb.ts +3 -3
- package/modules/auth/better-auth-nextjs/adapters/prisma-mongodb.ts +4 -4
- package/modules/auth/better-auth-nextjs/adapters/prisma-postgresql.ts +4 -4
- package/modules/auth/better-auth-nextjs/files/api/auth/[...all]/route.ts +2 -3
- package/modules/auth/better-auth-nextjs/files/lib/auth.ts +4 -4
- package/modules/auth/better-auth-react/files/lib/auth-client.ts +2 -2
- package/modules/auth/clerk-express/files/lib/auth.ts +1 -1
- package/modules/auth/clerk-nextjs/files/lib/auth-provider.tsx +1 -1
- package/modules/auth/clerk-nextjs/files/middleware.ts +3 -3
- package/modules/auth/clerk-react/files/lib/auth-provider.tsx +2 -2
- package/modules/database/mongoose-mongodb/files/lib/db.ts +3 -3
- package/modules/database/prisma-mongodb/files/lib/db.ts +2 -2
- package/modules/database/prisma-postgresql/files/lib/db.ts +2 -2
- package/package.json +8 -3
- package/templates/express/package.json +1 -0
- package/templates/express/src/app.ts +17 -15
- package/templates/express/src/config/env.ts +8 -8
- package/templates/express/src/middlewares/error.middleware.ts +3 -3
- package/templates/express/src/server.ts +2 -2
- package/templates/express/template.json +38 -1
- package/templates/express/tsconfig.json +17 -0
- 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
|
@@ -12,14 +12,14 @@ 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
14
|
async function createProject(projectName) {
|
|
15
|
-
console.log(chalk_1.default.bold.cyan(
|
|
15
|
+
console.log(chalk_1.default.bold.cyan("\n Create StackKit App\n"));
|
|
16
16
|
// Get project configuration through wizard
|
|
17
17
|
const config = await getProjectConfig(projectName);
|
|
18
18
|
// Validate target directory
|
|
19
19
|
const targetDir = path_1.default.join(process.cwd(), config.projectName);
|
|
20
20
|
if (await fs_extra_1.default.pathExists(targetDir)) {
|
|
21
21
|
console.log(chalk_1.default.red(`\nā Directory "${config.projectName}" already exists`));
|
|
22
|
-
console.log(chalk_1.default.gray(
|
|
22
|
+
console.log(chalk_1.default.gray("Please choose a different name or remove the existing directory.\n"));
|
|
23
23
|
process.exit(1);
|
|
24
24
|
}
|
|
25
25
|
// Create project
|
|
@@ -30,99 +30,103 @@ async function createProject(projectName) {
|
|
|
30
30
|
async function getProjectConfig(projectName) {
|
|
31
31
|
const answers = await inquirer_1.default.prompt([
|
|
32
32
|
{
|
|
33
|
-
type:
|
|
34
|
-
name:
|
|
35
|
-
message:
|
|
36
|
-
default: projectName ||
|
|
33
|
+
type: "input",
|
|
34
|
+
name: "projectName",
|
|
35
|
+
message: "Project name:",
|
|
36
|
+
default: projectName || "my-app",
|
|
37
37
|
when: !projectName,
|
|
38
38
|
validate: (input) => {
|
|
39
39
|
const validation = (0, validate_npm_package_name_1.default)(input);
|
|
40
40
|
if (!validation.validForNewPackages) {
|
|
41
|
-
return validation.errors?.[0] ||
|
|
41
|
+
return validation.errors?.[0] || "Invalid package name";
|
|
42
42
|
}
|
|
43
43
|
if (fs_extra_1.default.existsSync(path_1.default.join(process.cwd(), input))) {
|
|
44
|
-
return
|
|
44
|
+
return "Directory already exists";
|
|
45
45
|
}
|
|
46
46
|
return true;
|
|
47
47
|
},
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
|
-
type:
|
|
51
|
-
name:
|
|
52
|
-
message:
|
|
50
|
+
type: "list",
|
|
51
|
+
name: "framework",
|
|
52
|
+
message: "Select framework:",
|
|
53
53
|
choices: [
|
|
54
|
-
{ name:
|
|
55
|
-
{ name:
|
|
56
|
-
{ name:
|
|
54
|
+
{ name: "Next.js", value: "nextjs" },
|
|
55
|
+
{ name: "Express.js", value: "express" },
|
|
56
|
+
{ name: "React (Vite)", value: "react-vite" },
|
|
57
57
|
],
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
|
-
type:
|
|
61
|
-
name:
|
|
62
|
-
message:
|
|
63
|
-
when: (answers) => answers.framework !==
|
|
60
|
+
type: "list",
|
|
61
|
+
name: "database",
|
|
62
|
+
message: "Select database/ORM:",
|
|
63
|
+
when: (answers) => answers.framework !== "react-vite",
|
|
64
64
|
choices: [
|
|
65
|
-
{ name:
|
|
66
|
-
{ name:
|
|
67
|
-
{ name:
|
|
68
|
-
{ name:
|
|
69
|
-
{ name: 'None', value: 'none' },
|
|
65
|
+
{ name: "Prisma + PostgreSQL", value: "prisma-postgresql" },
|
|
66
|
+
{ name: "Prisma + MongoDB", value: "prisma-mongodb" },
|
|
67
|
+
{ name: "Mongoose + MongoDB", value: "mongoose-mongodb" },
|
|
68
|
+
{ name: "None", value: "none" },
|
|
70
69
|
],
|
|
71
70
|
},
|
|
72
71
|
{
|
|
73
|
-
type:
|
|
74
|
-
name:
|
|
75
|
-
message:
|
|
72
|
+
type: "list",
|
|
73
|
+
name: "auth",
|
|
74
|
+
message: "Select authentication:",
|
|
76
75
|
choices: (answers) => {
|
|
77
|
-
if (answers.framework ===
|
|
76
|
+
if (answers.framework === "react-vite") {
|
|
78
77
|
return [
|
|
79
|
-
{ name:
|
|
80
|
-
{ name:
|
|
81
|
-
{ name:
|
|
78
|
+
{ name: "Better Auth", value: "better-auth-react" },
|
|
79
|
+
{ name: "Clerk", value: "clerk-react" },
|
|
80
|
+
{ name: "None", value: "none" },
|
|
82
81
|
];
|
|
83
82
|
}
|
|
84
83
|
// Next.js apps
|
|
85
|
-
if (answers.framework ===
|
|
84
|
+
if (answers.framework === "nextjs") {
|
|
86
85
|
return [
|
|
87
|
-
{ name:
|
|
88
|
-
{ name:
|
|
89
|
-
{ name:
|
|
86
|
+
{ name: "Better Auth", value: "better-auth-nextjs" },
|
|
87
|
+
{ name: "Clerk", value: "clerk-nextjs" },
|
|
88
|
+
{ name: "None", value: "none" },
|
|
90
89
|
];
|
|
91
90
|
}
|
|
92
91
|
// Express apps
|
|
93
|
-
if (answers.framework ===
|
|
92
|
+
if (answers.framework === "express") {
|
|
94
93
|
return [
|
|
95
|
-
{ name:
|
|
96
|
-
{ name:
|
|
97
|
-
{ name:
|
|
94
|
+
{ name: "Better Auth", value: "better-auth-express" },
|
|
95
|
+
{ name: "Clerk", value: "clerk-express" },
|
|
96
|
+
{ name: "None", value: "none" },
|
|
98
97
|
];
|
|
99
98
|
}
|
|
100
99
|
// Default - no auth
|
|
101
|
-
return [{ name:
|
|
100
|
+
return [{ name: "None", value: "none" }];
|
|
102
101
|
},
|
|
103
102
|
},
|
|
104
103
|
{
|
|
105
|
-
type:
|
|
106
|
-
name:
|
|
107
|
-
message:
|
|
104
|
+
type: "list",
|
|
105
|
+
name: "language",
|
|
106
|
+
message: "Language:",
|
|
108
107
|
choices: [
|
|
109
|
-
{ name:
|
|
110
|
-
{ name:
|
|
108
|
+
{ name: "TypeScript", value: "typescript" },
|
|
109
|
+
{ name: "JavaScript", value: "javascript" },
|
|
111
110
|
],
|
|
112
|
-
default:
|
|
111
|
+
default: "typescript",
|
|
113
112
|
},
|
|
114
113
|
{
|
|
115
|
-
type:
|
|
116
|
-
name:
|
|
117
|
-
message:
|
|
118
|
-
choices: [
|
|
119
|
-
|
|
114
|
+
type: "list",
|
|
115
|
+
name: "packageManager",
|
|
116
|
+
message: "Package manager:",
|
|
117
|
+
choices: [
|
|
118
|
+
{ name: "pnpm (recommended)", value: "pnpm" },
|
|
119
|
+
{ name: "npm", value: "npm" },
|
|
120
|
+
{ name: "yarn", value: "yarn" },
|
|
121
|
+
{ name: "bun", value: "bun" },
|
|
122
|
+
],
|
|
123
|
+
default: "pnpm",
|
|
120
124
|
},
|
|
121
125
|
]);
|
|
122
126
|
return {
|
|
123
127
|
projectName: projectName || answers.projectName,
|
|
124
128
|
framework: answers.framework,
|
|
125
|
-
database: answers.framework ===
|
|
129
|
+
database: answers.framework === "react-vite" ? "none" : answers.database,
|
|
126
130
|
auth: answers.auth,
|
|
127
131
|
language: answers.language,
|
|
128
132
|
packageManager: answers.packageManager,
|
|
@@ -131,58 +135,58 @@ async function getProjectConfig(projectName) {
|
|
|
131
135
|
async function generateProject(config, targetDir) {
|
|
132
136
|
console.log();
|
|
133
137
|
// Copy and compose template
|
|
134
|
-
const copySpinner = (0, ora_1.default)(
|
|
138
|
+
const copySpinner = (0, ora_1.default)("Creating project files...").start();
|
|
135
139
|
try {
|
|
136
140
|
await composeTemplate(config, targetDir);
|
|
137
|
-
copySpinner.succeed(
|
|
141
|
+
copySpinner.succeed("Project files created");
|
|
138
142
|
}
|
|
139
143
|
catch (error) {
|
|
140
|
-
copySpinner.fail(
|
|
144
|
+
copySpinner.fail("Failed to create project files");
|
|
141
145
|
throw error;
|
|
142
146
|
}
|
|
143
147
|
// Install dependencies
|
|
144
|
-
const installSpinner = (0, ora_1.default)(
|
|
148
|
+
const installSpinner = (0, ora_1.default)("Installing dependencies...").start();
|
|
145
149
|
try {
|
|
146
150
|
await installDependencies(targetDir, config.packageManager);
|
|
147
|
-
installSpinner.succeed(
|
|
151
|
+
installSpinner.succeed("Dependencies installed");
|
|
148
152
|
}
|
|
149
153
|
catch (error) {
|
|
150
|
-
installSpinner.fail(
|
|
154
|
+
installSpinner.fail("Failed to install dependencies");
|
|
151
155
|
throw error;
|
|
152
156
|
}
|
|
153
157
|
// Initialize git
|
|
154
|
-
const gitSpinner = (0, ora_1.default)(
|
|
158
|
+
const gitSpinner = (0, ora_1.default)("Initializing git repository...").start();
|
|
155
159
|
try {
|
|
156
160
|
await initGit(targetDir);
|
|
157
|
-
gitSpinner.succeed(
|
|
161
|
+
gitSpinner.succeed("Git repository initialized");
|
|
158
162
|
}
|
|
159
163
|
catch (error) {
|
|
160
|
-
gitSpinner.warn(
|
|
164
|
+
gitSpinner.warn("Failed to initialize git repository");
|
|
161
165
|
}
|
|
162
166
|
}
|
|
163
167
|
async function composeTemplate(config, targetDir) {
|
|
164
|
-
const templatesDir = path_1.default.join(__dirname,
|
|
168
|
+
const templatesDir = path_1.default.join(__dirname, "..", "..", "templates");
|
|
165
169
|
await fs_extra_1.default.ensureDir(targetDir);
|
|
166
170
|
// 1. Copy base framework template
|
|
167
171
|
await copyBaseFramework(templatesDir, targetDir, config.framework);
|
|
168
172
|
// 2. Merge database configuration
|
|
169
|
-
if (config.database !==
|
|
173
|
+
if (config.database !== "none") {
|
|
170
174
|
await mergeDatabaseConfig(templatesDir, targetDir, config.database, config.framework);
|
|
171
175
|
}
|
|
172
176
|
// 3. Merge auth configuration
|
|
173
|
-
if (config.auth !==
|
|
177
|
+
if (config.auth !== "none") {
|
|
174
178
|
await mergeAuthConfig(templatesDir, targetDir, config.framework, config.auth, config.database);
|
|
175
179
|
}
|
|
176
180
|
// 4. Update package.json with project name
|
|
177
|
-
const packageJsonPath = path_1.default.join(targetDir,
|
|
181
|
+
const packageJsonPath = path_1.default.join(targetDir, "package.json");
|
|
178
182
|
if (await fs_extra_1.default.pathExists(packageJsonPath)) {
|
|
179
183
|
const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
|
|
180
184
|
packageJson.name = config.projectName;
|
|
181
185
|
await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
182
186
|
}
|
|
183
187
|
// 5. Convert to JavaScript if selected
|
|
184
|
-
if (config.language ===
|
|
185
|
-
await convertToJavaScript(targetDir);
|
|
188
|
+
if (config.language === "javascript") {
|
|
189
|
+
await convertToJavaScript(targetDir, config.framework);
|
|
186
190
|
}
|
|
187
191
|
}
|
|
188
192
|
async function copyBaseFramework(templatesDir, targetDir, framework) {
|
|
@@ -193,34 +197,34 @@ async function copyBaseFramework(templatesDir, targetDir, framework) {
|
|
|
193
197
|
await fs_extra_1.default.copy(baseDir, targetDir, {
|
|
194
198
|
filter: (src) => {
|
|
195
199
|
const basename = path_1.default.basename(src);
|
|
196
|
-
return ![
|
|
200
|
+
return !["template.json", "config.json", "node_modules", ".git"].includes(basename);
|
|
197
201
|
},
|
|
198
202
|
});
|
|
199
203
|
}
|
|
200
204
|
async function mergeDatabaseConfig(templatesDir, targetDir, database, framework) {
|
|
201
205
|
// Use modules directory (sibling to templates)
|
|
202
|
-
const modulesDir = path_1.default.join(templatesDir,
|
|
203
|
-
const dbModulePath = path_1.default.join(modulesDir,
|
|
206
|
+
const modulesDir = path_1.default.join(templatesDir, "..", "modules");
|
|
207
|
+
const dbModulePath = path_1.default.join(modulesDir, "database", database);
|
|
204
208
|
if (!(await fs_extra_1.default.pathExists(dbModulePath))) {
|
|
205
209
|
console.warn(`Database module not found: ${database}`);
|
|
206
210
|
return;
|
|
207
211
|
}
|
|
208
212
|
// Read module.json
|
|
209
|
-
const moduleJsonPath = path_1.default.join(dbModulePath,
|
|
213
|
+
const moduleJsonPath = path_1.default.join(dbModulePath, "module.json");
|
|
210
214
|
if (!(await fs_extra_1.default.pathExists(moduleJsonPath))) {
|
|
211
215
|
return;
|
|
212
216
|
}
|
|
213
217
|
const moduleData = await fs_extra_1.default.readJson(moduleJsonPath);
|
|
214
218
|
// Copy files from module
|
|
215
|
-
const filesDir = path_1.default.join(dbModulePath,
|
|
219
|
+
const filesDir = path_1.default.join(dbModulePath, "files");
|
|
216
220
|
if (await fs_extra_1.default.pathExists(filesDir)) {
|
|
217
221
|
// Copy files based on patches in module.json
|
|
218
222
|
for (const patch of moduleData.patches || []) {
|
|
219
|
-
if (patch.type ===
|
|
223
|
+
if (patch.type === "create-file") {
|
|
220
224
|
const sourceFile = path_1.default.join(filesDir, patch.source);
|
|
221
225
|
let destFile = path_1.default.join(targetDir, patch.destination);
|
|
222
226
|
// Simple placeholder replacement for lib
|
|
223
|
-
destFile = destFile.replace(
|
|
227
|
+
destFile = destFile.replace("{{lib}}", "lib").replace("{{src}}", "src");
|
|
224
228
|
if (await fs_extra_1.default.pathExists(sourceFile)) {
|
|
225
229
|
await fs_extra_1.default.ensureDir(path_1.default.dirname(destFile));
|
|
226
230
|
await fs_extra_1.default.copy(sourceFile, destFile, { overwrite: false });
|
|
@@ -241,66 +245,66 @@ async function mergeDatabaseConfig(templatesDir, targetDir, database, framework)
|
|
|
241
245
|
await mergeEnvFile(targetDir, envVars);
|
|
242
246
|
// Apply framework-specific patches from database module
|
|
243
247
|
if (moduleData.frameworkPatches) {
|
|
244
|
-
const frameworkKey = framework ===
|
|
248
|
+
const frameworkKey = framework === "react-vite" ? "react" : framework;
|
|
245
249
|
const patches = moduleData.frameworkPatches[frameworkKey];
|
|
246
250
|
if (patches) {
|
|
247
251
|
await applyFrameworkPatches(targetDir, patches);
|
|
248
252
|
}
|
|
249
253
|
}
|
|
250
254
|
}
|
|
251
|
-
async function mergeAuthConfig(templatesDir, targetDir, framework, auth, database =
|
|
255
|
+
async function mergeAuthConfig(templatesDir, targetDir, framework, auth, database = "none") {
|
|
252
256
|
// Use modules directory (sibling to templates)
|
|
253
|
-
const modulesDir = path_1.default.join(templatesDir,
|
|
257
|
+
const modulesDir = path_1.default.join(templatesDir, "..", "modules");
|
|
254
258
|
// Auth modules are now named with framework suffix
|
|
255
259
|
// e.g., better-auth-nextjs, authjs-express, better-auth-react
|
|
256
260
|
// If auth already has framework suffix, use it directly
|
|
257
261
|
// Otherwise, map old names to new ones
|
|
258
262
|
const authMap = {
|
|
259
|
-
nextauth:
|
|
260
|
-
|
|
261
|
-
clerk: framework ===
|
|
262
|
-
?
|
|
263
|
-
: framework ===
|
|
264
|
-
?
|
|
265
|
-
:
|
|
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",
|
|
266
270
|
};
|
|
267
|
-
const authKey = auth.includes(
|
|
268
|
-
const authModulePath = path_1.default.join(modulesDir,
|
|
271
|
+
const authKey = auth.includes("-") ? auth : authMap[auth] || auth;
|
|
272
|
+
const authModulePath = path_1.default.join(modulesDir, "auth", authKey);
|
|
269
273
|
if (!(await fs_extra_1.default.pathExists(authModulePath))) {
|
|
270
274
|
console.warn(`Auth module not found: ${authKey}`);
|
|
271
275
|
return;
|
|
272
276
|
}
|
|
273
277
|
// Read module.json
|
|
274
|
-
const moduleJsonPath = path_1.default.join(authModulePath,
|
|
278
|
+
const moduleJsonPath = path_1.default.join(authModulePath, "module.json");
|
|
275
279
|
if (!(await fs_extra_1.default.pathExists(moduleJsonPath))) {
|
|
276
280
|
return;
|
|
277
281
|
}
|
|
278
282
|
const moduleData = await fs_extra_1.default.readJson(moduleJsonPath);
|
|
279
283
|
// Copy files from module
|
|
280
|
-
const filesDir = path_1.default.join(authModulePath,
|
|
284
|
+
const filesDir = path_1.default.join(authModulePath, "files");
|
|
281
285
|
if (await fs_extra_1.default.pathExists(filesDir)) {
|
|
282
286
|
// Determine path replacements based on framework
|
|
283
287
|
const getReplacements = () => {
|
|
284
|
-
if (framework ===
|
|
285
|
-
return { lib:
|
|
288
|
+
if (framework === "nextjs") {
|
|
289
|
+
return { lib: "lib", router: "app" };
|
|
286
290
|
}
|
|
287
|
-
else if (framework ===
|
|
288
|
-
return { lib:
|
|
291
|
+
else if (framework === "express") {
|
|
292
|
+
return { lib: "src", router: "src" };
|
|
289
293
|
}
|
|
290
294
|
else {
|
|
291
|
-
return { lib:
|
|
295
|
+
return { lib: "src", router: "src" };
|
|
292
296
|
}
|
|
293
297
|
};
|
|
294
298
|
const replacements = getReplacements();
|
|
295
299
|
// Copy files based on patches in module.json
|
|
296
300
|
for (const patch of moduleData.patches || []) {
|
|
297
|
-
if (patch.type ===
|
|
301
|
+
if (patch.type === "create-file") {
|
|
298
302
|
const sourceFile = path_1.default.join(filesDir, patch.source);
|
|
299
303
|
let destFile = path_1.default.join(targetDir, patch.destination);
|
|
300
304
|
// Replace placeholders
|
|
301
305
|
destFile = destFile
|
|
302
|
-
.replace(
|
|
303
|
-
.replace(
|
|
306
|
+
.replace("{{lib}}", replacements.lib)
|
|
307
|
+
.replace("{{router}}", replacements.router);
|
|
304
308
|
if (await fs_extra_1.default.pathExists(sourceFile)) {
|
|
305
309
|
await fs_extra_1.default.ensureDir(path_1.default.dirname(destFile));
|
|
306
310
|
await fs_extra_1.default.copy(sourceFile, destFile, { overwrite: false });
|
|
@@ -309,7 +313,7 @@ async function mergeAuthConfig(templatesDir, targetDir, framework, auth, databas
|
|
|
309
313
|
}
|
|
310
314
|
}
|
|
311
315
|
// Handle database-specific adapters and schemas
|
|
312
|
-
if (database !==
|
|
316
|
+
if (database !== "none" && moduleData.databaseAdapters) {
|
|
313
317
|
const adapterConfig = moduleData.databaseAdapters[database];
|
|
314
318
|
if (adapterConfig) {
|
|
315
319
|
// Copy adapter file
|
|
@@ -318,14 +322,14 @@ async function mergeAuthConfig(templatesDir, targetDir, framework, auth, databas
|
|
|
318
322
|
const adapterFileName = path_1.default.basename(adapterConfig.adapter);
|
|
319
323
|
// Determine destination based on framework
|
|
320
324
|
let adapterDest;
|
|
321
|
-
if (framework ===
|
|
322
|
-
adapterDest = path_1.default.join(targetDir,
|
|
325
|
+
if (framework === "nextjs") {
|
|
326
|
+
adapterDest = path_1.default.join(targetDir, "lib", "auth.ts");
|
|
323
327
|
}
|
|
324
|
-
else if (framework ===
|
|
325
|
-
adapterDest = path_1.default.join(targetDir,
|
|
328
|
+
else if (framework === "express") {
|
|
329
|
+
adapterDest = path_1.default.join(targetDir, "src", "auth.ts");
|
|
326
330
|
}
|
|
327
331
|
else {
|
|
328
|
-
adapterDest = path_1.default.join(targetDir,
|
|
332
|
+
adapterDest = path_1.default.join(targetDir, "src", "lib", "auth.ts");
|
|
329
333
|
}
|
|
330
334
|
if (await fs_extra_1.default.pathExists(adapterSource)) {
|
|
331
335
|
await fs_extra_1.default.ensureDir(path_1.default.dirname(adapterDest));
|
|
@@ -362,7 +366,7 @@ async function mergeAuthConfig(templatesDir, targetDir, framework, auth, databas
|
|
|
362
366
|
await mergeEnvFile(targetDir, envVars);
|
|
363
367
|
}
|
|
364
368
|
async function mergePackageJson(targetDir, config) {
|
|
365
|
-
const pkgPath = path_1.default.join(targetDir,
|
|
369
|
+
const pkgPath = path_1.default.join(targetDir, "package.json");
|
|
366
370
|
if (!(await fs_extra_1.default.pathExists(pkgPath))) {
|
|
367
371
|
return;
|
|
368
372
|
}
|
|
@@ -382,19 +386,19 @@ async function mergeEnvFile(targetDir, envVars) {
|
|
|
382
386
|
if (Object.keys(envVars).length === 0) {
|
|
383
387
|
return;
|
|
384
388
|
}
|
|
385
|
-
const envExamplePath = path_1.default.join(targetDir,
|
|
386
|
-
const envPath = path_1.default.join(targetDir,
|
|
389
|
+
const envExamplePath = path_1.default.join(targetDir, ".env.example");
|
|
390
|
+
const envPath = path_1.default.join(targetDir, ".env");
|
|
387
391
|
const envContent = Object.entries(envVars)
|
|
388
392
|
.map(([key, value]) => `${key}="${value}"`)
|
|
389
|
-
.join(
|
|
393
|
+
.join("\n") + "\n";
|
|
390
394
|
// Update .env.example
|
|
391
395
|
if (await fs_extra_1.default.pathExists(envExamplePath)) {
|
|
392
|
-
const existing = await fs_extra_1.default.readFile(envExamplePath,
|
|
393
|
-
const existingKeys = existing.split(
|
|
396
|
+
const existing = await fs_extra_1.default.readFile(envExamplePath, "utf-8");
|
|
397
|
+
const existingKeys = existing.split("\n").map((line) => line.split("=")[0]);
|
|
394
398
|
const newVars = Object.keys(envVars).filter((key) => !existingKeys.includes(key));
|
|
395
399
|
if (newVars.length > 0) {
|
|
396
|
-
const newContent = newVars.map((key) => `${key}="${envVars[key]}"`).join(
|
|
397
|
-
await fs_extra_1.default.appendFile(envExamplePath,
|
|
400
|
+
const newContent = newVars.map((key) => `${key}="${envVars[key]}"`).join("\n");
|
|
401
|
+
await fs_extra_1.default.appendFile(envExamplePath, "\n" + newContent + "\n");
|
|
398
402
|
}
|
|
399
403
|
}
|
|
400
404
|
else {
|
|
@@ -405,74 +409,255 @@ async function mergeEnvFile(targetDir, envVars) {
|
|
|
405
409
|
await fs_extra_1.default.writeFile(envPath, envContent);
|
|
406
410
|
}
|
|
407
411
|
}
|
|
408
|
-
async function convertToJavaScript(targetDir) {
|
|
409
|
-
// Remove
|
|
410
|
-
const tsFiles = [
|
|
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
|
+
];
|
|
411
421
|
for (const file of tsFiles) {
|
|
412
422
|
const filePath = path_1.default.join(targetDir, file);
|
|
413
423
|
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
414
424
|
await fs_extra_1.default.remove(filePath);
|
|
415
425
|
}
|
|
416
426
|
}
|
|
417
|
-
|
|
418
|
-
|
|
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) => {
|
|
419
443
|
const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
|
|
420
444
|
for (const entry of entries) {
|
|
421
445
|
const fullPath = path_1.default.join(dir, entry.name);
|
|
422
|
-
if (entry.isDirectory() && entry.name !==
|
|
423
|
-
await
|
|
446
|
+
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
447
|
+
await transpileAllTsFiles(fullPath);
|
|
424
448
|
}
|
|
425
449
|
else if (entry.isFile()) {
|
|
426
|
-
if (entry.name.endsWith(
|
|
427
|
-
await fs_extra_1.default.
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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);
|
|
431
534
|
}
|
|
432
535
|
}
|
|
433
536
|
}
|
|
434
537
|
};
|
|
435
|
-
await
|
|
436
|
-
|
|
437
|
-
const
|
|
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");
|
|
438
603
|
if (await fs_extra_1.default.pathExists(packageJsonPath)) {
|
|
439
604
|
const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
|
|
440
605
|
if (packageJson.devDependencies) {
|
|
441
|
-
delete packageJson.devDependencies[
|
|
442
|
-
delete packageJson.devDependencies[
|
|
443
|
-
delete packageJson.devDependencies[
|
|
444
|
-
delete 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"];
|
|
445
610
|
}
|
|
446
611
|
await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
447
612
|
}
|
|
448
613
|
}
|
|
449
614
|
async function installDependencies(cwd, packageManager) {
|
|
450
615
|
const commands = {
|
|
451
|
-
npm:
|
|
452
|
-
yarn:
|
|
453
|
-
pnpm:
|
|
616
|
+
npm: "npm install",
|
|
617
|
+
yarn: "yarn install",
|
|
618
|
+
pnpm: "pnpm install",
|
|
619
|
+
bun: "bun install",
|
|
454
620
|
};
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
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
|
+
}
|
|
458
642
|
}
|
|
643
|
+
const command = commands[chosen];
|
|
459
644
|
(0, child_process_1.execSync)(command, {
|
|
460
645
|
cwd,
|
|
461
|
-
stdio:
|
|
646
|
+
stdio: "pipe",
|
|
462
647
|
});
|
|
463
648
|
}
|
|
464
649
|
async function initGit(cwd) {
|
|
465
650
|
try {
|
|
466
|
-
(0, child_process_1.execSync)(
|
|
467
|
-
(0, child_process_1.execSync)(
|
|
468
|
-
(0, child_process_1.execSync)(
|
|
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" });
|
|
469
654
|
(0, child_process_1.execSync)('git commit -m "Initial commit from create-stackkit-app"', {
|
|
470
655
|
cwd,
|
|
471
|
-
stdio:
|
|
656
|
+
stdio: "pipe",
|
|
472
657
|
});
|
|
473
658
|
}
|
|
474
659
|
catch (error) {
|
|
475
|
-
throw new Error(
|
|
660
|
+
throw new Error("Git initialization failed");
|
|
476
661
|
}
|
|
477
662
|
}
|
|
478
663
|
async function applyFrameworkPatches(targetDir, patches) {
|
|
@@ -491,7 +676,7 @@ async function applyFrameworkPatches(targetDir, patches) {
|
|
|
491
676
|
function deepMerge(target, source) {
|
|
492
677
|
const output = { ...target };
|
|
493
678
|
for (const key in source) {
|
|
494
|
-
if (source[key] && typeof source[key] ===
|
|
679
|
+
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
|
|
495
680
|
if (target[key]) {
|
|
496
681
|
output[key] = deepMerge(target[key], source[key]);
|
|
497
682
|
}
|
|
@@ -511,7 +696,8 @@ function deepMerge(target, source) {
|
|
|
511
696
|
}
|
|
512
697
|
function showNextSteps(config) {
|
|
513
698
|
console.log(chalk_1.default.green.bold(`\nā Created ${config.projectName}\n`));
|
|
514
|
-
console.log(chalk_1.default.bold(
|
|
699
|
+
console.log(chalk_1.default.bold("Next steps:"));
|
|
515
700
|
console.log(chalk_1.default.cyan(` cd ${config.projectName}`));
|
|
516
|
-
|
|
701
|
+
// Only `bun` is supported as the package manager in production-ready CLI
|
|
702
|
+
console.log(chalk_1.default.cyan(" bun run dev\n"));
|
|
517
703
|
}
|