stackkit 0.2.0 → 0.2.2

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.
package/README.md CHANGED
@@ -2,8 +2,7 @@
2
2
 
3
3
  CLI for scaffolding and composing modular JavaScript applications.
4
4
 
5
- Quick Start
6
- -----------
5
+ ## Quick Start
7
6
 
8
7
  Create a new project without installing globally:
9
8
 
@@ -23,15 +22,13 @@ Check project health:
23
22
  npx stackkit@latest doctor
24
23
  ```
25
24
 
26
- Supported technologies
27
- ----------------------
25
+ ## Supported technologies
28
26
 
29
27
  - Frameworks: Next.js, React, Express
30
28
  - Databases: Prisma, Mongoose
31
29
  - Auth: Auth.js / Better Auth
32
30
 
33
- Links
34
- -----
31
+ ## Links
35
32
 
36
33
  - Website: https://stackkit.tariqul.dev
37
- - Repo: https://github.com/tariqul420/stackkit
34
+ - Repo: https://github.com/tariqul420/stackkit
package/dist/cli/add.js CHANGED
@@ -9,14 +9,16 @@ const child_process_1 = require("child_process");
9
9
  const fs_extra_1 = __importDefault(require("fs-extra"));
10
10
  const inquirer_1 = __importDefault(require("inquirer"));
11
11
  const path_1 = __importDefault(require("path"));
12
- const code_generator_1 = require("../lib/generation/code-generator");
13
- const detect_1 = require("../lib/project/detect");
12
+ const module_discovery_1 = require("../lib/discovery/module-discovery");
13
+ const shared_1 = require("../lib/discovery/shared");
14
14
  const env_editor_1 = require("../lib/env/env-editor");
15
+ const framework_utils_1 = require("../lib/framework/framework-utils");
15
16
  const files_1 = require("../lib/fs/files");
16
- const logger_1 = require("../lib/ui/logger");
17
+ const code_generator_1 = require("../lib/generation/code-generator");
17
18
  const package_manager_1 = require("../lib/pm/package-manager");
19
+ const detect_1 = require("../lib/project/detect");
20
+ const logger_1 = require("../lib/ui/logger");
18
21
  const package_root_1 = require("../lib/utils/package-root");
19
- const framework_utils_1 = require("../lib/framework/framework-utils");
20
22
  async function addCommand(module, options) {
21
23
  try {
22
24
  const projectRoot = process.cwd();
@@ -39,13 +41,9 @@ async function addCommand(module, options) {
39
41
  }
40
42
  async function getAddConfig(module, options, projectInfo) {
41
43
  const modulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(), "modules");
42
- // If no module provided, go interactive
43
44
  if (!module) {
44
45
  return await getInteractiveConfig(modulesDir, projectInfo);
45
46
  }
46
- // Only allow: no-arg interactive, or explicit category + --provider.
47
- // Disallow positional provider names like `npx stackkit@latest add better-auth` or
48
- // `npx stackkit@latest add auth prisma-postgresql` — require `--provider` flag.
49
47
  if (module === "database" || module === "auth") {
50
48
  if (!options?.provider) {
51
49
  if (module === "database") {
@@ -56,13 +54,11 @@ async function getAddConfig(module, options, projectInfo) {
56
54
  }
57
55
  }
58
56
  if (module === "database") {
59
- let baseProvider = options.provider;
60
- let adapterProvider = options.provider;
61
- if (options.provider.includes("-")) {
62
- const parts = options.provider.split("-");
63
- baseProvider = parts[0]; // e.g., "prisma"
64
- adapterProvider = options.provider; // e.g., "prisma-postgresql"
65
- }
57
+ const parsed = (0, shared_1.parseDatabaseOption)(options.provider || "");
58
+ const baseProvider = parsed.database;
59
+ const adapterProvider = parsed.provider
60
+ ? `${parsed.database}-${parsed.provider}`
61
+ : parsed.database;
66
62
  const moduleMetadata = await loadModuleMetadata(modulesDir, baseProvider, baseProvider);
67
63
  if (!moduleMetadata) {
68
64
  throw new Error(`Database provider "${baseProvider}" not found`);
@@ -70,7 +66,9 @@ async function getAddConfig(module, options, projectInfo) {
70
66
  return {
71
67
  module: "database",
72
68
  provider: adapterProvider,
73
- displayName: `${moduleMetadata.displayName} (${adapterProvider.split("-")[1] || adapterProvider})`,
69
+ displayName: parsed.database === "prisma" && parsed.provider
70
+ ? `${moduleMetadata.displayName} (${parsed.provider})`
71
+ : moduleMetadata.displayName,
74
72
  metadata: moduleMetadata,
75
73
  };
76
74
  }
@@ -80,6 +78,20 @@ async function getAddConfig(module, options, projectInfo) {
80
78
  if (!moduleMetadata) {
81
79
  throw new Error(`Auth provider "${provider}" not found`);
82
80
  }
81
+ // Validate compatibility with existing project
82
+ if (projectInfo) {
83
+ // Auth.js requires Next.js + Prisma
84
+ if (provider === "authjs" &&
85
+ (projectInfo.framework !== "nextjs" || !projectInfo.hasPrisma)) {
86
+ throw new Error("Auth.js is only supported with Next.js and Prisma database in this project");
87
+ }
88
+ // Better-auth requires a database for server frameworks (non-react)
89
+ if (provider === "better-auth" &&
90
+ !projectInfo.hasDatabase &&
91
+ projectInfo.framework !== "react") {
92
+ throw new Error("Better Auth requires a database for server frameworks in this project");
93
+ }
94
+ }
83
95
  return {
84
96
  module: "auth",
85
97
  provider,
@@ -92,139 +104,92 @@ async function getAddConfig(module, options, projectInfo) {
92
104
  throw new Error(`Unknown module type "${module}". Use "database" or "auth", or specify a provider directly.`);
93
105
  }
94
106
  async function getInteractiveConfig(modulesDir, projectInfo) {
107
+ // Discover modules once and use compatibility info to decide which categories to show
108
+ const discovered = await (0, module_discovery_1.discoverModules)(modulesDir);
109
+ const compatibleAuths = (0, module_discovery_1.getCompatibleAuthOptions)(discovered.auth || [], projectInfo?.framework || "nextjs", projectInfo?.hasPrisma ? "prisma" : "none");
110
+ const categories = [
111
+ { name: "Database", value: "database" },
112
+ ];
113
+ // Only show Auth category if there is at least one compatible auth option
114
+ if (compatibleAuths.length > 1) {
115
+ categories.push({ name: "Auth", value: "auth" });
116
+ }
95
117
  const answers = await inquirer_1.default.prompt([
96
118
  {
97
119
  type: "list",
98
120
  name: "category",
99
121
  message: "What would you like to add?",
100
- choices: [
101
- { name: "Database", value: "database" },
102
- { name: "Auth", value: "auth" },
103
- ],
122
+ choices: categories,
104
123
  },
105
124
  ]);
106
125
  const category = answers.category;
107
126
  if (category === "database") {
127
+ const dbChoices = (0, module_discovery_1.getDatabaseChoices)(discovered.databases || [], projectInfo?.framework || "nextjs");
108
128
  const dbAnswers = await inquirer_1.default.prompt([
109
129
  {
110
130
  type: "list",
111
131
  name: "database",
112
132
  message: "Select database:",
113
- choices: [
114
- { name: "Prisma", value: "prisma" },
115
- { name: "Mongoose", value: "mongoose" },
116
- ],
133
+ choices: dbChoices,
117
134
  },
118
135
  ]);
119
136
  const selectedDb = dbAnswers.database;
120
- if (selectedDb === "prisma") {
121
- const providerAnswers = await inquirer_1.default.prompt([
122
- {
123
- type: "list",
124
- name: "provider",
125
- message: "Select Prisma provider:",
126
- choices: [
127
- { name: "PostgreSQL", value: "postgresql" },
128
- { name: "MongoDB", value: "mongodb" },
129
- { name: "MySQL", value: "mysql" },
130
- { name: "SQLite", value: "sqlite" },
131
- ],
132
- },
133
- ]);
137
+ if (selectedDb.startsWith("prisma-")) {
138
+ const provider = selectedDb.split("-")[1];
134
139
  return {
135
140
  module: "database",
136
- provider: `prisma-${providerAnswers.provider}`,
137
- displayName: `Prisma (${providerAnswers.provider})`,
141
+ provider: `prisma-${provider}`,
142
+ displayName: `Prisma (${provider})`,
138
143
  metadata: (await loadModuleMetadata(modulesDir, "prisma", "prisma")),
139
144
  };
140
145
  }
141
- else {
142
- return {
143
- module: "database",
144
- provider: "mongoose",
145
- displayName: "Mongoose",
146
- metadata: (await loadModuleMetadata(modulesDir, "mongoose", "mongoose")),
147
- };
148
- }
146
+ // Other databases (mongoose, etc.)
147
+ const meta = (await loadModuleMetadata(modulesDir, selectedDb, selectedDb));
148
+ if (!meta)
149
+ throw new Error(`Database provider "${selectedDb}" not found`);
150
+ return {
151
+ module: "database",
152
+ provider: selectedDb,
153
+ displayName: meta.displayName || selectedDb,
154
+ metadata: meta,
155
+ };
149
156
  }
150
157
  else if (category === "auth") {
158
+ // Filter auth choices based on project compatibility (framework + database)
159
+ const dbString = projectInfo?.hasPrisma ? "prisma" : "none";
160
+ const authChoices = (0, module_discovery_1.getCompatibleAuthOptions)(discovered.auth || [], projectInfo?.framework || "nextjs", dbString);
151
161
  const authAnswers = await inquirer_1.default.prompt([
152
162
  {
153
163
  type: "list",
154
164
  name: "auth",
155
165
  message: "Select authentication:",
156
- choices: [
157
- { name: "Better Auth", value: "better-auth" },
158
- { name: "Auth.js", value: "authjs" },
159
- ],
166
+ choices: authChoices,
160
167
  },
161
168
  ]);
162
169
  const selectedAuth = authAnswers.auth;
170
+ if (selectedAuth === "none") {
171
+ logger_1.logger.info("Cancelled");
172
+ process.exit(0);
173
+ }
163
174
  const metadata = await loadModuleMetadata(modulesDir, selectedAuth, selectedAuth);
164
175
  if (!metadata) {
165
176
  throw new Error(`Auth provider "${selectedAuth}" not found`);
166
177
  }
167
- if (projectInfo && !metadata.supportedFrameworks.includes(projectInfo.framework)) {
178
+ if (projectInfo &&
179
+ metadata.supportedFrameworks &&
180
+ !metadata.supportedFrameworks.includes(projectInfo.framework)) {
168
181
  throw new Error(`Auth provider "${selectedAuth}" does not support ${projectInfo.framework}`);
169
182
  }
170
183
  return {
171
184
  module: "auth",
172
185
  provider: selectedAuth,
173
- displayName: selectedAuth === "better-auth" ? "Better Auth" : "Auth.js",
186
+ displayName: metadata.displayName || selectedAuth,
174
187
  metadata,
175
188
  };
176
189
  }
177
190
  throw new Error("Invalid selection");
178
191
  }
179
- async function getProviderConfig(modulesDir, provider, projectInfo) {
180
- if (provider.includes("-")) {
181
- const parts = provider.split("-");
182
- const baseProvider = parts[0];
183
- const specificProvider = provider;
184
- if (baseProvider === "prisma") {
185
- const metadata = await loadModuleMetadata(modulesDir, "database", baseProvider);
186
- if (!metadata) {
187
- throw new Error(`Database provider "${baseProvider}" not found`);
188
- }
189
- return {
190
- module: "database",
191
- provider: specificProvider,
192
- displayName: `Prisma (${parts[1]})`,
193
- metadata,
194
- };
195
- }
196
- }
197
- else {
198
- if (provider === "mongoose") {
199
- const metadata = await loadModuleMetadata(modulesDir, "database", "mongoose");
200
- if (!metadata) {
201
- throw new Error(`Database provider "${provider}" not found`);
202
- }
203
- return {
204
- module: "database",
205
- provider: "mongoose",
206
- displayName: "Mongoose",
207
- metadata,
208
- };
209
- }
210
- else if (provider === "better-auth" || provider === "authjs") {
211
- const metadata = await loadModuleMetadata(modulesDir, provider, provider);
212
- if (!metadata) {
213
- throw new Error(`Auth provider "${provider}" not found`);
214
- }
215
- if (projectInfo && !metadata.supportedFrameworks.includes(projectInfo.framework)) {
216
- throw new Error(`Auth provider "${provider}" does not support ${projectInfo.framework}`);
217
- }
218
- return {
219
- module: "auth",
220
- provider,
221
- displayName: provider === "better-auth" ? "Better Auth" : "Auth.js",
222
- metadata,
223
- };
224
- }
225
- }
226
- throw new Error(`Unknown provider "${provider}". Available providers: better-auth, authjs, mongoose, prisma-postgresql, prisma-mongodb, prisma-mysql, prisma-sqlite`);
227
- }
192
+ /* removed unused getProviderConfig discovery-based flows handle providers */
228
193
  async function addModuleToProject(projectRoot, projectInfo, config, options) {
229
194
  const moduleMetadata = config.metadata;
230
195
  const selectedProvider = config.provider;
@@ -297,18 +262,28 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
297
262
  }
298
263
  const selectedModules = { framework: projectInfo.framework };
299
264
  if (config.module === "database" && config.provider) {
300
- if (config.provider.startsWith("prisma-")) {
301
- selectedModules.database = "prisma";
302
- selectedModules.prismaProvider = config.provider.split("-")[1];
303
- }
304
- else {
305
- selectedModules.database = config.provider;
265
+ const parsed = (0, shared_1.parseDatabaseOption)(config.provider);
266
+ selectedModules.database = parsed.database;
267
+ if (parsed.database === "prisma" && parsed.provider) {
268
+ selectedModules.prismaProvider = parsed.provider;
306
269
  }
307
270
  }
308
271
  if (config.module === "auth" && config.provider) {
309
272
  selectedModules.auth = config.provider;
310
273
  }
311
274
  const postInstall = await gen.applyToProject(selectedModules, [], projectRoot);
275
+ // Install dependencies first, then run post-install commands (e.g. prisma generate)
276
+ if (!options?.dryRun && options?.install !== false) {
277
+ const installSpinner = logger_1.logger.startSpinner("Installing dependencies...");
278
+ try {
279
+ await (0, package_manager_1.installDependencies)(projectRoot, projectInfo.packageManager);
280
+ installSpinner.succeed("Dependencies installed");
281
+ }
282
+ catch (err) {
283
+ installSpinner.fail("Failed to install dependencies");
284
+ throw err;
285
+ }
286
+ }
312
287
  if (postInstall && postInstall.length > 0 && !options?.dryRun) {
313
288
  const postInstallSpinner = logger_1.logger.startSpinner("Running post-install commands...");
314
289
  try {
@@ -322,17 +297,6 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
322
297
  throw error;
323
298
  }
324
299
  }
325
- if (!options?.dryRun && options?.install !== false) {
326
- const installSpinner = logger_1.logger.startSpinner("Installing dependencies...");
327
- try {
328
- await (0, package_manager_1.installDependencies)(projectRoot, projectInfo.packageManager);
329
- installSpinner.succeed("Dependencies installed");
330
- }
331
- catch (err) {
332
- installSpinner.fail("Failed to install dependencies");
333
- throw err;
334
- }
335
- }
336
300
  return;
337
301
  }
338
302
  const mergedDeps = {};
@@ -10,15 +10,16 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
10
10
  const inquirer_1 = __importDefault(require("inquirer"));
11
11
  const path_1 = __importDefault(require("path"));
12
12
  const validate_npm_package_name_1 = __importDefault(require("validate-npm-package-name"));
13
- const git_utils_1 = require("../lib/git-utils");
14
13
  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
14
  const module_discovery_1 = require("../lib/discovery/module-discovery");
15
+ const shared_1 = require("../lib/discovery/shared");
16
+ const env_editor_1 = require("../lib/env/env-editor");
17
+ const framework_utils_1 = require("../lib/framework/framework-utils");
19
18
  const code_generator_1 = require("../lib/generation/code-generator");
19
+ const git_utils_1 = require("../lib/git-utils");
20
+ const package_manager_1 = require("../lib/pm/package-manager");
21
+ const logger_1 = require("../lib/ui/logger");
20
22
  const package_root_1 = require("../lib/utils/package-root");
21
- const env_editor_1 = require("../lib/env/env-editor");
22
23
  async function createProject(projectName, options) {
23
24
  logger_1.logger.newLine();
24
25
  logger_1.logger.log(chalk_1.default.bold.cyan("📦 Create StackKit App"));
@@ -91,7 +92,7 @@ async function getProjectConfig(projectName, options) {
91
92
  let database = "none";
92
93
  let prismaProvider;
93
94
  if (db && db !== "none") {
94
- const parsed = (0, module_discovery_1.parseDatabaseOption)(db);
95
+ const parsed = (0, shared_1.parseDatabaseOption)(db);
95
96
  database = parsed.database;
96
97
  prismaProvider = parsed.provider;
97
98
  }
@@ -151,12 +152,16 @@ async function getProjectConfig(projectName, options) {
151
152
  name: "database",
152
153
  message: "Select database/ORM:",
153
154
  when: (answers) => answers.framework !== "react",
154
- choices: [
155
- { name: "Prisma", value: "prisma" },
156
- { name: "Mongoose", value: "mongoose" },
157
- { name: "None", value: "none" },
158
- ],
155
+ choices: (answers) => discoveredModules.databases && discoveredModules.databases.length > 0
156
+ ? (0, module_discovery_1.getDatabaseChoices)(discoveredModules.databases, answers.framework)
157
+ : [
158
+ { name: "Prisma", value: "prisma" },
159
+ { name: "Mongoose", value: "mongoose" },
160
+ { name: "None", value: "none" },
161
+ ],
159
162
  },
163
+ // If a prisma-* choice is selected above, `prismaProvider` will be derived from it,
164
+ // otherwise prompt for provider when `prisma` is selected directly.
160
165
  {
161
166
  type: "list",
162
167
  name: "prismaProvider",
@@ -199,13 +204,21 @@ async function getProjectConfig(projectName, options) {
199
204
  default: "pnpm",
200
205
  },
201
206
  ]));
207
+ // Normalize database answer (interactive flow): handle values like `prisma-postgresql`
208
+ let databaseAnswer = answers.framework === "react" ? "none" : answers.database;
209
+ let prismaProviderAnswer = answers.prismaProvider;
210
+ if (typeof databaseAnswer === "string" && databaseAnswer.startsWith("prisma-")) {
211
+ const parts = databaseAnswer.split("-");
212
+ if (parts.length >= 2) {
213
+ prismaProviderAnswer = parts[1];
214
+ databaseAnswer = "prisma";
215
+ }
216
+ }
202
217
  return {
203
218
  projectName: (projectName || answers.projectName),
204
219
  framework: answers.framework,
205
- database: (answers.framework === "react"
206
- ? "none"
207
- : answers.database),
208
- prismaProvider: answers.prismaProvider,
220
+ database: databaseAnswer,
221
+ prismaProvider: prismaProviderAnswer,
209
222
  auth: answers.auth || "none",
210
223
  language: answers.language,
211
224
  packageManager: answers.packageManager,