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.
Files changed (71) hide show
  1. package/bin/create-stackkit.js +10 -1
  2. package/dist/index.js +1 -0
  3. package/dist/lib/create-project.js +79 -539
  4. package/dist/lib/utils/config-utils.d.ts +2 -0
  5. package/dist/lib/utils/config-utils.js +33 -0
  6. package/dist/lib/utils/file-utils.d.ts +8 -0
  7. package/dist/lib/utils/file-utils.js +75 -0
  8. package/dist/lib/utils/git-utils.d.ts +1 -0
  9. package/dist/lib/utils/git-utils.js +9 -0
  10. package/dist/lib/utils/js-conversion.d.ts +1 -0
  11. package/dist/lib/utils/js-conversion.js +244 -0
  12. package/dist/lib/utils/module-utils.d.ts +2 -0
  13. package/dist/lib/utils/module-utils.js +311 -0
  14. package/dist/lib/utils/package-utils.d.ts +1 -0
  15. package/dist/lib/utils/package-utils.js +39 -0
  16. package/modules/auth/better-auth/files/lib/auth.ts +13 -0
  17. package/modules/auth/better-auth/files/schemas/prisma-schema.prisma +63 -0
  18. package/modules/auth/better-auth/module.json +54 -0
  19. package/modules/auth/clerk/module.json +115 -0
  20. package/modules/database/mongoose-mongodb/files/lib/db.ts +44 -6
  21. package/modules/database/mongoose-mongodb/files/models/User.ts +39 -0
  22. package/modules/database/mongoose-mongodb/module.json +27 -12
  23. package/modules/database/prisma/files/lib/prisma.ts +6 -0
  24. package/modules/database/prisma/files/prisma/schema.prisma +8 -0
  25. package/modules/database/prisma/files/prisma.config.ts +12 -0
  26. package/modules/database/prisma/module.json +140 -0
  27. package/package.json +7 -3
  28. package/templates/express/.env.example +2 -10
  29. package/templates/express/package.json +12 -18
  30. package/templates/express/src/app.ts +9 -29
  31. package/templates/express/src/config/env.ts +3 -14
  32. package/templates/express/src/features/auth/auth.controller.ts +48 -0
  33. package/templates/express/src/features/auth/auth.route.ts +10 -0
  34. package/templates/express/src/features/auth/auth.service.ts +21 -0
  35. package/templates/express/src/middlewares/error.middleware.ts +2 -2
  36. package/templates/express/src/server.ts +1 -1
  37. package/templates/express/template.json +1 -5
  38. package/templates/express/tsconfig.json +0 -1
  39. package/dist/lib/template-composer.d.ts +0 -16
  40. package/dist/lib/template-composer.js +0 -197
  41. package/modules/auth/better-auth-express/adapters/mongoose-mongodb.ts +0 -13
  42. package/modules/auth/better-auth-express/adapters/prisma-mongodb.ts +0 -15
  43. package/modules/auth/better-auth-express/adapters/prisma-postgresql.ts +0 -15
  44. package/modules/auth/better-auth-express/files/lib/auth.ts +0 -16
  45. package/modules/auth/better-auth-express/files/routes/auth.ts +0 -12
  46. package/modules/auth/better-auth-express/files/schemas/prisma-mongodb-schema.prisma +0 -72
  47. package/modules/auth/better-auth-express/files/schemas/prisma-postgresql-schema.prisma +0 -72
  48. package/modules/auth/better-auth-express/module.json +0 -61
  49. package/modules/auth/better-auth-nextjs/adapters/mongoose-mongodb.ts +0 -24
  50. package/modules/auth/better-auth-nextjs/adapters/prisma-mongodb.ts +0 -26
  51. package/modules/auth/better-auth-nextjs/adapters/prisma-postgresql.ts +0 -26
  52. package/modules/auth/better-auth-nextjs/files/lib/auth.ts +0 -26
  53. package/modules/auth/better-auth-nextjs/files/schemas/prisma-mongodb-schema.prisma +0 -72
  54. package/modules/auth/better-auth-nextjs/files/schemas/prisma-postgresql-schema.prisma +0 -72
  55. package/modules/auth/better-auth-nextjs/module.json +0 -62
  56. package/modules/auth/better-auth-react/files/lib/auth-client.ts +0 -9
  57. package/modules/auth/better-auth-react/module.json +0 -28
  58. package/modules/auth/clerk-express/module.json +0 -34
  59. package/modules/auth/clerk-nextjs/module.json +0 -64
  60. package/modules/auth/clerk-react/module.json +0 -28
  61. package/modules/database/prisma-mongodb/files/lib/db.ts +0 -9
  62. package/modules/database/prisma-mongodb/files/prisma/schema.prisma +0 -17
  63. package/modules/database/prisma-mongodb/module.json +0 -60
  64. package/modules/database/prisma-postgresql/files/lib/db.ts +0 -9
  65. package/modules/database/prisma-postgresql/files/prisma/schema.prisma +0 -17
  66. package/modules/database/prisma-postgresql/module.json +0 -60
  67. /package/modules/auth/{better-auth-nextjs → better-auth}/files/api/auth/[...all]/route.ts +0 -0
  68. /package/modules/auth/{clerk-express/files/lib → clerk/files/express}/auth.ts +0 -0
  69. /package/modules/auth/{clerk-nextjs/files/lib → clerk/files/nextjs}/auth-provider.tsx +0 -0
  70. /package/modules/auth/{clerk-nextjs/files → clerk/files/nextjs}/middleware.ts +0 -0
  71. /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 + PostgreSQL", value: "prisma-postgresql" },
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-react" },
79
- { name: "Clerk", value: "clerk-react" },
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-nextjs" },
87
- { name: "Clerk", value: "clerk-nextjs" },
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-express" },
95
- { name: "Clerk", value: "clerk-express" },
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" ? "none" : answers.database,
130
- auth: answers.auth,
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 (error) {
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
- // 1. Copy base framework template
171
- await copyBaseFramework(templatesDir, targetDir, config.framework);
172
- // 2. Merge database configuration
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 output;
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
- // Only `bun` is supported as the package manager in production-ready CLI
702
- console.log(chalk_1.default.cyan(" bun run dev\n"));
241
+ // eslint-disable-next-line no-console
242
+ console.log(chalk_1.default.cyan(` ${config.packageManager} run dev\n`));
703
243
  }