stackkit 0.1.5 → 0.1.6

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/dist/cli/add.js CHANGED
@@ -17,7 +17,6 @@ const logger_1 = require("../lib/ui/logger");
17
17
  const package_manager_1 = require("../lib/pm/package-manager");
18
18
  const package_root_1 = require("../lib/utils/package-root");
19
19
  const framework_utils_1 = require("../lib/framework/framework-utils");
20
- const generator_utils_1 = require("../lib/generation/generator-utils");
21
20
  async function addCommand(module, options) {
22
21
  try {
23
22
  const projectRoot = process.cwd();
@@ -29,10 +28,6 @@ async function addCommand(module, options) {
29
28
  logger_1.logger.newLine();
30
29
  logger_1.logger.success(`Added ${chalk_1.default.bold(config.displayName)}`);
31
30
  logger_1.logger.newLine();
32
- if (config.metadata.envVars && config.metadata.envVars.length > 0) {
33
- logger_1.logger.log("Next: Fill in environment variables in .env");
34
- }
35
- logger_1.logger.newLine();
36
31
  }
37
32
  catch (error) {
38
33
  logger_1.logger.error(`Failed to add module: ${error.message}`);
@@ -44,88 +39,59 @@ async function addCommand(module, options) {
44
39
  }
45
40
  async function getAddConfig(module, options, projectInfo) {
46
41
  const modulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(), "modules");
47
- const argv = process.argv.slice(2);
48
- const addIndex = argv.indexOf("add");
49
- const argsAfterAdd = addIndex >= 0 ? argv.slice(addIndex + 1) : [];
50
- const flagsProvided = argsAfterAdd.some((arg) => arg.startsWith("-"));
51
- const optionsProvided = flagsProvided ||
52
- !!(options && (options.yes || options.provider || options.force || options.dryRun));
53
- if (optionsProvided) {
54
- if (!module) {
55
- throw new Error("Module name is required when using flags");
56
- }
57
- if (module === "database" || module === "auth") {
42
+ // If no module provided, go interactive
43
+ if (!module) {
44
+ return await getInteractiveConfig(modulesDir, projectInfo);
45
+ }
46
+ // Only allow: no-arg interactive, or explicit category + --provider.
47
+ // Disallow positional provider names like `npx stackkit add better-auth` or
48
+ // `npx stackkit add auth prisma-postgresql` — require `--provider` flag.
49
+ if (module === "database" || module === "auth") {
50
+ if (!options?.provider) {
58
51
  if (module === "database") {
59
- if (!options?.provider) {
60
- throw new Error("Provider is required for database. Use --provider <provider>");
61
- }
62
- let baseProvider = options.provider;
63
- let adapterProvider = options.provider;
64
- if (options.provider.includes("-")) {
65
- const parts = options.provider.split("-");
66
- baseProvider = parts[0]; // e.g., "prisma"
67
- adapterProvider = options.provider; // e.g., "prisma-postgresql"
68
- }
69
- const moduleMetadata = await loadModuleMetadata(modulesDir, baseProvider, baseProvider);
70
- if (!moduleMetadata) {
71
- throw new Error(`Database provider "${baseProvider}" not found`);
72
- }
73
- return {
74
- module: "database",
75
- provider: adapterProvider,
76
- displayName: `${moduleMetadata.displayName} (${adapterProvider.split("-")[1] || adapterProvider})`,
77
- metadata: moduleMetadata,
78
- };
52
+ throw new Error('Provider is required for database. Use: `npx stackkit add database --provider <provider>`');
79
53
  }
80
- else if (module === "auth") {
81
- const provider = options?.provider || "better-auth";
82
- const moduleMetadata = await loadModuleMetadata(modulesDir, provider, provider);
83
- if (!moduleMetadata) {
84
- throw new Error(`Auth provider "${provider}" not found`);
85
- }
86
- return {
87
- module: "auth",
88
- provider,
89
- displayName: moduleMetadata.displayName,
90
- metadata: moduleMetadata,
91
- };
54
+ else {
55
+ throw new Error('Provider is required for auth. Use: `npx stackkit add auth --provider <provider>`');
92
56
  }
93
57
  }
94
- const moduleMetadata = await loadModuleMetadata(modulesDir, module, options?.provider);
95
- if (!moduleMetadata) {
96
- throw new Error(`Module "${module}" not found`);
97
- }
98
- let selectedProvider = options?.provider;
99
- if (!selectedProvider && moduleMetadata.category !== module) {
100
- selectedProvider = module;
101
- }
102
- if (moduleMetadata.category === "database" && !selectedProvider) {
103
- if (typeof moduleMetadata.dependencies === "object" &&
104
- "providers" in moduleMetadata.dependencies) {
105
- const providers = Object.keys(moduleMetadata.dependencies.providers || {});
106
- if (providers.length > 0) {
107
- const { provider } = await inquirer_1.default.prompt([
108
- {
109
- type: "list",
110
- name: "provider",
111
- message: "Select database provider:",
112
- choices: providers.map((p) => ({ name: p, value: p })),
113
- },
114
- ]);
115
- selectedProvider = provider;
116
- }
58
+ 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
+ }
66
+ const moduleMetadata = await loadModuleMetadata(modulesDir, baseProvider, baseProvider);
67
+ if (!moduleMetadata) {
68
+ throw new Error(`Database provider "${baseProvider}" not found`);
117
69
  }
70
+ return {
71
+ module: "database",
72
+ provider: adapterProvider,
73
+ displayName: `${moduleMetadata.displayName} (${adapterProvider.split("-")[1] || adapterProvider})`,
74
+ metadata: moduleMetadata,
75
+ };
118
76
  }
119
- if (projectInfo && !moduleMetadata.supportedFrameworks.includes(projectInfo.framework)) {
120
- throw new Error(`Module "${module}" does not support ${projectInfo.framework}. Supported: ${moduleMetadata.supportedFrameworks.join(", ")}`);
77
+ else if (module === "auth") {
78
+ const provider = options.provider;
79
+ const moduleMetadata = await loadModuleMetadata(modulesDir, provider, provider);
80
+ if (!moduleMetadata) {
81
+ throw new Error(`Auth provider "${provider}" not found`);
82
+ }
83
+ return {
84
+ module: "auth",
85
+ provider,
86
+ displayName: moduleMetadata.displayName,
87
+ metadata: moduleMetadata,
88
+ };
121
89
  }
122
- return {
123
- module,
124
- provider: selectedProvider,
125
- displayName: moduleMetadata.displayName,
126
- metadata: moduleMetadata,
127
- };
128
90
  }
91
+ // Unknown module type
92
+ throw new Error(`Unknown module type "${module}". Use "database" or "auth", or specify a provider directly.`);
93
+ }
94
+ async function getInteractiveConfig(modulesDir, projectInfo) {
129
95
  const answers = await inquirer_1.default.prompt([
130
96
  {
131
97
  type: "list",
@@ -133,7 +99,7 @@ async function getAddConfig(module, options, projectInfo) {
133
99
  message: "What would you like to add?",
134
100
  choices: [
135
101
  { name: "Database", value: "database" },
136
- { name: "Authentication", value: "auth" },
102
+ { name: "Auth", value: "auth" },
137
103
  ],
138
104
  },
139
105
  ]);
@@ -167,7 +133,7 @@ async function getAddConfig(module, options, projectInfo) {
167
133
  ]);
168
134
  return {
169
135
  module: "database",
170
- provider: "prisma",
136
+ provider: `prisma-${providerAnswers.provider}`,
171
137
  displayName: `Prisma (${providerAnswers.provider})`,
172
138
  metadata: (await loadModuleMetadata(modulesDir, "prisma", "prisma")),
173
139
  };
@@ -210,6 +176,55 @@ async function getAddConfig(module, options, projectInfo) {
210
176
  }
211
177
  throw new Error("Invalid selection");
212
178
  }
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
+ }
213
228
  async function addModuleToProject(projectRoot, projectInfo, config, options) {
214
229
  const moduleMetadata = config.metadata;
215
230
  const selectedProvider = config.provider;
@@ -228,6 +243,21 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
228
243
  return;
229
244
  }
230
245
  }
246
+ if (config.module === "database" && projectInfo.hasDatabase && !options?.force) {
247
+ logger_1.logger.warn("Database library already detected in this project");
248
+ const { proceed } = await inquirer_1.default.prompt([
249
+ {
250
+ type: "confirm",
251
+ name: "proceed",
252
+ message: "Continue anyway? (use --force to skip this prompt)",
253
+ default: false,
254
+ },
255
+ ]);
256
+ if (!proceed) {
257
+ logger_1.logger.info("Cancelled");
258
+ return;
259
+ }
260
+ }
231
261
  if (options?.dryRun) {
232
262
  logger_1.logger.warn("Dry run mode - no changes will be made");
233
263
  logger_1.logger.newLine();
@@ -413,10 +443,10 @@ async function loadModuleMetadata(modulesDir, moduleName, provider) {
413
443
  if (await fs_extra_1.default.pathExists(metadataPath)) {
414
444
  const metadata = await fs_extra_1.default.readJSON(metadataPath);
415
445
  if (provider && moduleDir === provider) {
416
- return await (0, generator_utils_1.mergeGeneratorIntoModuleMetadata)(metadata, modulePath);
446
+ return metadata;
417
447
  }
418
448
  if (!provider && (metadata.category === moduleName || moduleDir === moduleName)) {
419
- return await (0, generator_utils_1.mergeGeneratorIntoModuleMetadata)(metadata, modulePath);
449
+ return metadata;
420
450
  }
421
451
  }
422
452
  }
@@ -137,7 +137,7 @@ async function runDoctorChecks() {
137
137
  summary: {
138
138
  errors: checks.filter((c) => c.status === "error").length,
139
139
  warnings: checks.filter((c) => c.status === "warning").length,
140
- suggestions: generateSuggestions(),
140
+ suggestions: generateSuggestions(authModules, databaseModules),
141
141
  },
142
142
  };
143
143
  return report;
@@ -261,6 +261,7 @@ async function checkAuthRoutesExist(projectRoot, projectType) {
261
261
  if (projectType !== "nextjs")
262
262
  return true; // Skip for non-Next.js
263
263
  const possiblePaths = [
264
+ // NextAuth routes
264
265
  "app/api/auth/[...nextauth]/route.ts",
265
266
  "app/api/auth/[...nextauth]/route.js",
266
267
  "src/app/api/auth/[...nextauth]/route.ts",
@@ -269,6 +270,11 @@ async function checkAuthRoutesExist(projectRoot, projectType) {
269
270
  "pages/api/auth/[...nextauth].js",
270
271
  "src/pages/api/auth/[...nextauth].ts",
271
272
  "src/pages/api/auth/[...nextauth].js",
273
+ // Better Auth routes
274
+ "app/api/auth/[...all]/route.ts",
275
+ "app/api/auth/[...all]/route.js",
276
+ "src/app/api/auth/[...all]/route.ts",
277
+ "src/app/api/auth/[...all]/route.js",
272
278
  ];
273
279
  for (const routePath of possiblePaths) {
274
280
  if (await fs_extra_1.default.pathExists(path_1.default.join(projectRoot, routePath))) {
@@ -372,8 +378,10 @@ async function checkDependencies(packageJson) {
372
378
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
373
379
  for (const [name, version] of Object.entries(deps || {})) {
374
380
  if (typeof version === "string" && (version.startsWith("^") || version.startsWith("~"))) {
381
+ // Assume up to date if using flexible versioning
375
382
  }
376
383
  else {
384
+ // Assume outdated if using exact versions (simplified check)
377
385
  outdated.push(name);
378
386
  }
379
387
  }
@@ -406,11 +414,16 @@ async function checkEslintConfigExists(projectRoot) {
406
414
  }
407
415
  return false;
408
416
  }
409
- function generateSuggestions() {
417
+ function generateSuggestions(authModules, databaseModules) {
410
418
  const suggestions = [];
419
+ // Show suggestions based on what's missing
420
+ if (authModules.length === 0) {
421
+ suggestions.push("stackkit add auth - Add authentication module");
422
+ }
423
+ if (databaseModules.length === 0) {
424
+ suggestions.push("stackkit add db - Add database module");
425
+ }
411
426
  // Always show available commands
412
- suggestions.push("stackkit add auth - Add authentication module");
413
- suggestions.push("stackkit add db - Add database module");
414
427
  suggestions.push("stackkit list - View available modules");
415
428
  return suggestions;
416
429
  }
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ const program = new commander_1.Command();
11
11
  program
12
12
  .name("stackkit")
13
13
  .description("CLI for creating and managing StackKit projects")
14
- .version("0.1.5")
14
+ .version("0.1.6")
15
15
  .configureHelp({
16
16
  subcommandTerm: (cmd) => {
17
17
  const name = cmd.name();
@@ -74,15 +74,20 @@ async function mergeGeneratorIntoModuleMetadata(metadata, modulePath) {
74
74
  if (await fs.pathExists(generatorPath)) {
75
75
  try {
76
76
  const generator = await fs.readJson(generatorPath);
77
- if (generator.envVars) {
78
- metadata.envVars = metadata.envVars || [];
79
- for (const [key, value] of Object.entries(generator.envVars)) {
80
- metadata.envVars.push({
81
- key,
82
- value: value,
83
- description: `Environment variable for ${key}`,
84
- required: true,
85
- });
77
+ // Process add-env operations to extract envVars
78
+ if (generator.operations && Array.isArray(generator.operations)) {
79
+ for (const operation of generator.operations) {
80
+ if (operation.type === "add-env" && operation.envVars) {
81
+ metadata.envVars = metadata.envVars || [];
82
+ for (const [key, value] of Object.entries(operation.envVars)) {
83
+ metadata.envVars.push({
84
+ key,
85
+ value: value,
86
+ description: `Environment variable for ${key}`,
87
+ required: true,
88
+ });
89
+ }
90
+ }
86
91
  }
87
92
  }
88
93
  if (generator.dependencies) {
@@ -5,6 +5,5 @@
5
5
  "operations": [],
6
6
  "dependencies": {},
7
7
  "devDependencies": {},
8
- "scripts": {},
9
- "envVars": {}
8
+ "scripts": {}
10
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stackkit",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Production-ready CLI to create and extend JavaScript or TypeScript apps with modular stacks.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -28,6 +28,8 @@
28
28
  "copy-assets": "cp -r ../../templates . && cp -r ../../modules .",
29
29
  "clean": "rm -rf dist templates modules",
30
30
  "typecheck": "tsc --noEmit",
31
+ "lint": "eslint src --ext .ts",
32
+ "lint:fix": "eslint src --ext .ts --fix",
31
33
  "prepublishOnly": "npm run build"
32
34
  },
33
35
  "keywords": [