create-stackkit-app 0.4.2 → 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 (102) hide show
  1. package/README.md +1 -0
  2. package/bin/create-stackkit.js +10 -1
  3. package/dist/index.js +2 -1
  4. package/dist/lib/create-project.js +138 -412
  5. package/dist/lib/utils/config-utils.d.ts +2 -0
  6. package/dist/lib/utils/config-utils.js +33 -0
  7. package/dist/lib/utils/file-utils.d.ts +8 -0
  8. package/dist/lib/utils/file-utils.js +75 -0
  9. package/dist/lib/utils/git-utils.d.ts +1 -0
  10. package/dist/lib/utils/git-utils.js +9 -0
  11. package/dist/lib/utils/js-conversion.d.ts +1 -0
  12. package/dist/lib/utils/js-conversion.js +244 -0
  13. package/dist/lib/utils/module-utils.d.ts +2 -0
  14. package/dist/lib/utils/module-utils.js +311 -0
  15. package/dist/lib/utils/package-utils.d.ts +1 -0
  16. package/dist/lib/utils/package-utils.js +39 -0
  17. package/modules/auth/better-auth/files/api/auth/[...all]/route.ts +4 -0
  18. package/modules/auth/better-auth/files/lib/auth.ts +13 -0
  19. package/modules/auth/better-auth/files/schemas/prisma-schema.prisma +63 -0
  20. package/modules/auth/better-auth/module.json +54 -0
  21. package/modules/auth/{clerk-express/files/lib → clerk/files/express}/auth.ts +1 -1
  22. package/modules/auth/{clerk-nextjs/files/lib → clerk/files/nextjs}/auth-provider.tsx +1 -1
  23. package/modules/auth/clerk/files/nextjs/middleware.ts +9 -0
  24. package/modules/auth/{clerk-react/files/lib → clerk/files/react}/auth-provider.tsx +2 -2
  25. package/modules/auth/clerk/module.json +115 -0
  26. package/modules/database/mongoose-mongodb/files/lib/db.ts +45 -7
  27. package/modules/database/mongoose-mongodb/files/models/User.ts +39 -0
  28. package/modules/database/mongoose-mongodb/module.json +27 -12
  29. package/modules/database/prisma/files/lib/prisma.ts +6 -0
  30. package/modules/database/prisma/files/prisma/schema.prisma +8 -0
  31. package/modules/database/prisma/files/prisma.config.ts +12 -0
  32. package/modules/database/prisma/module.json +140 -0
  33. package/package.json +12 -3
  34. package/templates/express/.env.example +2 -10
  35. package/templates/express/package.json +13 -18
  36. package/templates/express/src/app.ts +21 -39
  37. package/templates/express/src/config/env.ts +6 -17
  38. package/templates/express/src/features/auth/auth.controller.ts +48 -0
  39. package/templates/express/src/features/auth/auth.route.ts +10 -0
  40. package/templates/express/src/features/auth/auth.service.ts +21 -0
  41. package/templates/express/src/middlewares/error.middleware.ts +5 -5
  42. package/templates/express/src/server.ts +3 -3
  43. package/templates/express/template.json +34 -1
  44. package/templates/express/tsconfig.json +17 -1
  45. package/templates/nextjs/app/layout.tsx +1 -5
  46. package/templates/nextjs/app/page.tsx +26 -34
  47. package/templates/nextjs/package.json +2 -1
  48. package/templates/nextjs/template.json +13 -1
  49. package/templates/react-vite/eslint.config.js +9 -9
  50. package/templates/react-vite/package.json +1 -2
  51. package/templates/react-vite/src/api/client.ts +16 -16
  52. package/templates/react-vite/src/api/services/user.service.ts +2 -10
  53. package/templates/react-vite/src/components/ErrorBoundary.tsx +4 -4
  54. package/templates/react-vite/src/components/Layout.tsx +1 -1
  55. package/templates/react-vite/src/components/Loading.tsx +1 -1
  56. package/templates/react-vite/src/components/SEO.tsx +5 -5
  57. package/templates/react-vite/src/config/constants.ts +3 -3
  58. package/templates/react-vite/src/hooks/index.ts +5 -5
  59. package/templates/react-vite/src/lib/queryClient.ts +2 -2
  60. package/templates/react-vite/src/main.tsx +12 -12
  61. package/templates/react-vite/src/pages/About.tsx +6 -2
  62. package/templates/react-vite/src/pages/Home.tsx +8 -4
  63. package/templates/react-vite/src/pages/NotFound.tsx +2 -2
  64. package/templates/react-vite/src/pages/UserProfile.tsx +6 -6
  65. package/templates/react-vite/src/router.tsx +13 -13
  66. package/templates/react-vite/src/types/{api.ts → api.d.ts} +6 -6
  67. package/templates/react-vite/src/types/user.d.ts +6 -0
  68. package/templates/react-vite/src/utils/helpers.ts +11 -11
  69. package/templates/react-vite/src/utils/storage.ts +4 -4
  70. package/templates/react-vite/template.json +26 -0
  71. package/templates/react-vite/tsconfig.json +1 -4
  72. package/templates/react-vite/vite.config.ts +5 -5
  73. package/dist/lib/template-composer.d.ts +0 -16
  74. package/dist/lib/template-composer.js +0 -197
  75. package/modules/auth/better-auth-express/adapters/mongoose-mongodb.ts +0 -13
  76. package/modules/auth/better-auth-express/adapters/prisma-mongodb.ts +0 -15
  77. package/modules/auth/better-auth-express/adapters/prisma-postgresql.ts +0 -15
  78. package/modules/auth/better-auth-express/files/lib/auth.ts +0 -16
  79. package/modules/auth/better-auth-express/files/routes/auth.ts +0 -12
  80. package/modules/auth/better-auth-express/files/schemas/prisma-mongodb-schema.prisma +0 -72
  81. package/modules/auth/better-auth-express/files/schemas/prisma-postgresql-schema.prisma +0 -72
  82. package/modules/auth/better-auth-express/module.json +0 -61
  83. package/modules/auth/better-auth-nextjs/adapters/mongoose-mongodb.ts +0 -24
  84. package/modules/auth/better-auth-nextjs/adapters/prisma-mongodb.ts +0 -26
  85. package/modules/auth/better-auth-nextjs/adapters/prisma-postgresql.ts +0 -26
  86. package/modules/auth/better-auth-nextjs/files/api/auth/[...all]/route.ts +0 -5
  87. package/modules/auth/better-auth-nextjs/files/lib/auth.ts +0 -26
  88. package/modules/auth/better-auth-nextjs/files/schemas/prisma-mongodb-schema.prisma +0 -72
  89. package/modules/auth/better-auth-nextjs/files/schemas/prisma-postgresql-schema.prisma +0 -72
  90. package/modules/auth/better-auth-nextjs/module.json +0 -62
  91. package/modules/auth/better-auth-react/files/lib/auth-client.ts +0 -9
  92. package/modules/auth/better-auth-react/module.json +0 -28
  93. package/modules/auth/clerk-express/module.json +0 -34
  94. package/modules/auth/clerk-nextjs/files/middleware.ts +0 -9
  95. package/modules/auth/clerk-nextjs/module.json +0 -64
  96. package/modules/auth/clerk-react/module.json +0 -28
  97. package/modules/database/prisma-mongodb/files/lib/db.ts +0 -9
  98. package/modules/database/prisma-mongodb/files/prisma/schema.prisma +0 -17
  99. package/modules/database/prisma-mongodb/module.json +0 -60
  100. package/modules/database/prisma-postgresql/files/lib/db.ts +0 -9
  101. package/modules/database/prisma-postgresql/files/prisma/schema.prisma +0 -17
  102. package/modules/database/prisma-postgresql/module.json +0 -60
@@ -11,507 +11,233 @@ 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) {
15
- console.log(chalk_1.default.bold.cyan('\n🚀 Create StackKit App\n'));
16
- // Get project configuration through wizard
20
+ // eslint-disable-next-line no-console
21
+ console.log(chalk_1.default.bold.cyan("\n Create StackKit App\n"));
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`));
22
- console.log(chalk_1.default.gray('Please choose a different name or remove the existing directory.\n'));
27
+ // eslint-disable-next-line no-console
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
- type: 'input',
34
- name: 'projectName',
35
- message: 'Project name:',
36
- default: projectName || 'my-app',
37
+ type: "input",
38
+ name: "projectName",
39
+ message: "Project name:",
40
+ default: projectName || "my-app",
37
41
  when: !projectName,
38
42
  validate: (input) => {
39
43
  const validation = (0, validate_npm_package_name_1.default)(input);
40
44
  if (!validation.validForNewPackages) {
41
- return validation.errors?.[0] || 'Invalid package name';
45
+ return validation.errors?.[0] || "Invalid package name";
42
46
  }
43
47
  if (fs_extra_1.default.existsSync(path_1.default.join(process.cwd(), input))) {
44
- return 'Directory already exists';
48
+ return "Directory already exists";
45
49
  }
46
50
  return true;
47
51
  },
48
52
  },
49
53
  {
50
- type: 'list',
51
- name: 'framework',
52
- message: 'Select framework:',
54
+ type: "list",
55
+ name: "framework",
56
+ message: "Select framework:",
53
57
  choices: [
54
- { name: 'Next.js', value: 'nextjs' },
55
- { name: 'Express.js', value: 'express' },
56
- { name: 'React (Vite)', value: 'react-vite' },
58
+ { name: "Next.js", value: "nextjs" },
59
+ { name: "Express.js", value: "express" },
60
+ { name: "React (Vite)", value: "react-vite" },
57
61
  ],
58
62
  },
59
63
  {
60
- type: 'list',
61
- name: 'database',
62
- message: 'Select database/ORM:',
63
- when: (answers) => answers.framework !== 'react-vite',
64
+ type: "list",
65
+ name: "database",
66
+ message: "Select database/ORM:",
67
+ when: (answers) => answers.framework !== "react-vite",
64
68
  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' },
69
+ { name: "Prisma", value: "prisma" },
70
+ { name: "Mongoose + MongoDB", value: "mongoose-mongodb" },
71
+ { name: "None", value: "none" },
70
72
  ],
71
73
  },
72
74
  {
73
- type: 'list',
74
- name: 'auth',
75
- message: 'Select authentication:',
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
+ },
86
+ {
87
+ type: "list",
88
+ name: "auth",
89
+ message: "Select authentication:",
90
+ when: (answers) => answers.database !== "none" || answers.framework === "react-vite",
76
91
  choices: (answers) => {
77
- if (answers.framework === 'react-vite') {
92
+ if (answers.framework === "react-vite") {
78
93
  return [
79
- { name: 'Better Auth', value: 'better-auth-react' },
80
- { name: 'Clerk', value: 'clerk-react' },
81
- { name: 'None', value: 'none' },
94
+ { name: "Better Auth", value: "better-auth" },
95
+ { name: "Clerk", value: "clerk" },
96
+ { name: "None", value: "none" },
82
97
  ];
83
98
  }
84
99
  // Next.js apps
85
- if (answers.framework === 'nextjs') {
100
+ if (answers.framework === "nextjs") {
86
101
  return [
87
- { name: 'Better Auth', value: 'better-auth-nextjs' },
88
- { name: 'Clerk', value: 'clerk-nextjs' },
89
- { name: 'None', value: 'none' },
102
+ { name: "Better Auth", value: "better-auth" },
103
+ { name: "Clerk", value: "clerk" },
104
+ { name: "None", value: "none" },
90
105
  ];
91
106
  }
92
107
  // Express apps
93
- if (answers.framework === 'express') {
108
+ if (answers.framework === "express") {
94
109
  return [
95
- { name: 'Better Auth', value: 'better-auth-express' },
96
- { name: 'Clerk', value: 'clerk-express' },
97
- { name: 'None', value: 'none' },
110
+ { name: "Better Auth", value: "better-auth" },
111
+ { name: "Clerk", value: "clerk" },
112
+ { name: "None", value: "none" },
98
113
  ];
99
114
  }
100
115
  // Default - no auth
101
- return [{ name: 'None', value: 'none' }];
116
+ return [{ name: "None", value: "none" }];
102
117
  },
103
118
  },
104
119
  {
105
- type: 'list',
106
- name: 'language',
107
- message: 'Language:',
120
+ type: "list",
121
+ name: "language",
122
+ message: "Language:",
108
123
  choices: [
109
- { name: 'TypeScript', value: 'typescript' },
110
- { name: 'JavaScript', value: 'javascript' },
124
+ { name: "TypeScript", value: "typescript" },
125
+ { name: "JavaScript", value: "javascript" },
111
126
  ],
112
- default: 'typescript',
127
+ default: "typescript",
113
128
  },
114
129
  {
115
- type: 'list',
116
- name: 'packageManager',
117
- message: 'Package manager:',
118
- choices: ['pnpm', 'npm', 'yarn'],
119
- default: 'pnpm',
130
+ type: "list",
131
+ name: "packageManager",
132
+ message: "Package manager:",
133
+ choices: [
134
+ { name: "pnpm (recommended)", value: "pnpm" },
135
+ { name: "npm", value: "npm" },
136
+ { name: "yarn", value: "yarn" },
137
+ { name: "bun", value: "bun" },
138
+ ],
139
+ default: "pnpm",
120
140
  },
121
- ]);
141
+ ]));
122
142
  return {
123
- projectName: projectName || answers.projectName,
143
+ projectName: (projectName || answers.projectName),
124
144
  framework: answers.framework,
125
- database: answers.framework === 'react-vite' ? 'none' : answers.database,
126
- auth: answers.auth,
145
+ database: (answers.framework === "react-vite"
146
+ ? "none"
147
+ : answers.database),
148
+ dbProvider: answers.dbProvider,
149
+ auth: answers.auth || "none",
127
150
  language: answers.language,
128
151
  packageManager: answers.packageManager,
129
152
  };
130
153
  }
131
154
  async function generateProject(config, targetDir) {
132
- console.log();
133
- // Copy and compose template
134
- const copySpinner = (0, ora_1.default)('Creating project files...').start();
155
+ const copySpinner = (0, ora_1.default)("Creating project files...").start();
156
+ let postInstallCommands = [];
135
157
  try {
136
- await composeTemplate(config, targetDir);
137
- copySpinner.succeed('Project files created');
158
+ postInstallCommands = await composeTemplate(config, targetDir);
159
+ copySpinner.succeed("Project files created");
138
160
  }
139
161
  catch (error) {
140
- copySpinner.fail('Failed to create project files');
162
+ copySpinner.fail("Failed to create project files");
141
163
  throw error;
142
164
  }
143
165
  // Install dependencies
144
- const installSpinner = (0, ora_1.default)('Installing dependencies...').start();
166
+ const installSpinner = (0, ora_1.default)("Installing dependencies...").start();
145
167
  try {
146
- await installDependencies(targetDir, config.packageManager);
147
- installSpinner.succeed('Dependencies installed');
168
+ await (0, package_utils_1.installDependencies)(targetDir, config.packageManager);
169
+ installSpinner.succeed("Dependencies installed");
148
170
  }
149
171
  catch (error) {
150
- installSpinner.fail('Failed to install dependencies');
172
+ installSpinner.fail("Failed to install dependencies");
151
173
  throw error;
152
174
  }
153
- // Initialize git
154
- const gitSpinner = (0, ora_1.default)('Initializing git repository...').start();
155
- try {
156
- await initGit(targetDir);
157
- gitSpinner.succeed('Git repository initialized');
158
- }
159
- catch (error) {
160
- gitSpinner.warn('Failed to initialize git repository');
161
- }
162
- }
163
- async function composeTemplate(config, targetDir) {
164
- const templatesDir = path_1.default.join(__dirname, '..', '..', 'templates');
165
- await fs_extra_1.default.ensureDir(targetDir);
166
- // 1. Copy base framework template
167
- await copyBaseFramework(templatesDir, targetDir, config.framework);
168
- // 2. Merge database configuration
169
- if (config.database !== 'none') {
170
- await mergeDatabaseConfig(templatesDir, targetDir, config.database, config.framework);
171
- }
172
- // 3. Merge auth configuration
173
- if (config.auth !== 'none') {
174
- await mergeAuthConfig(templatesDir, targetDir, config.framework, config.auth, config.database);
175
- }
176
- // 4. Update package.json with project name
177
- const packageJsonPath = path_1.default.join(targetDir, 'package.json');
178
- if (await fs_extra_1.default.pathExists(packageJsonPath)) {
179
- const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
180
- packageJson.name = config.projectName;
181
- await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
182
- }
183
- // 5. Convert to JavaScript if selected
184
- if (config.language === 'javascript') {
185
- await convertToJavaScript(targetDir);
186
- }
187
- }
188
- async function copyBaseFramework(templatesDir, targetDir, framework) {
189
- const baseDir = path_1.default.join(templatesDir, framework);
190
- if (!(await fs_extra_1.default.pathExists(baseDir))) {
191
- throw new Error(`Base template not found for framework: ${framework}\n` + `Expected at: ${baseDir}`);
192
- }
193
- await fs_extra_1.default.copy(baseDir, targetDir, {
194
- filter: (src) => {
195
- const basename = path_1.default.basename(src);
196
- return !['template.json', 'config.json', 'node_modules', '.git'].includes(basename);
197
- },
198
- });
199
- }
200
- async function mergeDatabaseConfig(templatesDir, targetDir, database, framework) {
201
- // 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);
204
- if (!(await fs_extra_1.default.pathExists(dbModulePath))) {
205
- console.warn(`Database module not found: ${database}`);
206
- return;
207
- }
208
- // Read module.json
209
- const moduleJsonPath = path_1.default.join(dbModulePath, 'module.json');
210
- if (!(await fs_extra_1.default.pathExists(moduleJsonPath))) {
211
- return;
212
- }
213
- const moduleData = await fs_extra_1.default.readJson(moduleJsonPath);
214
- // Copy files from module
215
- const filesDir = path_1.default.join(dbModulePath, 'files');
216
- if (await fs_extra_1.default.pathExists(filesDir)) {
217
- // Copy files based on patches in module.json
218
- for (const patch of moduleData.patches || []) {
219
- if (patch.type === 'create-file') {
220
- const sourceFile = path_1.default.join(filesDir, patch.source);
221
- let destFile = path_1.default.join(targetDir, patch.destination);
222
- // Simple placeholder replacement for lib
223
- destFile = destFile.replace('{{lib}}', 'lib').replace('{{src}}', 'src');
224
- if (await fs_extra_1.default.pathExists(sourceFile)) {
225
- await fs_extra_1.default.ensureDir(path_1.default.dirname(destFile));
226
- await fs_extra_1.default.copy(sourceFile, destFile, { overwrite: false });
227
- }
228
- }
229
- }
230
- }
231
- // Merge package.json with module dependencies
232
- await mergePackageJson(targetDir, {
233
- dependencies: moduleData.dependencies,
234
- devDependencies: moduleData.devDependencies,
235
- });
236
- // Merge .env with module envVars
237
- const envVars = {};
238
- for (const envVar of moduleData.envVars || []) {
239
- envVars[envVar.key] = envVar.value;
240
- }
241
- await mergeEnvFile(targetDir, envVars);
242
- // Apply framework-specific patches from database module
243
- if (moduleData.frameworkPatches) {
244
- const frameworkKey = framework === 'react-vite' ? 'react' : framework;
245
- const patches = moduleData.frameworkPatches[frameworkKey];
246
- if (patches) {
247
- await applyFrameworkPatches(targetDir, patches);
248
- }
249
- }
250
- }
251
- async function mergeAuthConfig(templatesDir, targetDir, framework, auth, database = 'none') {
252
- // Use modules directory (sibling to templates)
253
- const modulesDir = path_1.default.join(templatesDir, '..', 'modules');
254
- // Auth modules are now named with framework suffix
255
- // e.g., better-auth-nextjs, authjs-express, better-auth-react
256
- // If auth already has framework suffix, use it directly
257
- // Otherwise, map old names to new ones
258
- 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',
266
- };
267
- const authKey = auth.includes('-') ? auth : authMap[auth] || auth;
268
- const authModulePath = path_1.default.join(modulesDir, 'auth', authKey);
269
- if (!(await fs_extra_1.default.pathExists(authModulePath))) {
270
- console.warn(`Auth module not found: ${authKey}`);
271
- return;
272
- }
273
- // Read module.json
274
- const moduleJsonPath = path_1.default.join(authModulePath, 'module.json');
275
- if (!(await fs_extra_1.default.pathExists(moduleJsonPath))) {
276
- return;
277
- }
278
- const moduleData = await fs_extra_1.default.readJson(moduleJsonPath);
279
- // Copy files from module
280
- const filesDir = path_1.default.join(authModulePath, 'files');
281
- if (await fs_extra_1.default.pathExists(filesDir)) {
282
- // Determine path replacements based on framework
283
- const getReplacements = () => {
284
- if (framework === 'nextjs') {
285
- return { lib: 'lib', router: 'app' };
286
- }
287
- else if (framework === 'express') {
288
- return { lib: 'src', router: 'src' };
289
- }
290
- else {
291
- return { lib: 'src', router: 'src' };
292
- }
293
- };
294
- const replacements = getReplacements();
295
- // Copy files based on patches in module.json
296
- for (const patch of moduleData.patches || []) {
297
- if (patch.type === 'create-file') {
298
- const sourceFile = path_1.default.join(filesDir, patch.source);
299
- let destFile = path_1.default.join(targetDir, patch.destination);
300
- // Replace placeholders
301
- destFile = destFile
302
- .replace('{{lib}}', replacements.lib)
303
- .replace('{{router}}', replacements.router);
304
- if (await fs_extra_1.default.pathExists(sourceFile)) {
305
- await fs_extra_1.default.ensureDir(path_1.default.dirname(destFile));
306
- await fs_extra_1.default.copy(sourceFile, destFile, { overwrite: false });
307
- }
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" });
308
181
  }
182
+ postInstallSpinner.succeed("Post-install commands completed");
309
183
  }
310
- }
311
- // Handle database-specific adapters and schemas
312
- if (database !== 'none' && moduleData.databaseAdapters) {
313
- const adapterConfig = moduleData.databaseAdapters[database];
314
- if (adapterConfig) {
315
- // Copy adapter file
316
- if (adapterConfig.adapter) {
317
- const adapterSource = path_1.default.join(authModulePath, adapterConfig.adapter);
318
- const adapterFileName = path_1.default.basename(adapterConfig.adapter);
319
- // Determine destination based on framework
320
- let adapterDest;
321
- if (framework === 'nextjs') {
322
- adapterDest = path_1.default.join(targetDir, 'lib', 'auth.ts');
323
- }
324
- else if (framework === 'express') {
325
- adapterDest = path_1.default.join(targetDir, 'src', 'auth.ts');
326
- }
327
- else {
328
- adapterDest = path_1.default.join(targetDir, 'src', 'lib', 'auth.ts');
329
- }
330
- if (await fs_extra_1.default.pathExists(adapterSource)) {
331
- await fs_extra_1.default.ensureDir(path_1.default.dirname(adapterDest));
332
- await fs_extra_1.default.copy(adapterSource, adapterDest, { overwrite: true });
333
- }
334
- }
335
- // Copy schema file if it exists
336
- if (adapterConfig.schema && adapterConfig.schemaDestination) {
337
- const schemaSource = path_1.default.join(authModulePath, adapterConfig.schema);
338
- const schemaDest = path_1.default.join(targetDir, adapterConfig.schemaDestination);
339
- if (await fs_extra_1.default.pathExists(schemaSource)) {
340
- await fs_extra_1.default.ensureDir(path_1.default.dirname(schemaDest));
341
- await fs_extra_1.default.copy(schemaSource, schemaDest, { overwrite: true });
342
- }
343
- }
344
- // Merge adapter-specific dependencies
345
- if (adapterConfig.dependencies) {
346
- await mergePackageJson(targetDir, {
347
- dependencies: adapterConfig.dependencies,
348
- });
349
- }
184
+ catch (error) {
185
+ postInstallSpinner.fail("Failed to run post-install commands");
186
+ throw error;
350
187
  }
351
188
  }
352
- // Merge package.json with module dependencies
353
- await mergePackageJson(targetDir, {
354
- dependencies: moduleData.dependencies,
355
- devDependencies: moduleData.devDependencies,
356
- });
357
- // Merge .env with module envVars
358
- const envVars = {};
359
- for (const envVar of moduleData.envVars || []) {
360
- envVars[envVar.key] = envVar.value;
361
- }
362
- await mergeEnvFile(targetDir, envVars);
363
- }
364
- async function mergePackageJson(targetDir, config) {
365
- const pkgPath = path_1.default.join(targetDir, 'package.json');
366
- if (!(await fs_extra_1.default.pathExists(pkgPath))) {
367
- return;
368
- }
369
- const pkg = await fs_extra_1.default.readJson(pkgPath);
370
- if (config.dependencies) {
371
- pkg.dependencies = { ...pkg.dependencies, ...config.dependencies };
372
- }
373
- if (config.devDependencies) {
374
- pkg.devDependencies = { ...pkg.devDependencies, ...config.devDependencies };
189
+ // Initialize git
190
+ const gitSpinner = (0, ora_1.default)("Initializing git repository...").start();
191
+ try {
192
+ await (0, git_utils_1.initGit)(targetDir);
193
+ gitSpinner.succeed("Git repository initialized");
375
194
  }
376
- if (config.scripts) {
377
- pkg.scripts = { ...pkg.scripts, ...config.scripts };
195
+ catch {
196
+ gitSpinner.warn("Failed to initialize git repository");
378
197
  }
379
- await fs_extra_1.default.writeJson(pkgPath, pkg, { spaces: 2 });
380
198
  }
381
- async function mergeEnvFile(targetDir, envVars) {
382
- if (Object.keys(envVars).length === 0) {
383
- return;
384
- }
385
- const envExamplePath = path_1.default.join(targetDir, '.env.example');
386
- const envPath = path_1.default.join(targetDir, '.env');
387
- const envContent = Object.entries(envVars)
388
- .map(([key, value]) => `${key}="${value}"`)
389
- .join('\n') + '\n';
390
- // Update .env.example
391
- 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]);
394
- const newVars = Object.keys(envVars).filter((key) => !existingKeys.includes(key));
395
- 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');
199
+ async function composeTemplate(config, targetDir) {
200
+ const templatesDir = path_1.default.join(__dirname, "..", "..", "templates");
201
+ await fs_extra_1.default.ensureDir(targetDir);
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);
398
210
  }
399
211
  }
400
- else {
401
- await fs_extra_1.default.writeFile(envExamplePath, envContent);
212
+ catch {
213
+ // non-fatal
402
214
  }
403
- // Create .env if doesn't exist
404
- if (!(await fs_extra_1.default.pathExists(envPath))) {
405
- await fs_extra_1.default.writeFile(envPath, envContent);
215
+ const postInstallCommands = [];
216
+ if (config.database !== "none") {
217
+ const dbPostInstall = await (0, module_utils_1.mergeDatabaseConfig)(templatesDir, targetDir, config.database, config.framework, config.dbProvider);
218
+ postInstallCommands.push(...dbPostInstall);
406
219
  }
407
- }
408
- async function convertToJavaScript(targetDir) {
409
- // Remove TypeScript config files
410
- const tsFiles = ['tsconfig.json', 'next-env.d.ts'];
411
- for (const file of tsFiles) {
412
- const filePath = path_1.default.join(targetDir, file);
413
- if (await fs_extra_1.default.pathExists(filePath)) {
414
- await fs_extra_1.default.remove(filePath);
415
- }
220
+ if (config.auth !== "none") {
221
+ await (0, module_utils_1.mergeAuthConfig)(templatesDir, targetDir, config.framework, config.auth, config.database, config.dbProvider);
416
222
  }
417
- // Rename .ts(x) files to .js(x)
418
- const renameExtensions = async (dir) => {
419
- const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
420
- for (const entry of entries) {
421
- const fullPath = path_1.default.join(dir, entry.name);
422
- if (entry.isDirectory() && entry.name !== 'node_modules') {
423
- await renameExtensions(fullPath);
424
- }
425
- 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'));
431
- }
432
- }
433
- }
434
- };
435
- await renameExtensions(targetDir);
436
- // Update package.json to remove TypeScript dependencies
437
- const packageJsonPath = path_1.default.join(targetDir, 'package.json');
223
+ const packageJsonPath = path_1.default.join(targetDir, "package.json");
438
224
  if (await fs_extra_1.default.pathExists(packageJsonPath)) {
439
225
  const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
440
- 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'];
445
- }
226
+ packageJson.name = config.projectName;
446
227
  await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
447
228
  }
448
- }
449
- async function installDependencies(cwd, packageManager) {
450
- const commands = {
451
- npm: 'npm install',
452
- yarn: 'yarn install',
453
- pnpm: 'pnpm install',
454
- };
455
- const command = commands[packageManager];
456
- if (!command) {
457
- throw new Error(`Unsupported package manager: ${packageManager}`);
458
- }
459
- (0, child_process_1.execSync)(command, {
460
- cwd,
461
- stdio: 'pipe',
462
- });
463
- }
464
- async function initGit(cwd) {
465
- 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' });
469
- (0, child_process_1.execSync)('git commit -m "Initial commit from create-stackkit-app"', {
470
- cwd,
471
- stdio: 'pipe',
472
- });
473
- }
474
- catch (error) {
475
- throw new Error('Git initialization failed');
476
- }
477
- }
478
- async function applyFrameworkPatches(targetDir, patches) {
479
- for (const [filename, patchConfig] of Object.entries(patches)) {
480
- const filePath = path_1.default.join(targetDir, filename);
481
- if (await fs_extra_1.default.pathExists(filePath)) {
482
- const fileContent = await fs_extra_1.default.readJson(filePath);
483
- if (patchConfig.merge) {
484
- // Deep merge configuration
485
- const merged = deepMerge(fileContent, patchConfig.merge);
486
- await fs_extra_1.default.writeJson(filePath, merged, { spaces: 2 });
487
- }
488
- }
489
- }
490
- }
491
- function deepMerge(target, source) {
492
- const output = { ...target };
493
- for (const key in source) {
494
- if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
495
- if (target[key]) {
496
- output[key] = deepMerge(target[key], source[key]);
497
- }
498
- else {
499
- output[key] = source[key];
500
- }
501
- }
502
- else if (Array.isArray(source[key])) {
503
- // For arrays, merge uniquely
504
- output[key] = Array.from(new Set([...(target[key] || []), ...source[key]]));
505
- }
506
- else {
507
- output[key] = source[key];
508
- }
229
+ if (config.language === "javascript") {
230
+ await (0, js_conversion_1.convertToJavaScript)(targetDir, config.framework);
509
231
  }
510
- return output;
232
+ return postInstallCommands;
511
233
  }
512
234
  function showNextSteps(config) {
235
+ // eslint-disable-next-line no-console
513
236
  console.log(chalk_1.default.green.bold(`\n✓ Created ${config.projectName}\n`));
514
- console.log(chalk_1.default.bold('Next steps:'));
237
+ // eslint-disable-next-line no-console
238
+ console.log(chalk_1.default.bold("Next steps:"));
239
+ // eslint-disable-next-line no-console
515
240
  console.log(chalk_1.default.cyan(` cd ${config.projectName}`));
516
- console.log(chalk_1.default.cyan(` ${config.packageManager}${config.packageManager === 'npm' ? ' run' : ''} dev\n`));
241
+ // eslint-disable-next-line no-console
242
+ console.log(chalk_1.default.cyan(` ${config.packageManager} run dev\n`));
517
243
  }
@@ -0,0 +1,2 @@
1
+ export declare function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown>;
2
+ export declare function applyFrameworkPatches(targetDir: string, patches: Record<string, unknown>): Promise<void>;