stackkit 0.1.0

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 (121) hide show
  1. package/README.md +41 -0
  2. package/bin/stackkit.js +4 -0
  3. package/dist/cli/add.d.ts +8 -0
  4. package/dist/cli/add.js +313 -0
  5. package/dist/cli/create.d.ts +22 -0
  6. package/dist/cli/create.js +336 -0
  7. package/dist/cli/doctor.d.ts +7 -0
  8. package/dist/cli/doctor.js +569 -0
  9. package/dist/cli/list.d.ts +6 -0
  10. package/dist/cli/list.js +123 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +91 -0
  13. package/dist/lib/conversion/js-conversion.d.ts +1 -0
  14. package/dist/lib/conversion/js-conversion.js +244 -0
  15. package/dist/lib/database/database-config.d.ts +6 -0
  16. package/dist/lib/database/database-config.js +9 -0
  17. package/dist/lib/discovery/module-discovery.d.ts +62 -0
  18. package/dist/lib/discovery/module-discovery.js +188 -0
  19. package/dist/lib/env/env-editor.d.ts +9 -0
  20. package/dist/lib/env/env-editor.js +116 -0
  21. package/dist/lib/framework/framework-utils.d.ts +22 -0
  22. package/dist/lib/framework/framework-utils.js +74 -0
  23. package/dist/lib/fs/files.d.ts +14 -0
  24. package/dist/lib/fs/files.js +101 -0
  25. package/dist/lib/generation/code-generator.d.ts +83 -0
  26. package/dist/lib/generation/code-generator.js +681 -0
  27. package/dist/lib/git-utils.d.ts +1 -0
  28. package/dist/lib/git-utils.js +9 -0
  29. package/dist/lib/pm/package-manager.d.ts +5 -0
  30. package/dist/lib/pm/package-manager.js +69 -0
  31. package/dist/lib/project/detect.d.ts +4 -0
  32. package/dist/lib/project/detect.js +121 -0
  33. package/dist/lib/ui/logger.d.ts +16 -0
  34. package/dist/lib/ui/logger.js +59 -0
  35. package/dist/types/index.d.ts +92 -0
  36. package/dist/types/index.js +2 -0
  37. package/modules/auth/authjs/files/api/auth/[...nextauth]/route.ts +6 -0
  38. package/modules/auth/authjs/files/lib/auth-client.ts +11 -0
  39. package/modules/auth/authjs/files/lib/auth.ts +36 -0
  40. package/modules/auth/authjs/files/schemas/prisma-schema.prisma +45 -0
  41. package/modules/auth/authjs/module.json +22 -0
  42. package/modules/auth/better-auth/files/api/auth/[...all]/route.ts +4 -0
  43. package/modules/auth/better-auth/files/lib/auth-client.ts +7 -0
  44. package/modules/auth/better-auth/files/lib/auth.ts +83 -0
  45. package/modules/auth/better-auth/files/lib/email-service.ts +34 -0
  46. package/modules/auth/better-auth/files/lib/email-templates.ts +89 -0
  47. package/modules/auth/better-auth/files/prisma/schema.prisma +63 -0
  48. package/modules/auth/better-auth/generator.json +78 -0
  49. package/modules/auth/better-auth/module.json +37 -0
  50. package/modules/database/mongoose/files/lib/db.ts +63 -0
  51. package/modules/database/mongoose/files/models/User.ts +34 -0
  52. package/modules/database/mongoose/generator.json +24 -0
  53. package/modules/database/mongoose/module.json +15 -0
  54. package/modules/database/prisma/files/lib/prisma.ts +45 -0
  55. package/modules/database/prisma/files/prisma/schema.prisma +8 -0
  56. package/modules/database/prisma/files/prisma.config.ts +12 -0
  57. package/modules/database/prisma/generator.json +43 -0
  58. package/modules/database/prisma/module.json +17 -0
  59. package/package.json +83 -0
  60. package/templates/express/.env.example +2 -0
  61. package/templates/express/eslint.config.cjs +42 -0
  62. package/templates/express/package.json +33 -0
  63. package/templates/express/src/app.ts +51 -0
  64. package/templates/express/src/config/env.ts +12 -0
  65. package/templates/express/src/features/health/health.controller.ts +18 -0
  66. package/templates/express/src/features/health/health.route.ts +9 -0
  67. package/templates/express/src/features/health/health.service.ts +6 -0
  68. package/templates/express/src/middlewares/error.middleware.ts +18 -0
  69. package/templates/express/src/server.ts +8 -0
  70. package/templates/express/template.json +27 -0
  71. package/templates/express/tsconfig.json +30 -0
  72. package/templates/nextjs/README.md +52 -0
  73. package/templates/nextjs/app/favicon.ico +0 -0
  74. package/templates/nextjs/app/globals.css +26 -0
  75. package/templates/nextjs/app/layout.tsx +30 -0
  76. package/templates/nextjs/app/page.tsx +57 -0
  77. package/templates/nextjs/eslint.config.mjs +18 -0
  78. package/templates/nextjs/lib/env.ts +8 -0
  79. package/templates/nextjs/next.config.ts +7 -0
  80. package/templates/nextjs/package.json +27 -0
  81. package/templates/nextjs/postcss.config.mjs +7 -0
  82. package/templates/nextjs/public/file.svg +1 -0
  83. package/templates/nextjs/public/globe.svg +1 -0
  84. package/templates/nextjs/public/next.svg +1 -0
  85. package/templates/nextjs/public/vercel.svg +1 -0
  86. package/templates/nextjs/public/window.svg +1 -0
  87. package/templates/nextjs/template.json +34 -0
  88. package/templates/nextjs/tsconfig.json +34 -0
  89. package/templates/react/.env.example +1 -0
  90. package/templates/react/.prettierignore +4 -0
  91. package/templates/react/.prettierrc +9 -0
  92. package/templates/react/README.md +56 -0
  93. package/templates/react/eslint.config.js +23 -0
  94. package/templates/react/index.html +14 -0
  95. package/templates/react/package.json +44 -0
  96. package/templates/react/public/vite.svg +1 -0
  97. package/templates/react/src/api/client.ts +47 -0
  98. package/templates/react/src/assets/react.svg +1 -0
  99. package/templates/react/src/components/ErrorBoundary.tsx +51 -0
  100. package/templates/react/src/components/Layout.tsx +13 -0
  101. package/templates/react/src/components/Loading.tsx +8 -0
  102. package/templates/react/src/components/SEO.tsx +49 -0
  103. package/templates/react/src/config/constants.ts +5 -0
  104. package/templates/react/src/hooks/index.ts +64 -0
  105. package/templates/react/src/index.css +1 -0
  106. package/templates/react/src/lib/queryClient.ts +12 -0
  107. package/templates/react/src/main.tsx +22 -0
  108. package/templates/react/src/pages/About.tsx +78 -0
  109. package/templates/react/src/pages/Home.tsx +49 -0
  110. package/templates/react/src/pages/NotFound.tsx +24 -0
  111. package/templates/react/src/router.tsx +21 -0
  112. package/templates/react/src/types/api.d.ts +20 -0
  113. package/templates/react/src/utils/helpers.ts +51 -0
  114. package/templates/react/src/utils/storage.ts +35 -0
  115. package/templates/react/src/vite-env.d.ts +11 -0
  116. package/templates/react/template.json +38 -0
  117. package/templates/react/tsconfig.app.json +28 -0
  118. package/templates/react/tsconfig.json +4 -0
  119. package/templates/react/tsconfig.node.json +26 -0
  120. package/templates/react/vite.config.ts +7 -0
  121. package/templates/react-vite/README.md +56 -0
@@ -0,0 +1,336 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createProject = createProject;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const child_process_1 = require("child_process");
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const inquirer_1 = __importDefault(require("inquirer"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const validate_npm_package_name_1 = __importDefault(require("validate-npm-package-name"));
13
+ const git_utils_1 = require("../lib/git-utils");
14
+ const js_conversion_1 = require("../lib/conversion/js-conversion");
15
+ const package_manager_1 = require("../lib/pm/package-manager");
16
+ const logger_1 = require("../lib/ui/logger");
17
+ const framework_utils_1 = require("../lib/framework/framework-utils");
18
+ const module_discovery_1 = require("../lib/discovery/module-discovery");
19
+ const code_generator_1 = require("../lib/generation/code-generator");
20
+ async function createProject(projectName, options) {
21
+ logger_1.logger.newLine();
22
+ logger_1.logger.log(chalk_1.default.bold.cyan("📦 Create StackKit App"));
23
+ logger_1.logger.newLine();
24
+ const config = await getProjectConfig(projectName, options);
25
+ const targetDir = path_1.default.join(process.cwd(), config.projectName);
26
+ if (await fs_extra_1.default.pathExists(targetDir)) {
27
+ logger_1.logger.error(`Directory "${config.projectName}" already exists`);
28
+ logger_1.logger.log(chalk_1.default.gray("Please choose a different name or remove the existing directory.\n"));
29
+ process.exit(1);
30
+ }
31
+ await generateProject(config, targetDir, options);
32
+ showNextSteps(config);
33
+ }
34
+ async function getProjectConfig(projectName, options) {
35
+ // Resolve modules directory (dist first, then package root)
36
+ const modulesCandidates = [
37
+ path_1.default.join(__dirname, "..", "..", "..", "modules"),
38
+ path_1.default.join(__dirname, "..", "..", "..", "..", "modules"),
39
+ ];
40
+ let modulesDir;
41
+ for (const c of modulesCandidates) {
42
+ if (await fs_extra_1.default.pathExists(c)) {
43
+ modulesDir = c;
44
+ break;
45
+ }
46
+ }
47
+ const discoveredModules = await (0, module_discovery_1.discoverModules)(modulesDir || modulesCandidates[1]);
48
+ // Detect flags or `--yes` after `create`; project-name-only remains interactive
49
+ const argv = process.argv.slice(2);
50
+ const createIndex = argv.indexOf('create');
51
+ const argsAfterCreate = createIndex >= 0 ? argv.slice(createIndex + 1) : [];
52
+ const flagsProvided = argsAfterCreate.some(arg => arg.startsWith('-'));
53
+ const optionsProvided = flagsProvided || !!(options && (options.yes || options.y));
54
+ if (optionsProvided) {
55
+ if ((options && (options.yes || options.y))) {
56
+ return {
57
+ projectName: projectName || "my-app",
58
+ framework: "nextjs",
59
+ database: "prisma",
60
+ prismaProvider: "postgresql",
61
+ auth: "better-auth",
62
+ language: "typescript",
63
+ packageManager: "pnpm",
64
+ };
65
+ }
66
+ // Validate options using discovered modules (if any discovered)
67
+ const framework = (options && (options.framework || options.f)) || undefined;
68
+ if (discoveredModules.frameworks && discoveredModules.frameworks.length > 0) {
69
+ const validFrameworks = discoveredModules.frameworks.map(f => f.name);
70
+ if (framework && !validFrameworks.includes(framework)) {
71
+ throw new Error(`Invalid framework: ${framework}. Valid options: ${validFrameworks.join(', ')}`);
72
+ }
73
+ }
74
+ const db = (options && (options.database || options.d)) || undefined;
75
+ let allValidDatabases = [];
76
+ if (discoveredModules.databases && discoveredModules.databases.length > 0) {
77
+ const validDatabases = (0, module_discovery_1.getValidDatabaseOptions)(discoveredModules.databases);
78
+ // Also allow base database names like 'prisma', 'mongoose'
79
+ const validBaseDatabases = discoveredModules.databases.map(db => db.name);
80
+ allValidDatabases = [...validDatabases, ...validBaseDatabases];
81
+ if (db && !allValidDatabases.includes(db)) {
82
+ throw new Error(`Invalid database: ${db}. Valid options: ${allValidDatabases.filter((v, i, arr) => arr.indexOf(v) === i).join(', ')}`);
83
+ }
84
+ }
85
+ const authOpt = (options && (options.auth || options.a)) || undefined;
86
+ if (discoveredModules.auth && discoveredModules.auth.length > 0) {
87
+ const validAuth = (0, module_discovery_1.getValidAuthOptions)(discoveredModules.auth);
88
+ if (authOpt && !validAuth.includes(authOpt)) {
89
+ throw new Error(`Invalid auth: ${authOpt}. Valid options: ${validAuth.join(', ')}`);
90
+ }
91
+ }
92
+ const validLanguages = ['typescript', 'javascript'];
93
+ const language = (options && (options.language || options.l)) || undefined;
94
+ if (language && !validLanguages.includes(language)) {
95
+ throw new Error(`Invalid language: ${language}. Valid options: ${validLanguages.join(', ')}`);
96
+ }
97
+ const validPackageManagers = ['pnpm', 'npm', 'yarn', 'bun'];
98
+ const pm = (options && (options.packageManager || options.p)) || undefined;
99
+ if (pm && !validPackageManagers.includes(pm)) {
100
+ throw new Error(`Invalid package manager: ${pm}. Valid options: ${validPackageManagers.join(', ')}`);
101
+ }
102
+ let database = "none";
103
+ let prismaProvider;
104
+ if (db && db !== "none") {
105
+ const parsed = (0, module_discovery_1.parseDatabaseOption)(db);
106
+ database = parsed.database;
107
+ prismaProvider = parsed.provider;
108
+ }
109
+ let auth = "none";
110
+ if (authOpt && authOpt !== "none") {
111
+ auth = authOpt;
112
+ }
113
+ const finalFramework = (framework || "nextjs");
114
+ // Validate auth compatibility
115
+ if (auth === "authjs" && (database !== "prisma" || finalFramework !== "nextjs")) {
116
+ throw new Error("Auth.js is only supported with Next.js and Prisma database");
117
+ }
118
+ if (auth === "better-auth" && database === "none" && finalFramework !== "react") {
119
+ throw new Error("Better Auth requires a database for server frameworks");
120
+ }
121
+ return {
122
+ projectName: projectName || "my-app",
123
+ framework: finalFramework,
124
+ database,
125
+ prismaProvider,
126
+ auth,
127
+ language: (language || "typescript"),
128
+ packageManager: (pm || "pnpm"),
129
+ };
130
+ }
131
+ // Interactive prompts
132
+ const answers = (await inquirer_1.default.prompt([
133
+ {
134
+ type: "input",
135
+ name: "projectName",
136
+ message: "Project name:",
137
+ default: projectName || "my-app",
138
+ when: !projectName,
139
+ validate: (input) => {
140
+ const validation = (0, validate_npm_package_name_1.default)(input);
141
+ if (!validation.validForNewPackages) {
142
+ return validation.errors?.[0] || "Invalid package name";
143
+ }
144
+ if (fs_extra_1.default.existsSync(path_1.default.join(process.cwd(), input))) {
145
+ return "Directory already exists";
146
+ }
147
+ return true;
148
+ },
149
+ },
150
+ {
151
+ type: "list",
152
+ name: "framework",
153
+ message: "Select framework:",
154
+ choices: discoveredModules.frameworks.map(f => ({
155
+ name: f.displayName,
156
+ value: f.name
157
+ })),
158
+ },
159
+ {
160
+ type: "list",
161
+ name: "database",
162
+ message: "Select database/ORM:",
163
+ when: (answers) => answers.framework !== "react",
164
+ choices: [
165
+ { name: "Prisma", value: "prisma" },
166
+ { name: "Mongoose", value: "mongoose" },
167
+ { name: "None", value: "none" },
168
+ ],
169
+ },
170
+ {
171
+ type: "list",
172
+ name: "prismaProvider",
173
+ message: "Select database provider for Prisma:",
174
+ when: (answers) => answers.database === "prisma",
175
+ choices: [
176
+ { name: "PostgreSQL", value: "postgresql" },
177
+ { name: "MongoDB", value: "mongodb" },
178
+ { name: "MySQL", value: "mysql" },
179
+ { name: "SQLite", value: "sqlite" },
180
+ ],
181
+ },
182
+ {
183
+ type: "list",
184
+ name: "auth",
185
+ message: "Select authentication:",
186
+ when: (answers) => (answers.database !== "none" || answers.framework === "react"),
187
+ choices: (answers) => (0, module_discovery_1.getCompatibleAuthOptions)(discoveredModules.auth, answers.framework, answers.database || "none"),
188
+ },
189
+ {
190
+ type: "list",
191
+ name: "language",
192
+ message: "Language:",
193
+ choices: [
194
+ { name: "TypeScript", value: "typescript" },
195
+ { name: "JavaScript", value: "javascript" },
196
+ ],
197
+ default: "typescript",
198
+ },
199
+ {
200
+ type: "list",
201
+ name: "packageManager",
202
+ message: "Package manager:",
203
+ choices: [
204
+ { name: "pnpm (recommended)", value: "pnpm" },
205
+ { name: "npm", value: "npm" },
206
+ { name: "yarn", value: "yarn" },
207
+ { name: "bun", value: "bun" },
208
+ ],
209
+ default: "pnpm",
210
+ },
211
+ ]));
212
+ return {
213
+ projectName: (projectName || answers.projectName),
214
+ framework: answers.framework,
215
+ database: (answers.framework === "react"
216
+ ? "none"
217
+ : answers.database),
218
+ prismaProvider: answers.prismaProvider,
219
+ auth: (answers.auth || "none"),
220
+ language: answers.language,
221
+ packageManager: answers.packageManager,
222
+ };
223
+ }
224
+ async function generateProject(config, targetDir, options) {
225
+ const copySpinner = logger_1.logger.startSpinner("Creating project files...");
226
+ let postInstallCommands = [];
227
+ try {
228
+ postInstallCommands = await composeTemplate(config, targetDir);
229
+ copySpinner.succeed("Project files created");
230
+ }
231
+ catch (error) {
232
+ copySpinner.fail("Failed to create project files");
233
+ throw error;
234
+ }
235
+ // Install dependencies
236
+ if (options?.install !== false && !(options?.['skip-install'] || options?.skipInstall)) {
237
+ const installSpinner = logger_1.logger.startSpinner("Installing dependencies...");
238
+ try {
239
+ await (0, package_manager_1.installDependencies)(targetDir, config.packageManager);
240
+ installSpinner.succeed("Dependencies installed");
241
+ }
242
+ catch (error) {
243
+ installSpinner.fail("Failed to install dependencies");
244
+ throw error;
245
+ }
246
+ }
247
+ // Run post-install commands (skip if install was skipped)
248
+ if (postInstallCommands.length > 0 && options?.install !== false && !(options?.['skip-install'] || options?.skipInstall)) {
249
+ const postInstallSpinner = logger_1.logger.startSpinner("Running post-install commands...");
250
+ try {
251
+ for (const command of postInstallCommands) {
252
+ (0, child_process_1.execSync)(command, { cwd: targetDir, stdio: "pipe" });
253
+ }
254
+ postInstallSpinner.succeed("Post-install commands completed");
255
+ }
256
+ catch (error) {
257
+ postInstallSpinner.fail("Failed to run post-install commands");
258
+ throw error;
259
+ }
260
+ }
261
+ // Initialize git
262
+ if (options?.git !== false && !(options?.['no-git'] || options?.noGit)) {
263
+ const gitSpinner = logger_1.logger.startSpinner("Initializing git repository...");
264
+ try {
265
+ await (0, git_utils_1.initGit)(targetDir);
266
+ gitSpinner.succeed("Git repository initialized");
267
+ }
268
+ catch {
269
+ gitSpinner.warn("Failed to initialize git repository");
270
+ }
271
+ }
272
+ }
273
+ async function composeTemplate(config, targetDir) {
274
+ // Resolve templates/modules paths
275
+ const templatesCandidates = [
276
+ path_1.default.join(__dirname, "..", "..", "..", "templates"),
277
+ path_1.default.join(__dirname, "..", "..", "..", "..", "templates"),
278
+ ];
279
+ const modulesCandidates2 = [
280
+ path_1.default.join(__dirname, "..", "..", "..", "modules"),
281
+ path_1.default.join(__dirname, "..", "..", "..", "..", "modules"),
282
+ ];
283
+ const templatesDir = (await (async () => { for (const c of templatesCandidates)
284
+ if (await fs_extra_1.default.pathExists(c))
285
+ return c; return templatesCandidates[1]; })());
286
+ const modulesDirForGenerator = (await (async () => { for (const c of modulesCandidates2)
287
+ if (await fs_extra_1.default.pathExists(c))
288
+ return c; return modulesCandidates2[1]; })());
289
+ await fs_extra_1.default.ensureDir(targetDir);
290
+ // Load framework configuration
291
+ const frameworkConfig = await framework_utils_1.FrameworkUtils.loadFrameworkConfig(config.framework, templatesDir);
292
+ // Initialize advanced code generator
293
+ const generator = new code_generator_1.AdvancedCodeGenerator(frameworkConfig);
294
+ await generator.loadGenerators(modulesDirForGenerator);
295
+ // Generate project using advanced code generator
296
+ const features = [];
297
+ const postInstallCommands = await generator.generate({
298
+ framework: config.framework,
299
+ database: config.database === 'none' ? undefined : config.database,
300
+ auth: config.auth === 'none' ? undefined : config.auth,
301
+ prismaProvider: config.prismaProvider,
302
+ }, features, targetDir);
303
+ // Update project name in package.json
304
+ const packageJsonPath = path_1.default.join(targetDir, "package.json");
305
+ if (await fs_extra_1.default.pathExists(packageJsonPath)) {
306
+ const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
307
+ packageJson.name = config.projectName;
308
+ await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
309
+ }
310
+ // Ensure .env exists: if .env.example was copied from the template, create .env from it
311
+ try {
312
+ const envExamplePath = path_1.default.join(targetDir, ".env.example");
313
+ const envPath = path_1.default.join(targetDir, ".env");
314
+ if ((await fs_extra_1.default.pathExists(envExamplePath)) && !(await fs_extra_1.default.pathExists(envPath))) {
315
+ const envContent = await fs_extra_1.default.readFile(envExamplePath, "utf-8");
316
+ await fs_extra_1.default.writeFile(envPath, envContent);
317
+ }
318
+ }
319
+ catch {
320
+ // non-fatal
321
+ }
322
+ if (config.language === "javascript") {
323
+ await (0, js_conversion_1.convertToJavaScript)(targetDir, config.framework);
324
+ }
325
+ // For now, return empty array as post-install commands are handled by the generator
326
+ return postInstallCommands;
327
+ }
328
+ function showNextSteps(config) {
329
+ logger_1.logger.newLine();
330
+ logger_1.logger.success(`Created ${config.projectName}`);
331
+ logger_1.logger.newLine();
332
+ logger_1.logger.log("Next steps:");
333
+ logger_1.logger.log(` cd ${config.projectName}`);
334
+ logger_1.logger.log(` ${config.packageManager} run dev`);
335
+ logger_1.logger.newLine();
336
+ }
@@ -0,0 +1,7 @@
1
+ interface DoctorOptions {
2
+ json?: boolean;
3
+ verbose?: boolean;
4
+ strict?: boolean;
5
+ }
6
+ export declare function doctorCommand(options: DoctorOptions): Promise<void>;
7
+ export {};