create-stackkit-app 0.4.1 → 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 (118) hide show
  1. package/README.md +27 -7
  2. package/bin/create-stackkit.js +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/lib/create-project.js +408 -139
  5. package/dist/lib/template-composer.js +22 -22
  6. package/modules/auth/better-auth-express/adapters/mongoose-mongodb.ts +13 -0
  7. package/modules/auth/better-auth-express/adapters/prisma-mongodb.ts +15 -0
  8. package/modules/auth/better-auth-express/adapters/prisma-postgresql.ts +15 -0
  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-express/files/schemas/prisma-mongodb-schema.prisma +72 -0
  12. package/modules/auth/better-auth-express/files/schemas/prisma-postgresql-schema.prisma +72 -0
  13. package/modules/auth/better-auth-express/module.json +26 -3
  14. package/modules/auth/better-auth-nextjs/adapters/mongoose-mongodb.ts +24 -0
  15. package/modules/auth/better-auth-nextjs/adapters/prisma-mongodb.ts +26 -0
  16. package/modules/auth/better-auth-nextjs/adapters/prisma-postgresql.ts +26 -0
  17. package/modules/auth/better-auth-nextjs/files/api/auth/[...all]/route.ts +2 -3
  18. package/modules/auth/better-auth-nextjs/files/lib/auth.ts +4 -4
  19. package/modules/auth/better-auth-nextjs/files/schemas/prisma-mongodb-schema.prisma +72 -0
  20. package/modules/auth/better-auth-nextjs/files/schemas/prisma-postgresql-schema.prisma +72 -0
  21. package/modules/auth/better-auth-nextjs/module.json +26 -5
  22. package/modules/auth/better-auth-react/files/lib/auth-client.ts +2 -2
  23. package/modules/auth/better-auth-react/module.json +7 -5
  24. package/modules/auth/clerk-express/files/lib/auth.ts +1 -1
  25. package/modules/auth/clerk-express/module.json +23 -8
  26. package/modules/auth/clerk-nextjs/files/lib/auth-provider.tsx +1 -1
  27. package/modules/auth/clerk-nextjs/files/middleware.ts +3 -3
  28. package/modules/auth/clerk-nextjs/module.json +51 -14
  29. package/modules/auth/clerk-react/files/lib/auth-provider.tsx +2 -2
  30. package/modules/auth/clerk-react/module.json +17 -7
  31. package/modules/database/mongoose-mongodb/files/lib/db.ts +3 -3
  32. package/modules/database/mongoose-mongodb/module.json +44 -6
  33. package/modules/database/prisma-mongodb/files/lib/db.ts +2 -2
  34. package/modules/database/prisma-mongodb/files/prisma/schema.prisma +1 -1
  35. package/modules/database/prisma-mongodb/module.json +28 -4
  36. package/modules/database/prisma-postgresql/files/lib/db.ts +2 -2
  37. package/modules/database/prisma-postgresql/files/prisma/schema.prisma +1 -1
  38. package/modules/database/prisma-postgresql/module.json +28 -4
  39. package/package.json +8 -3
  40. package/templates/express/.env.example +11 -0
  41. package/templates/express/eslint.config.cjs +42 -0
  42. package/templates/express/package.json +39 -0
  43. package/templates/express/src/app.ts +71 -0
  44. package/templates/express/src/config/env.ts +23 -0
  45. package/templates/express/src/middlewares/error.middleware.ts +18 -0
  46. package/templates/{bases/express-base → express}/src/server.ts +2 -2
  47. package/templates/express/template.json +44 -0
  48. package/templates/express/tsconfig.json +31 -0
  49. package/templates/{bases/nextjs-base → nextjs}/app/layout.tsx +1 -5
  50. package/templates/nextjs/app/page.tsx +57 -0
  51. package/templates/{bases/nextjs-base → nextjs}/package.json +2 -1
  52. package/templates/{bases/nextjs-base → nextjs}/template.json +13 -1
  53. package/templates/react-vite/.env.example +2 -0
  54. package/templates/react-vite/README.md +85 -0
  55. package/templates/react-vite/eslint.config.js +23 -0
  56. package/templates/{bases/react-vite-base → react-vite}/index.html +1 -0
  57. package/templates/{bases/react-vite-base → react-vite}/package.json +16 -2
  58. package/templates/react-vite/src/api/client.ts +47 -0
  59. package/templates/react-vite/src/api/services/user.service.ts +18 -0
  60. package/templates/react-vite/src/components/ErrorBoundary.tsx +51 -0
  61. package/templates/react-vite/src/components/Layout.tsx +13 -0
  62. package/templates/react-vite/src/components/Loading.tsx +8 -0
  63. package/templates/react-vite/src/components/SEO.tsx +49 -0
  64. package/templates/react-vite/src/config/constants.ts +5 -0
  65. package/templates/react-vite/src/hooks/index.ts +64 -0
  66. package/templates/react-vite/src/index.css +1 -0
  67. package/templates/react-vite/src/lib/queryClient.ts +12 -0
  68. package/templates/react-vite/src/main.tsx +22 -0
  69. package/templates/react-vite/src/pages/About.tsx +78 -0
  70. package/templates/react-vite/src/pages/Home.tsx +49 -0
  71. package/templates/react-vite/src/pages/NotFound.tsx +24 -0
  72. package/templates/react-vite/src/pages/UserProfile.tsx +40 -0
  73. package/templates/react-vite/src/router.tsx +33 -0
  74. package/templates/react-vite/src/types/api.d.ts +20 -0
  75. package/templates/react-vite/src/types/user.d.ts +6 -0
  76. package/templates/react-vite/src/utils/helpers.ts +51 -0
  77. package/templates/react-vite/src/utils/storage.ts +35 -0
  78. package/templates/react-vite/src/vite-env.d.ts +11 -0
  79. package/templates/react-vite/template.json +46 -0
  80. package/templates/react-vite/tsconfig.json +4 -0
  81. package/templates/react-vite/vite.config.ts +13 -0
  82. package/modules/database/drizzle-postgresql/files/drizzle.config.ts +0 -10
  83. package/modules/database/drizzle-postgresql/files/lib/db.ts +0 -7
  84. package/modules/database/drizzle-postgresql/files/lib/schema.ts +0 -8
  85. package/modules/database/drizzle-postgresql/module.json +0 -34
  86. package/templates/bases/express-base/.env.example +0 -2
  87. package/templates/bases/express-base/package.json +0 -23
  88. package/templates/bases/express-base/src/app.ts +0 -34
  89. package/templates/bases/express-base/src/config/env.ts +0 -14
  90. package/templates/bases/express-base/src/middlewares/error.middleware.ts +0 -12
  91. package/templates/bases/express-base/template.json +0 -7
  92. package/templates/bases/express-base/tsconfig.json +0 -14
  93. package/templates/bases/nextjs-base/app/page.tsx +0 -65
  94. package/templates/bases/react-vite-base/README.md +0 -73
  95. package/templates/bases/react-vite-base/eslint.config.js +0 -23
  96. package/templates/bases/react-vite-base/src/App.css +0 -42
  97. package/templates/bases/react-vite-base/src/App.tsx +0 -35
  98. package/templates/bases/react-vite-base/src/index.css +0 -68
  99. package/templates/bases/react-vite-base/src/main.tsx +0 -10
  100. package/templates/bases/react-vite-base/template.json +0 -19
  101. package/templates/bases/react-vite-base/tsconfig.json +0 -7
  102. package/templates/bases/react-vite-base/vite.config.ts +0 -7
  103. /package/templates/{bases/nextjs-base → nextjs}/README.md +0 -0
  104. /package/templates/{bases/nextjs-base → nextjs}/app/favicon.ico +0 -0
  105. /package/templates/{bases/nextjs-base → nextjs}/app/globals.css +0 -0
  106. /package/templates/{bases/nextjs-base → nextjs}/eslint.config.mjs +0 -0
  107. /package/templates/{bases/nextjs-base → nextjs}/next.config.ts +0 -0
  108. /package/templates/{bases/nextjs-base → nextjs}/postcss.config.mjs +0 -0
  109. /package/templates/{bases/nextjs-base → nextjs}/public/file.svg +0 -0
  110. /package/templates/{bases/nextjs-base → nextjs}/public/globe.svg +0 -0
  111. /package/templates/{bases/nextjs-base → nextjs}/public/next.svg +0 -0
  112. /package/templates/{bases/nextjs-base → nextjs}/public/vercel.svg +0 -0
  113. /package/templates/{bases/nextjs-base → nextjs}/public/window.svg +0 -0
  114. /package/templates/{bases/nextjs-base → nextjs}/tsconfig.json +0 -0
  115. /package/templates/{bases/react-vite-base → react-vite}/public/vite.svg +0 -0
  116. /package/templates/{bases/react-vite-base → react-vite}/src/assets/react.svg +0 -0
  117. /package/templates/{bases/react-vite-base → react-vite}/tsconfig.app.json +0 -0
  118. /package/templates/{bases/react-vite-base → react-vite}/tsconfig.node.json +0 -0
@@ -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,96 +135,96 @@ 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') {
170
- await mergeDatabaseConfig(templatesDir, targetDir, config.database);
173
+ if (config.database !== "none") {
174
+ await mergeDatabaseConfig(templatesDir, targetDir, config.database, config.framework);
171
175
  }
172
176
  // 3. Merge auth configuration
173
- if (config.auth !== 'none') {
174
- await mergeAuthConfig(templatesDir, targetDir, config.framework, config.auth);
177
+ if (config.auth !== "none") {
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) {
189
- const baseDir = path_1.default.join(templatesDir, 'bases', `${framework}-base`);
193
+ const baseDir = path_1.default.join(templatesDir, framework);
190
194
  if (!(await fs_extra_1.default.pathExists(baseDir))) {
191
195
  throw new Error(`Base template not found for framework: ${framework}\n` + `Expected at: ${baseDir}`);
192
196
  }
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
- async function mergeDatabaseConfig(templatesDir, targetDir, database) {
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 });
@@ -239,60 +243,68 @@ async function mergeDatabaseConfig(templatesDir, targetDir, database) {
239
243
  envVars[envVar.key] = envVar.value;
240
244
  }
241
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
+ }
242
254
  }
243
- async function mergeAuthConfig(templatesDir, targetDir, framework, auth) {
255
+ async function mergeAuthConfig(templatesDir, targetDir, framework, auth, database = "none") {
244
256
  // Use modules directory (sibling to templates)
245
- const modulesDir = path_1.default.join(templatesDir, '..', 'modules');
257
+ const modulesDir = path_1.default.join(templatesDir, "..", "modules");
246
258
  // Auth modules are now named with framework suffix
247
259
  // e.g., better-auth-nextjs, authjs-express, better-auth-react
248
260
  // If auth already has framework suffix, use it directly
249
261
  // Otherwise, map old names to new ones
250
262
  const authMap = {
251
- nextauth: 'nextauth',
252
- 'better-auth': framework === 'nextjs' ? 'better-auth-nextjs' : 'better-auth-express',
253
- clerk: framework === 'nextjs'
254
- ? 'clerk-nextjs'
255
- : framework === 'react-vite'
256
- ? 'clerk-react'
257
- : '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",
258
270
  };
259
- const authKey = auth.includes('-') ? auth : authMap[auth] || auth;
260
- 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);
261
273
  if (!(await fs_extra_1.default.pathExists(authModulePath))) {
262
274
  console.warn(`Auth module not found: ${authKey}`);
263
275
  return;
264
276
  }
265
277
  // Read module.json
266
- const moduleJsonPath = path_1.default.join(authModulePath, 'module.json');
278
+ const moduleJsonPath = path_1.default.join(authModulePath, "module.json");
267
279
  if (!(await fs_extra_1.default.pathExists(moduleJsonPath))) {
268
280
  return;
269
281
  }
270
282
  const moduleData = await fs_extra_1.default.readJson(moduleJsonPath);
271
283
  // Copy files from module
272
- const filesDir = path_1.default.join(authModulePath, 'files');
284
+ const filesDir = path_1.default.join(authModulePath, "files");
273
285
  if (await fs_extra_1.default.pathExists(filesDir)) {
274
286
  // Determine path replacements based on framework
275
287
  const getReplacements = () => {
276
- if (framework === 'nextjs') {
277
- return { lib: 'lib', router: 'app' };
288
+ if (framework === "nextjs") {
289
+ return { lib: "lib", router: "app" };
278
290
  }
279
- else if (framework === 'express') {
280
- return { lib: 'src', router: 'src' };
291
+ else if (framework === "express") {
292
+ return { lib: "src", router: "src" };
281
293
  }
282
294
  else {
283
- return { lib: 'src', router: 'src' };
295
+ return { lib: "src", router: "src" };
284
296
  }
285
297
  };
286
298
  const replacements = getReplacements();
287
299
  // Copy files based on patches in module.json
288
300
  for (const patch of moduleData.patches || []) {
289
- if (patch.type === 'create-file') {
301
+ if (patch.type === "create-file") {
290
302
  const sourceFile = path_1.default.join(filesDir, patch.source);
291
303
  let destFile = path_1.default.join(targetDir, patch.destination);
292
304
  // Replace placeholders
293
305
  destFile = destFile
294
- .replace('{{lib}}', replacements.lib)
295
- .replace('{{router}}', replacements.router);
306
+ .replace("{{lib}}", replacements.lib)
307
+ .replace("{{router}}", replacements.router);
296
308
  if (await fs_extra_1.default.pathExists(sourceFile)) {
297
309
  await fs_extra_1.default.ensureDir(path_1.default.dirname(destFile));
298
310
  await fs_extra_1.default.copy(sourceFile, destFile, { overwrite: false });
@@ -300,6 +312,47 @@ async function mergeAuthConfig(templatesDir, targetDir, framework, auth) {
300
312
  }
301
313
  }
302
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
+ }
303
356
  // Merge package.json with module dependencies
304
357
  await mergePackageJson(targetDir, {
305
358
  dependencies: moduleData.dependencies,
@@ -313,7 +366,7 @@ async function mergeAuthConfig(templatesDir, targetDir, framework, auth) {
313
366
  await mergeEnvFile(targetDir, envVars);
314
367
  }
315
368
  async function mergePackageJson(targetDir, config) {
316
- const pkgPath = path_1.default.join(targetDir, 'package.json');
369
+ const pkgPath = path_1.default.join(targetDir, "package.json");
317
370
  if (!(await fs_extra_1.default.pathExists(pkgPath))) {
318
371
  return;
319
372
  }
@@ -333,19 +386,19 @@ async function mergeEnvFile(targetDir, envVars) {
333
386
  if (Object.keys(envVars).length === 0) {
334
387
  return;
335
388
  }
336
- const envExamplePath = path_1.default.join(targetDir, '.env.example');
337
- 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");
338
391
  const envContent = Object.entries(envVars)
339
392
  .map(([key, value]) => `${key}="${value}"`)
340
- .join('\n') + '\n';
393
+ .join("\n") + "\n";
341
394
  // Update .env.example
342
395
  if (await fs_extra_1.default.pathExists(envExamplePath)) {
343
- const existing = await fs_extra_1.default.readFile(envExamplePath, 'utf-8');
344
- 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]);
345
398
  const newVars = Object.keys(envVars).filter((key) => !existingKeys.includes(key));
346
399
  if (newVars.length > 0) {
347
- const newContent = newVars.map((key) => `${key}="${envVars[key]}"`).join('\n');
348
- 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");
349
402
  }
350
403
  }
351
404
  else {
@@ -356,79 +409,295 @@ async function mergeEnvFile(targetDir, envVars) {
356
409
  await fs_extra_1.default.writeFile(envPath, envContent);
357
410
  }
358
411
  }
359
- async function convertToJavaScript(targetDir) {
360
- // Remove TypeScript config files
361
- 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
+ ];
362
421
  for (const file of tsFiles) {
363
422
  const filePath = path_1.default.join(targetDir, file);
364
423
  if (await fs_extra_1.default.pathExists(filePath)) {
365
424
  await fs_extra_1.default.remove(filePath);
366
425
  }
367
426
  }
368
- // Rename .ts(x) files to .js(x)
369
- 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) => {
370
443
  const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
371
444
  for (const entry of entries) {
372
445
  const fullPath = path_1.default.join(dir, entry.name);
373
- if (entry.isDirectory() && entry.name !== 'node_modules') {
374
- await renameExtensions(fullPath);
446
+ if (entry.isDirectory() && entry.name !== "node_modules") {
447
+ await transpileAllTsFiles(fullPath);
375
448
  }
376
449
  else if (entry.isFile()) {
377
- if (entry.name.endsWith('.ts')) {
378
- await fs_extra_1.default.rename(fullPath, fullPath.replace(/\.ts$/, '.js'));
379
- }
380
- else if (entry.name.endsWith('.tsx')) {
381
- 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);
382
534
  }
383
535
  }
384
536
  }
385
537
  };
386
- await renameExtensions(targetDir);
387
- // Update package.json to remove TypeScript dependencies
388
- 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");
389
603
  if (await fs_extra_1.default.pathExists(packageJsonPath)) {
390
604
  const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
391
605
  if (packageJson.devDependencies) {
392
- delete packageJson.devDependencies['typescript'];
393
- delete packageJson.devDependencies['@types/node'];
394
- delete packageJson.devDependencies['@types/react'];
395
- 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"];
396
610
  }
397
611
  await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
398
612
  }
399
613
  }
400
614
  async function installDependencies(cwd, packageManager) {
401
615
  const commands = {
402
- npm: 'npm install',
403
- yarn: 'yarn install',
404
- pnpm: 'pnpm install',
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
+ }
405
629
  };
406
- const command = commands[packageManager];
407
- if (!command) {
408
- throw new Error(`Unsupported package manager: ${packageManager}`);
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
+ }
409
642
  }
643
+ const command = commands[chosen];
410
644
  (0, child_process_1.execSync)(command, {
411
645
  cwd,
412
- stdio: 'pipe',
646
+ stdio: "pipe",
413
647
  });
414
648
  }
415
649
  async function initGit(cwd) {
416
650
  try {
417
- (0, child_process_1.execSync)('git --version', { stdio: 'pipe' });
418
- (0, child_process_1.execSync)('git init', { cwd, stdio: 'pipe' });
419
- (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" });
420
654
  (0, child_process_1.execSync)('git commit -m "Initial commit from create-stackkit-app"', {
421
655
  cwd,
422
- stdio: 'pipe',
656
+ stdio: "pipe",
423
657
  });
424
658
  }
425
659
  catch (error) {
426
- throw new Error('Git initialization failed');
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
+ }
427
694
  }
695
+ return output;
428
696
  }
429
697
  function showNextSteps(config) {
430
698
  console.log(chalk_1.default.green.bold(`\n✓ Created ${config.projectName}\n`));
431
- console.log(chalk_1.default.bold('Next steps:'));
699
+ console.log(chalk_1.default.bold("Next steps:"));
432
700
  console.log(chalk_1.default.cyan(` cd ${config.projectName}`));
433
- 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"));
434
703
  }