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.
Files changed (59) hide show
  1. package/README.md +1 -0
  2. package/bin/create-stackkit.js +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/lib/create-project.js +329 -143
  5. package/dist/lib/template-composer.js +21 -21
  6. package/modules/auth/better-auth-express/adapters/mongoose-mongodb.ts +3 -3
  7. package/modules/auth/better-auth-express/adapters/prisma-mongodb.ts +4 -4
  8. package/modules/auth/better-auth-express/adapters/prisma-postgresql.ts +4 -4
  9. package/modules/auth/better-auth-express/files/lib/auth.ts +1 -1
  10. package/modules/auth/better-auth-express/files/routes/auth.ts +3 -3
  11. package/modules/auth/better-auth-nextjs/adapters/mongoose-mongodb.ts +3 -3
  12. package/modules/auth/better-auth-nextjs/adapters/prisma-mongodb.ts +4 -4
  13. package/modules/auth/better-auth-nextjs/adapters/prisma-postgresql.ts +4 -4
  14. package/modules/auth/better-auth-nextjs/files/api/auth/[...all]/route.ts +2 -3
  15. package/modules/auth/better-auth-nextjs/files/lib/auth.ts +4 -4
  16. package/modules/auth/better-auth-react/files/lib/auth-client.ts +2 -2
  17. package/modules/auth/clerk-express/files/lib/auth.ts +1 -1
  18. package/modules/auth/clerk-nextjs/files/lib/auth-provider.tsx +1 -1
  19. package/modules/auth/clerk-nextjs/files/middleware.ts +3 -3
  20. package/modules/auth/clerk-react/files/lib/auth-provider.tsx +2 -2
  21. package/modules/database/mongoose-mongodb/files/lib/db.ts +3 -3
  22. package/modules/database/prisma-mongodb/files/lib/db.ts +2 -2
  23. package/modules/database/prisma-postgresql/files/lib/db.ts +2 -2
  24. package/package.json +8 -3
  25. package/templates/express/package.json +1 -0
  26. package/templates/express/src/app.ts +17 -15
  27. package/templates/express/src/config/env.ts +8 -8
  28. package/templates/express/src/middlewares/error.middleware.ts +3 -3
  29. package/templates/express/src/server.ts +2 -2
  30. package/templates/express/template.json +38 -1
  31. package/templates/express/tsconfig.json +17 -0
  32. package/templates/nextjs/app/layout.tsx +1 -5
  33. package/templates/nextjs/app/page.tsx +26 -34
  34. package/templates/nextjs/package.json +2 -1
  35. package/templates/nextjs/template.json +13 -1
  36. package/templates/react-vite/eslint.config.js +9 -9
  37. package/templates/react-vite/package.json +1 -2
  38. package/templates/react-vite/src/api/client.ts +16 -16
  39. package/templates/react-vite/src/api/services/user.service.ts +2 -10
  40. package/templates/react-vite/src/components/ErrorBoundary.tsx +4 -4
  41. package/templates/react-vite/src/components/Layout.tsx +1 -1
  42. package/templates/react-vite/src/components/Loading.tsx +1 -1
  43. package/templates/react-vite/src/components/SEO.tsx +5 -5
  44. package/templates/react-vite/src/config/constants.ts +3 -3
  45. package/templates/react-vite/src/hooks/index.ts +5 -5
  46. package/templates/react-vite/src/lib/queryClient.ts +2 -2
  47. package/templates/react-vite/src/main.tsx +12 -12
  48. package/templates/react-vite/src/pages/About.tsx +6 -2
  49. package/templates/react-vite/src/pages/Home.tsx +8 -4
  50. package/templates/react-vite/src/pages/NotFound.tsx +2 -2
  51. package/templates/react-vite/src/pages/UserProfile.tsx +6 -6
  52. package/templates/react-vite/src/router.tsx +13 -13
  53. package/templates/react-vite/src/types/{api.ts → api.d.ts} +6 -6
  54. package/templates/react-vite/src/types/user.d.ts +6 -0
  55. package/templates/react-vite/src/utils/helpers.ts +11 -11
  56. package/templates/react-vite/src/utils/storage.ts +4 -4
  57. package/templates/react-vite/template.json +26 -0
  58. package/templates/react-vite/tsconfig.json +1 -4
  59. 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('\nšŸš€ Create StackKit App\n'));
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('Please choose a different name or remove the existing directory.\n'));
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: 'input',
34
- name: 'projectName',
35
- message: 'Project name:',
36
- default: projectName || 'my-app',
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] || 'Invalid package name';
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 'Directory already exists';
44
+ return "Directory already exists";
45
45
  }
46
46
  return true;
47
47
  },
48
48
  },
49
49
  {
50
- type: 'list',
51
- name: 'framework',
52
- message: 'Select framework:',
50
+ type: "list",
51
+ name: "framework",
52
+ message: "Select framework:",
53
53
  choices: [
54
- { name: 'Next.js', value: 'nextjs' },
55
- { name: 'Express.js', value: 'express' },
56
- { name: 'React (Vite)', value: 'react-vite' },
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: 'list',
61
- name: 'database',
62
- message: 'Select database/ORM:',
63
- when: (answers) => answers.framework !== 'react-vite',
60
+ type: "list",
61
+ name: "database",
62
+ message: "Select database/ORM:",
63
+ when: (answers) => answers.framework !== "react-vite",
64
64
  choices: [
65
- { name: 'Prisma + PostgreSQL', value: 'prisma-postgresql' },
66
- { name: 'Prisma + MongoDB', value: 'prisma-mongodb' },
67
- { name: 'Mongoose + MongoDB', value: 'mongoose-mongodb' },
68
- { name: 'Drizzle + PostgreSQL', value: 'drizzle-postgresql' },
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: 'list',
74
- name: 'auth',
75
- message: 'Select authentication:',
72
+ type: "list",
73
+ name: "auth",
74
+ message: "Select authentication:",
76
75
  choices: (answers) => {
77
- if (answers.framework === 'react-vite') {
76
+ if (answers.framework === "react-vite") {
78
77
  return [
79
- { name: 'Better Auth', value: 'better-auth-react' },
80
- { name: 'Clerk', value: 'clerk-react' },
81
- { name: 'None', value: 'none' },
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 === 'nextjs') {
84
+ if (answers.framework === "nextjs") {
86
85
  return [
87
- { name: 'Better Auth', value: 'better-auth-nextjs' },
88
- { name: 'Clerk', value: 'clerk-nextjs' },
89
- { name: 'None', value: 'none' },
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 === 'express') {
92
+ if (answers.framework === "express") {
94
93
  return [
95
- { name: 'Better Auth', value: 'better-auth-express' },
96
- { name: 'Clerk', value: 'clerk-express' },
97
- { name: 'None', value: 'none' },
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: 'None', value: 'none' }];
100
+ return [{ name: "None", value: "none" }];
102
101
  },
103
102
  },
104
103
  {
105
- type: 'list',
106
- name: 'language',
107
- message: 'Language:',
104
+ type: "list",
105
+ name: "language",
106
+ message: "Language:",
108
107
  choices: [
109
- { name: 'TypeScript', value: 'typescript' },
110
- { name: 'JavaScript', value: 'javascript' },
108
+ { name: "TypeScript", value: "typescript" },
109
+ { name: "JavaScript", value: "javascript" },
111
110
  ],
112
- default: 'typescript',
111
+ default: "typescript",
113
112
  },
114
113
  {
115
- type: 'list',
116
- name: 'packageManager',
117
- message: 'Package manager:',
118
- choices: ['pnpm', 'npm', 'yarn'],
119
- default: 'pnpm',
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 === 'react-vite' ? 'none' : answers.database,
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)('Creating project files...').start();
138
+ const copySpinner = (0, ora_1.default)("Creating project files...").start();
135
139
  try {
136
140
  await composeTemplate(config, targetDir);
137
- copySpinner.succeed('Project files created');
141
+ copySpinner.succeed("Project files created");
138
142
  }
139
143
  catch (error) {
140
- copySpinner.fail('Failed to create project files');
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)('Installing dependencies...').start();
148
+ const installSpinner = (0, ora_1.default)("Installing dependencies...").start();
145
149
  try {
146
150
  await installDependencies(targetDir, config.packageManager);
147
- installSpinner.succeed('Dependencies installed');
151
+ installSpinner.succeed("Dependencies installed");
148
152
  }
149
153
  catch (error) {
150
- installSpinner.fail('Failed to install dependencies');
154
+ installSpinner.fail("Failed to install dependencies");
151
155
  throw error;
152
156
  }
153
157
  // Initialize git
154
- const gitSpinner = (0, ora_1.default)('Initializing git repository...').start();
158
+ const gitSpinner = (0, ora_1.default)("Initializing git repository...").start();
155
159
  try {
156
160
  await initGit(targetDir);
157
- gitSpinner.succeed('Git repository initialized');
161
+ gitSpinner.succeed("Git repository initialized");
158
162
  }
159
163
  catch (error) {
160
- gitSpinner.warn('Failed to initialize git repository');
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, '..', '..', 'templates');
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 !== 'none') {
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 !== 'none') {
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, 'package.json');
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 === 'javascript') {
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 !['template.json', 'config.json', 'node_modules', '.git'].includes(basename);
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, '..', 'modules');
203
- const dbModulePath = path_1.default.join(modulesDir, 'database', database);
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, 'module.json');
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, 'files');
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 === 'create-file') {
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('{{lib}}', 'lib').replace('{{src}}', 'src');
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 === 'react-vite' ? 'react' : 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 = 'none') {
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, '..', 'modules');
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: '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',
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('-') ? auth : authMap[auth] || auth;
268
- const authModulePath = path_1.default.join(modulesDir, 'auth', authKey);
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, 'module.json');
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, 'files');
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 === 'nextjs') {
285
- return { lib: 'lib', router: 'app' };
288
+ if (framework === "nextjs") {
289
+ return { lib: "lib", router: "app" };
286
290
  }
287
- else if (framework === 'express') {
288
- return { lib: 'src', router: 'src' };
291
+ else if (framework === "express") {
292
+ return { lib: "src", router: "src" };
289
293
  }
290
294
  else {
291
- return { lib: 'src', router: 'src' };
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 === 'create-file') {
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('{{lib}}', replacements.lib)
303
- .replace('{{router}}', replacements.router);
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 !== 'none' && moduleData.databaseAdapters) {
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 === 'nextjs') {
322
- adapterDest = path_1.default.join(targetDir, 'lib', 'auth.ts');
325
+ if (framework === "nextjs") {
326
+ adapterDest = path_1.default.join(targetDir, "lib", "auth.ts");
323
327
  }
324
- else if (framework === 'express') {
325
- adapterDest = path_1.default.join(targetDir, 'src', 'auth.ts');
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, 'src', 'lib', 'auth.ts');
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, 'package.json');
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, '.env.example');
386
- const envPath = path_1.default.join(targetDir, '.env');
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('\n') + '\n';
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, 'utf-8');
393
- const existingKeys = existing.split('\n').map((line) => line.split('=')[0]);
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('\n');
397
- await fs_extra_1.default.appendFile(envExamplePath, '\n' + newContent + '\n');
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 TypeScript config files
410
- const tsFiles = ['tsconfig.json', 'next-env.d.ts'];
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
- // Rename .ts(x) files to .js(x)
418
- const renameExtensions = async (dir) => {
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 !== 'node_modules') {
423
- await renameExtensions(fullPath);
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('.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'));
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 renameExtensions(targetDir);
436
- // Update package.json to remove TypeScript dependencies
437
- const packageJsonPath = path_1.default.join(targetDir, 'package.json');
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['typescript'];
442
- delete packageJson.devDependencies['@types/node'];
443
- delete packageJson.devDependencies['@types/react'];
444
- delete packageJson.devDependencies['@types/react-dom'];
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: 'npm install',
452
- yarn: 'yarn install',
453
- pnpm: 'pnpm install',
616
+ npm: "npm install",
617
+ yarn: "yarn install",
618
+ pnpm: "pnpm install",
619
+ bun: "bun install",
454
620
  };
455
- const command = commands[packageManager];
456
- if (!command) {
457
- throw new Error(`Unsupported package manager: ${packageManager}`);
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: 'pipe',
646
+ stdio: "pipe",
462
647
  });
463
648
  }
464
649
  async function initGit(cwd) {
465
650
  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' });
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: 'pipe',
656
+ stdio: "pipe",
472
657
  });
473
658
  }
474
659
  catch (error) {
475
- throw new Error('Git initialization failed');
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] === 'object' && !Array.isArray(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('Next steps:'));
699
+ console.log(chalk_1.default.bold("Next steps:"));
515
700
  console.log(chalk_1.default.cyan(` cd ${config.projectName}`));
516
- console.log(chalk_1.default.cyan(` ${config.packageManager}${config.packageManager === 'npm' ? ' run' : ''} dev\n`));
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
  }