create-stackforge 0.0.1 → 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.
package/dist/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/cli.ts
2
2
  import { Command as Command17 } from "commander";
3
+ import { createRequire } from "module";
3
4
 
4
5
  // src/cli/commands/create.ts
5
6
  import { Command } from "commander";
@@ -77,6 +78,7 @@ var presets = {
77
78
  features: []
78
79
  }
79
80
  };
81
+ var presetNames = Object.keys(presets);
80
82
  function getPreset(name) {
81
83
  if (!name) return null;
82
84
  return presets[name] ?? null;
@@ -667,9 +669,13 @@ async function removeEnvKey(path, key, ctx) {
667
669
  } catch {
668
670
  return;
669
671
  }
670
- const lines = existing.split(/\r?\n/).filter((line) => line.trim() !== "");
671
- const filtered = lines.filter((line) => !line.startsWith(`${key}=`) && !line.startsWith(`${key}="`));
672
- await writeFile3(path, filtered.join("\n") + (filtered.length ? "\n" : ""), "utf8");
672
+ const lines = existing.split(/\r?\n/);
673
+ const filtered = lines.filter((line) => {
674
+ const trimmed = line.trim();
675
+ if (!trimmed || trimmed.startsWith("#")) return true;
676
+ return !trimmed.startsWith(`${key}=`);
677
+ });
678
+ await writeFile3(path, filtered.join("\n"), "utf8");
673
679
  }
674
680
 
675
681
  // src/generators/database/database-files.ts
@@ -1199,7 +1205,7 @@ async function generateAuthFiles(root, config, ctx) {
1199
1205
  if (config.frontend.type === "nextjs") {
1200
1206
  const routeDir = join7(projectRoot, "app", "api", "auth", "[...nextauth]");
1201
1207
  await ensureDir(routeDir, ctx);
1202
- const route = await readTextFile(join7(templatesRoot, "auth", "nextauth-route.ts"));
1208
+ const route = await readTextFile(join7(templatesRoot, "auth", `nextauth-route.${ext}`));
1203
1209
  await writeTextFile(join7(routeDir, config.frontend.language === "ts" ? "route.ts" : "route.js"), route, ctx);
1204
1210
  }
1205
1211
  const authDir = join7(projectRoot, "auth");
@@ -1357,8 +1363,9 @@ async function generateApiFiles(root, config, ctx) {
1357
1363
  if (config.frontend.type === "nextjs") {
1358
1364
  const routeDir = join8(projectRoot, "app", "api", "hello");
1359
1365
  await ensureDir(routeDir, ctx);
1360
- const handler = await readTextFile(join8(templatesRoot, "api", "rest", "route.ts"));
1361
- await writeTextFile(join8(routeDir, config.frontend.language === "ts" ? "route.ts" : "route.js"), handler, ctx);
1366
+ const ext = config.frontend.language === "ts" ? "ts" : "js";
1367
+ const handler = await readTextFile(join8(templatesRoot, "api", "rest", `route.${ext}`));
1368
+ await writeTextFile(join8(routeDir, `route.${ext}`), handler, ctx);
1362
1369
  if (config.database.orm) {
1363
1370
  const usersDir = join8(projectRoot, "app", "api", "users");
1364
1371
  await ensureDir(usersDir, ctx);
@@ -1503,8 +1510,9 @@ async function generateApiFiles(root, config, ctx) {
1503
1510
  await writeTextFile(join8(gqlDir, "schema.graphql"), schema, ctx);
1504
1511
  const routeDir = join8(projectRoot, "app", "api", "graphql");
1505
1512
  await ensureDir(routeDir, ctx);
1506
- const handler = await readTextFile(join8(templatesRoot, "api", "graphql", "route.ts"));
1507
- await writeTextFile(join8(routeDir, config.frontend.language === "ts" ? "route.ts" : "route.js"), handler, ctx);
1513
+ const gqlExt = config.frontend.language === "ts" ? "ts" : "js";
1514
+ const handler = await readTextFile(join8(templatesRoot, "api", "graphql", `route.${gqlExt}`));
1515
+ await writeTextFile(join8(routeDir, `route.${gqlExt}`), handler, ctx);
1508
1516
  const clientDir = join8(projectRoot, "src", "graphql");
1509
1517
  const client = await readTextFile(
1510
1518
  join8(templatesRoot, "api", "graphql", config.frontend.language === "ts" ? "client.ts" : "client.js")
@@ -1831,7 +1839,6 @@ export function handleFunctionCall(name, args) {
1831
1839
  if (agent === "gemini") {
1832
1840
  const content = JSON.stringify({ functions: buildFunctionDefinitions(tools) }, null, 2);
1833
1841
  await writeTextFile(join9(agentDir, "function_declarations.json"), content + "\n", ctx);
1834
- await writeTextFile(join9(agentDir, "function-declarations.json"), content + "\n", ctx);
1835
1842
  const serverDir = join9(serversRoot, "gemini");
1836
1843
  await ensureDir(serverDir, ctx);
1837
1844
  const serverContent = `export const functions = ${JSON.stringify(buildFunctionDefinitions(tools), null, 2)};
@@ -1842,7 +1849,6 @@ export function handleFunctionCall(name, args) {
1842
1849
  `;
1843
1850
  await writeTextFile(join9(serverDir, `functions.${ext}`), serverContent, ctx);
1844
1851
  await writeTextFile(join9(serverDir, "function_declarations.json"), content + "\n", ctx);
1845
- await writeTextFile(join9(serverDir, "function-declarations.json"), content + "\n", ctx);
1846
1852
  }
1847
1853
  if (agent === "cursor") {
1848
1854
  const content = `# Cursor rules
@@ -1947,25 +1953,66 @@ ${hints || "- none"}
1947
1953
  }
1948
1954
  function buildMcpServerContent(ext, tools, hints) {
1949
1955
  const toolCases = [
1950
- "case 'stackforge_database':\\n return { ok: true, message: 'Check DATABASE_URL in .env and review db clients in src/db or drizzle/' };",
1951
- "case 'stackforge_orm':\\n return { ok: true, message: 'Review ORM schema or models in drizzle/ or prisma/ or src/db' };",
1952
- "case 'stackforge_api':\\n return { ok: true, message: 'API routes in app/api and clients in src/api, src/graphql, or src/trpc' };",
1953
- "case 'stackforge_auth':\\n return { ok: true, message: 'Auth routes in app/api/auth and auth helpers in auth/' };",
1954
- "case 'stackforge_email':\\n return { ok: true, message: 'Email client at src/lib/resend.ts and docs in features/email' };",
1955
- "case 'stackforge_storage':\\n return { ok: true, message: 'Storage docs in features/storage' };",
1956
- "case 'stackforge_payments':\\n return { ok: true, message: 'Stripe client at src/lib/stripe.ts and docs in features/payments' };",
1957
- "case 'stackforge_analytics':\\n return { ok: true, message: 'PostHog client at src/lib/posthog.ts' };",
1958
- "case 'stackforge_error-tracking':\\n return { ok: true, message: 'Sentry client at src/lib/sentry.ts' };"
1959
- ].join("\\n ");
1960
- const common = `const tools = ${JSON.stringify(tools, null, 2)};\\nconst hints = ${JSON.stringify(
1961
- hints,
1962
- null,
1963
- 2
1964
- )};\\n\\nfunction handleTool(name${ext === "ts" ? ": string" : ""}, action${ext === "ts" ? ": string" : ""}) {\\n switch (name) {\\n ${toolCases}\\n default:\\n return { ok: false, message: 'Unknown tool' };\\n }\\n}\\n\\nconst server = ${ext === "ts" ? "createServer" : "http.createServer"}((req, res) => {\\n if (req.url === '/tools') {\\n res.writeHead(200, { 'Content-Type': 'application/json' });\\n res.end(JSON.stringify({ tools, hints }));\\n return;\\n }\\n if (req.url === '/invoke' && req.method === 'POST') {\\n let body = '';\\n req.on('data', (chunk) => (body += chunk));\\n req.on('end', () => {\\n try {\\n const payload = JSON.parse(body || '{}');\\n const name = payload.name || '';\\n const action = payload.arguments?.action || '';\\n const result = handleTool(name, action);\\n res.writeHead(200, { 'Content-Type': 'application/json' });\\n res.end(JSON.stringify(result));\\n } catch (err) {\\n res.writeHead(400, { 'Content-Type': 'application/json' });\\n res.end(JSON.stringify({ ok: false, message: 'Invalid JSON' }));\\n }\\n });\\n return;\\n }\\n res.writeHead(404);\\n res.end();\\n});\\n\\nconst port = Number(process.env.MCP_PORT || 7341);\\nserver.listen(port, () => {\\n console.log('MCP server listening on', port);\\n});\\n`;
1965
- if (ext === "ts") {
1966
- return `import { createServer } from 'node:http';\\n\\n${common}`;
1956
+ "case 'stackforge_database':\n return { ok: true, message: 'Check DATABASE_URL in .env and review db clients in src/db or drizzle/' };",
1957
+ "case 'stackforge_orm':\n return { ok: true, message: 'Review ORM schema or models in drizzle/ or prisma/ or src/db' };",
1958
+ "case 'stackforge_api':\n return { ok: true, message: 'API routes in app/api and clients in src/api, src/graphql, or src/trpc' };",
1959
+ "case 'stackforge_auth':\n return { ok: true, message: 'Auth routes in app/api/auth and auth helpers in auth/' };",
1960
+ "case 'stackforge_email':\n return { ok: true, message: 'Email client at src/lib/resend.ts and docs in features/email' };",
1961
+ "case 'stackforge_storage':\n return { ok: true, message: 'Storage docs in features/storage' };",
1962
+ "case 'stackforge_payments':\n return { ok: true, message: 'Stripe client at src/lib/stripe.ts and docs in features/payments' };",
1963
+ "case 'stackforge_analytics':\n return { ok: true, message: 'PostHog client at src/lib/posthog.ts' };",
1964
+ "case 'stackforge_error-tracking':\n return { ok: true, message: 'Sentry client at src/lib/sentry.ts' };"
1965
+ ].join("\n ");
1966
+ const nameType = ext === "ts" ? ": string" : "";
1967
+ const actionType = ext === "ts" ? ": string" : "";
1968
+ const createServerExpr = ext === "ts" ? "createServer" : "http.createServer";
1969
+ const importLine = ext === "ts" ? "import { createServer } from 'node:http';" : "const http = require('node:http');";
1970
+ return `${importLine}
1971
+
1972
+ const tools = ${JSON.stringify(tools, null, 2)};
1973
+ const hints = ${JSON.stringify(hints, null, 2)};
1974
+
1975
+ function handleTool(name${nameType}, action${actionType}) {
1976
+ switch (name) {
1977
+ ${toolCases}
1978
+ default:
1979
+ return { ok: false, message: 'Unknown tool' };
1967
1980
  }
1968
- return `const http = require('node:http');\\n\\n${common}`;
1981
+ }
1982
+
1983
+ const server = ${createServerExpr}((req, res) => {
1984
+ if (req.url === '/tools') {
1985
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1986
+ res.end(JSON.stringify({ tools, hints }));
1987
+ return;
1988
+ }
1989
+ if (req.url === '/invoke' && req.method === 'POST') {
1990
+ let body = '';
1991
+ req.on('data', (chunk) => (body += chunk));
1992
+ req.on('end', () => {
1993
+ try {
1994
+ const payload = JSON.parse(body || '{}');
1995
+ const name = payload.name || '';
1996
+ const action = payload.arguments?.action || '';
1997
+ const result = handleTool(name, action);
1998
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1999
+ res.end(JSON.stringify(result));
2000
+ } catch (err) {
2001
+ res.writeHead(400, { 'Content-Type': 'application/json' });
2002
+ res.end(JSON.stringify({ ok: false, message: 'Invalid JSON' }));
2003
+ }
2004
+ });
2005
+ return;
2006
+ }
2007
+ res.writeHead(404);
2008
+ res.end();
2009
+ });
2010
+
2011
+ const port = Number(process.env.MCP_PORT || 7341);
2012
+ server.listen(port, () => {
2013
+ console.log('MCP server listening on', port);
2014
+ });
2015
+ `;
1969
2016
  }
1970
2017
 
1971
2018
  // src/generators/features/feature-files.ts
@@ -2076,12 +2123,6 @@ function validateConfig(config) {
2076
2123
  throw new Error(`Unsupported feature: ${feature}`);
2077
2124
  }
2078
2125
  }
2079
- if (config.auth.provider === "nextauth" && config.frontend.type !== "nextjs") {
2080
- throw new Error("NextAuth requires Next.js.");
2081
- }
2082
- if (config.api.type === "trpc" && config.frontend.language !== "ts") {
2083
- throw new Error("tRPC requires TypeScript.");
2084
- }
2085
2126
  if (config.database.orm && config.database.provider === "none") {
2086
2127
  throw new Error("ORM requires a database provider.");
2087
2128
  }
@@ -2098,6 +2139,9 @@ function validateCompatibility(config) {
2098
2139
  if (config.api.type === "trpc" && config.frontend.language !== "ts") {
2099
2140
  throw new Error("tRPC requires TypeScript.");
2100
2141
  }
2142
+ if (config.database.orm === "mongoose") {
2143
+ throw new Error("Mongoose requires MongoDB. Use drizzle, prisma, or typeorm with SQL databases.");
2144
+ }
2101
2145
  if (config.database.orm === "typeorm") {
2102
2146
  const supported2 = ["postgres", "mysql", "sqlite"];
2103
2147
  if (!supported2.includes(config.database.provider)) {
@@ -2210,48 +2254,53 @@ var createCommand = new Command("create").argument("[project-name]", "name of th
2210
2254
  // src/cli/commands/list.ts
2211
2255
  import { Command as Command2 } from "commander";
2212
2256
  var listCommand = new Command2("list").option("--available", "show available features").option("--category <name>", "filter by category").action(async (options) => {
2213
- if (options.available) {
2214
- logger.info("Available features:");
2215
- logger.info(`frontend: ${supported.frontend.join(", ")}`);
2216
- logger.info(`ui: ${supported.ui.join(", ")}`);
2217
- logger.info(`database: ${supported.database.join(", ")}`);
2218
- logger.info(`orm: ${supported.orm.join(", ")}`);
2219
- logger.info(`auth: ${supported.auth.join(", ")}`);
2220
- logger.info(`api: ${supported.api.join(", ")}`);
2221
- logger.info(`features: ${supported.features.join(", ")}`);
2222
- return;
2223
- }
2224
- const config = await readProjectConfig(process.cwd());
2225
- if (!options.category) {
2226
- logger.info(`frontend: ${config.frontend.type} (${config.frontend.language})`);
2227
- logger.info(`ui: ${config.ui.library}`);
2228
- logger.info(`database: ${config.database.provider}${config.database.orm ? " (" + config.database.orm + ")" : ""}`);
2229
- logger.info(`auth: ${config.auth.provider}`);
2230
- logger.info(`api: ${config.api.type}`);
2231
- logger.info(`features: ${config.features.length ? config.features.join(", ") : "none"}`);
2232
- return;
2233
- }
2234
- switch (options.category) {
2235
- case "frontend":
2257
+ try {
2258
+ if (options.available) {
2259
+ logger.info("Available features:");
2260
+ logger.info(`frontend: ${supported.frontend.join(", ")}`);
2261
+ logger.info(`ui: ${supported.ui.join(", ")}`);
2262
+ logger.info(`database: ${supported.database.join(", ")}`);
2263
+ logger.info(`orm: ${supported.orm.join(", ")}`);
2264
+ logger.info(`auth: ${supported.auth.join(", ")}`);
2265
+ logger.info(`api: ${supported.api.join(", ")}`);
2266
+ logger.info(`features: ${supported.features.join(", ")}`);
2267
+ return;
2268
+ }
2269
+ const config = await readProjectConfig(process.cwd());
2270
+ if (!options.category) {
2236
2271
  logger.info(`frontend: ${config.frontend.type} (${config.frontend.language})`);
2237
- break;
2238
- case "ui":
2239
2272
  logger.info(`ui: ${config.ui.library}`);
2240
- break;
2241
- case "database":
2242
2273
  logger.info(`database: ${config.database.provider}${config.database.orm ? " (" + config.database.orm + ")" : ""}`);
2243
- break;
2244
- case "auth":
2245
2274
  logger.info(`auth: ${config.auth.provider}`);
2246
- break;
2247
- case "api":
2248
2275
  logger.info(`api: ${config.api.type}`);
2249
- break;
2250
- case "features":
2251
2276
  logger.info(`features: ${config.features.length ? config.features.join(", ") : "none"}`);
2252
- break;
2253
- default:
2254
- logger.error(`Unknown category: ${options.category}`);
2277
+ return;
2278
+ }
2279
+ switch (options.category) {
2280
+ case "frontend":
2281
+ logger.info(`frontend: ${config.frontend.type} (${config.frontend.language})`);
2282
+ break;
2283
+ case "ui":
2284
+ logger.info(`ui: ${config.ui.library}`);
2285
+ break;
2286
+ case "database":
2287
+ logger.info(`database: ${config.database.provider}${config.database.orm ? " (" + config.database.orm + ")" : ""}`);
2288
+ break;
2289
+ case "auth":
2290
+ logger.info(`auth: ${config.auth.provider}`);
2291
+ break;
2292
+ case "api":
2293
+ logger.info(`api: ${config.api.type}`);
2294
+ break;
2295
+ case "features":
2296
+ logger.info(`features: ${config.features.length ? config.features.join(", ") : "none"}`);
2297
+ break;
2298
+ default:
2299
+ logger.error(`Unknown category: ${options.category}`);
2300
+ }
2301
+ } catch (err) {
2302
+ logger.error(err instanceof Error ? err.message : String(err));
2303
+ process.exitCode = 1;
2255
2304
  }
2256
2305
  });
2257
2306
 
@@ -2375,31 +2424,37 @@ async function syncPackageJson(path, oldConfig, newConfig) {
2375
2424
  // src/cli/commands/add.ts
2376
2425
  import { dirname as dirname3, join as join12 } from "path";
2377
2426
  function parseFeature(feature) {
2378
- const [category, value] = feature.split(":");
2379
- if (!category || !value) {
2427
+ const parts = feature.split(":");
2428
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
2380
2429
  throw new Error("Feature must be in the form category:value (e.g., auth:nextauth).");
2381
2430
  }
2431
+ const [category, value] = parts;
2382
2432
  return { category, value };
2383
2433
  }
2384
2434
  var addCommand = new Command3("add").argument("<feature>", "feature to add (category:value)").action(async (feature) => {
2385
- const cwd = process.cwd();
2386
- const { category, value } = parseFeature(feature);
2387
- const current = await readProjectConfig(cwd);
2388
- const next = updateConfigForFeature(current, category, value, "add");
2389
- validateConfig(next);
2390
- validateCompatibility(next);
2391
- validateDependencies(next);
2392
- await writeProjectConfig(cwd, next);
2393
- await syncPackageJson(`${cwd}/package.json`, current, next);
2394
- const root = dirname3(cwd);
2395
- if (category === "ui") await generateUiFiles(root, next);
2396
- if (category === "database" || category === "orm") await generateDatabaseFiles(root, next);
2397
- if (category === "auth") await generateAuthFiles(root, next);
2398
- if (category === "api") await generateApiFiles(root, next);
2399
- if (category === "feature") await generateFeatureFiles(root, next);
2400
- const readme = buildProjectReadme(next);
2401
- await writeTextFile(join12(cwd, "README.md"), readme + "\n");
2402
- logger.info(`Added ${feature}`);
2435
+ try {
2436
+ const cwd = process.cwd();
2437
+ const { category, value } = parseFeature(feature);
2438
+ const current = await readProjectConfig(cwd);
2439
+ const next = updateConfigForFeature(current, category, value, "add");
2440
+ validateConfig(next);
2441
+ validateCompatibility(next);
2442
+ validateDependencies(next);
2443
+ await writeProjectConfig(cwd, next);
2444
+ await syncPackageJson(`${cwd}/package.json`, current, next);
2445
+ const root = dirname3(cwd);
2446
+ if (category === "ui") await generateUiFiles(root, next);
2447
+ if (category === "database" || category === "orm") await generateDatabaseFiles(root, next);
2448
+ if (category === "auth") await generateAuthFiles(root, next);
2449
+ if (category === "api") await generateApiFiles(root, next);
2450
+ if (category === "feature") await generateFeatureFiles(root, next);
2451
+ const readme = buildProjectReadme(next);
2452
+ await writeTextFile(join12(cwd, "README.md"), readme + "\n");
2453
+ logger.info(`Added ${feature}`);
2454
+ } catch (err) {
2455
+ logger.error(err instanceof Error ? err.message : String(err));
2456
+ process.exitCode = 1;
2457
+ }
2403
2458
  });
2404
2459
 
2405
2460
  // src/cli/commands/remove.ts
@@ -2589,10 +2644,11 @@ async function cleanupFeature(cwd, config, category, value) {
2589
2644
  // src/cli/commands/remove.ts
2590
2645
  import { join as join14 } from "path";
2591
2646
  function parseFeature2(feature) {
2592
- const [category, value] = feature.split(":");
2593
- if (!category || !value) {
2647
+ const parts = feature.split(":");
2648
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
2594
2649
  throw new Error("Feature must be in the form category:value (e.g., auth:nextauth).");
2595
2650
  }
2651
+ const [category, value] = parts;
2596
2652
  return { category, value };
2597
2653
  }
2598
2654
  function assertRemovalTarget(category, value, current) {
@@ -2632,20 +2688,25 @@ function assertRemovalTarget(category, value, current) {
2632
2688
  }
2633
2689
  }
2634
2690
  var removeCommand = new Command4("remove").argument("<feature>", "feature to remove (category:value)").action(async (feature) => {
2635
- const cwd = process.cwd();
2636
- const { category, value } = parseFeature2(feature);
2637
- const current = await readProjectConfig(cwd);
2638
- assertRemovalTarget(category, value, current);
2639
- await cleanupFeature(cwd, current, category, value);
2640
- const next = updateConfigForFeature(current, category, value, "remove");
2641
- validateConfig(next);
2642
- validateCompatibility(next);
2643
- validateDependencies(next);
2644
- await writeProjectConfig(cwd, next);
2645
- await syncPackageJson(`${cwd}/package.json`, current, next);
2646
- const readme = buildProjectReadme(next);
2647
- await writeTextFile(join14(cwd, "README.md"), readme + "\n");
2648
- logger.info(`Removed ${feature}`);
2691
+ try {
2692
+ const cwd = process.cwd();
2693
+ const { category, value } = parseFeature2(feature);
2694
+ const current = await readProjectConfig(cwd);
2695
+ assertRemovalTarget(category, value, current);
2696
+ await cleanupFeature(cwd, current, category, value);
2697
+ const next = updateConfigForFeature(current, category, value, "remove");
2698
+ validateConfig(next);
2699
+ validateCompatibility(next);
2700
+ validateDependencies(next);
2701
+ await writeProjectConfig(cwd, next);
2702
+ await syncPackageJson(`${cwd}/package.json`, current, next);
2703
+ const readme = buildProjectReadme(next);
2704
+ await writeTextFile(join14(cwd, "README.md"), readme + "\n");
2705
+ logger.info(`Removed ${feature}`);
2706
+ } catch (err) {
2707
+ logger.error(err instanceof Error ? err.message : String(err));
2708
+ process.exitCode = 1;
2709
+ }
2649
2710
  });
2650
2711
 
2651
2712
  // src/cli/commands/update.ts
@@ -2653,83 +2714,88 @@ import { Command as Command5 } from "commander";
2653
2714
  import { join as join15 } from "path";
2654
2715
  import { readFile as readFile5 } from "fs/promises";
2655
2716
  var updateCommand = new Command5("update").option("--check", "check for updates").option("--major", "allow major updates").option("--live", "compare against latest registry versions").action(async (options) => {
2656
- const cwd = process.cwd();
2657
- const config = await readProjectConfig(cwd);
2658
- const pkgPath = join15(cwd, "package.json");
2659
- if (options.check) {
2660
- const pkg = JSON.parse(await readFile5(pkgPath, "utf8"));
2661
- const expectedScripts = resolveScripts(config);
2662
- const expectedDeps = resolveDependencies(config, { allowMajor: Boolean(options.major) });
2663
- const issues = [];
2664
- for (const key of Object.keys(expectedScripts)) {
2665
- if (!pkg.scripts || !(key in pkg.scripts)) {
2666
- issues.push(`Missing script: ${key}`);
2667
- } else if (pkg.scripts[key] !== expectedScripts[key]) {
2668
- issues.push(`Script mismatch: ${key}`);
2717
+ try {
2718
+ const cwd = process.cwd();
2719
+ const config = await readProjectConfig(cwd);
2720
+ const pkgPath = join15(cwd, "package.json");
2721
+ if (options.check) {
2722
+ const pkg = JSON.parse(await readFile5(pkgPath, "utf8"));
2723
+ const expectedScripts = resolveScripts(config);
2724
+ const expectedDeps = resolveDependencies(config, { allowMajor: Boolean(options.major) });
2725
+ const issues = [];
2726
+ for (const key of Object.keys(expectedScripts)) {
2727
+ if (!pkg.scripts || !(key in pkg.scripts)) {
2728
+ issues.push(`Missing script: ${key}`);
2729
+ } else if (pkg.scripts[key] !== expectedScripts[key]) {
2730
+ issues.push(`Script mismatch: ${key}`);
2731
+ }
2669
2732
  }
2670
- }
2671
- for (const key of Object.keys(expectedDeps.dependencies)) {
2672
- if (!pkg.dependencies || !(key in pkg.dependencies)) {
2673
- issues.push(`Missing dependency: ${key}`);
2674
- } else if (pkg.dependencies[key] !== expectedDeps.dependencies[key]) {
2675
- issues.push(`Dependency version mismatch: ${key}`);
2733
+ for (const key of Object.keys(expectedDeps.dependencies)) {
2734
+ if (!pkg.dependencies || !(key in pkg.dependencies)) {
2735
+ issues.push(`Missing dependency: ${key}`);
2736
+ } else if (pkg.dependencies[key] !== expectedDeps.dependencies[key]) {
2737
+ issues.push(`Dependency version mismatch: ${key}`);
2738
+ }
2676
2739
  }
2677
- }
2678
- for (const key of Object.keys(expectedDeps.devDependencies)) {
2679
- if (!pkg.devDependencies || !(key in pkg.devDependencies)) {
2680
- issues.push(`Missing devDependency: ${key}`);
2681
- } else if (pkg.devDependencies[key] !== expectedDeps.devDependencies[key]) {
2682
- issues.push(`Dev dependency version mismatch: ${key}`);
2740
+ for (const key of Object.keys(expectedDeps.devDependencies)) {
2741
+ if (!pkg.devDependencies || !(key in pkg.devDependencies)) {
2742
+ issues.push(`Missing devDependency: ${key}`);
2743
+ } else if (pkg.devDependencies[key] !== expectedDeps.devDependencies[key]) {
2744
+ issues.push(`Dev dependency version mismatch: ${key}`);
2745
+ }
2683
2746
  }
2684
- }
2685
- if (options.live) {
2686
- const { fetchLatestVersion } = await import("./npm-registry-F7EVX3RR.js");
2687
- const allDeps = {
2688
- ...expectedDeps.dependencies,
2689
- ...expectedDeps.devDependencies
2690
- };
2691
- for (const [name, version] of Object.entries(allDeps)) {
2692
- const latest = await fetchLatestVersion(name);
2693
- if (latest && !version.includes(latest)) {
2694
- issues.push(`Latest available: ${name}@${latest} (current ${version})`);
2747
+ if (options.live) {
2748
+ const { fetchLatestVersion } = await import("./npm-registry-NY37YK2P.js");
2749
+ const allDeps = {
2750
+ ...expectedDeps.dependencies,
2751
+ ...expectedDeps.devDependencies
2752
+ };
2753
+ for (const [name, version2] of Object.entries(allDeps)) {
2754
+ const latest = await fetchLatestVersion(name);
2755
+ if (latest && !version2.includes(latest)) {
2756
+ issues.push(`Latest available: ${name}@${latest} (current ${version2})`);
2757
+ }
2695
2758
  }
2696
2759
  }
2760
+ if (issues.length === 0) {
2761
+ logger.info("No updates needed.");
2762
+ } else {
2763
+ for (const issue of issues) logger.warn(issue);
2764
+ }
2765
+ return;
2697
2766
  }
2698
- if (issues.length === 0) {
2699
- logger.info("No updates needed.");
2700
- } else {
2701
- for (const issue of issues) logger.warn(issue);
2702
- }
2703
- return;
2704
- }
2705
- await syncPackageJson(pkgPath, config, config);
2706
- if (options.live) {
2707
- const { fetchLatestVersion } = await import("./npm-registry-F7EVX3RR.js");
2708
- const pkg = await readPackageJson(pkgPath);
2709
- const allowMajor = Boolean(options.major);
2710
- const updateMap = async (deps) => {
2711
- if (!deps) return;
2712
- for (const [name, current] of Object.entries(deps)) {
2713
- const latest = await fetchLatestVersion(name);
2714
- if (!latest) continue;
2715
- const currentMajor = parseMajor(current);
2716
- const latestMajor = parseMajor(latest);
2717
- if (!allowMajor && currentMajor !== null && latestMajor !== null && latestMajor > currentMajor) {
2718
- continue;
2767
+ await syncPackageJson(pkgPath, config, config);
2768
+ if (options.live) {
2769
+ const { fetchLatestVersion } = await import("./npm-registry-NY37YK2P.js");
2770
+ const pkg = await readPackageJson(pkgPath);
2771
+ const allowMajor = Boolean(options.major);
2772
+ const updateMap = async (deps) => {
2773
+ if (!deps) return;
2774
+ for (const [name, current] of Object.entries(deps)) {
2775
+ const latest = await fetchLatestVersion(name);
2776
+ if (!latest) continue;
2777
+ const currentMajor = parseMajor(current);
2778
+ const latestMajor = parseMajor(latest);
2779
+ if (!allowMajor && currentMajor !== null && latestMajor !== null && latestMajor > currentMajor) {
2780
+ continue;
2781
+ }
2782
+ deps[name] = `^${latest}`;
2719
2783
  }
2720
- deps[name] = `^${latest}`;
2721
- }
2722
- };
2723
- await updateMap(pkg.dependencies);
2724
- await updateMap(pkg.devDependencies);
2725
- await writePackageJson(pkgPath, pkg);
2784
+ };
2785
+ await updateMap(pkg.dependencies);
2786
+ await updateMap(pkg.devDependencies);
2787
+ await writePackageJson(pkgPath, pkg);
2788
+ }
2789
+ const readme = buildProjectReadme(config);
2790
+ await writeTextFile(join15(cwd, "README.md"), readme + "\n");
2791
+ logger.info("Project updated.");
2792
+ } catch (err) {
2793
+ logger.error(err instanceof Error ? err.message : String(err));
2794
+ process.exitCode = 1;
2726
2795
  }
2727
- const readme = buildProjectReadme(config);
2728
- await writeTextFile(join15(cwd, "README.md"), readme + "\n");
2729
- logger.info("Project updated.");
2730
2796
  });
2731
- function parseMajor(version) {
2732
- const match = version.match(/(\d+)\./);
2797
+ function parseMajor(version2) {
2798
+ const match = version2.match(/(\d+)\./);
2733
2799
  if (!match) return null;
2734
2800
  return Number(match[1]);
2735
2801
  }
@@ -2766,6 +2832,8 @@ function agentFiles(agent) {
2766
2832
  if (agent === "gemini") files.push("function_declarations.json");
2767
2833
  if (agent === "cursor") files.push(".cursorrules");
2768
2834
  if (agent === "codeium") files.push("server-config.json");
2835
+ if (agent === "windsurf") files.push("cascade.json");
2836
+ if (agent === "tabnine") files.push("config.json");
2769
2837
  return files;
2770
2838
  }
2771
2839
  async function checkProject(cwd) {
@@ -2776,7 +2844,7 @@ async function checkProject(cwd) {
2776
2844
  if (!existsSync3(configPath)) {
2777
2845
  return {
2778
2846
  issues: ["Missing stackforge.json. Run from a StackForge project root."],
2779
- config: await Promise.reject(new Error("Missing stackforge.json")),
2847
+ config: void 0,
2780
2848
  pkgPath,
2781
2849
  envPath,
2782
2850
  hasConfig: false,
@@ -2857,8 +2925,9 @@ async function checkProject(cwd) {
2857
2925
  hasPackageJson: true
2858
2926
  };
2859
2927
  }
2860
- async function fixProject(result) {
2928
+ async function fixProject(result, cwd) {
2861
2929
  const { config, pkgPath, envPath } = result;
2930
+ if (!config) return;
2862
2931
  await syncPackageJson(pkgPath, config, config);
2863
2932
  const envContent = existsSync3(envPath) ? await readFile6(envPath, "utf8") : "";
2864
2933
  const envKeys = new Set(
@@ -2869,11 +2938,11 @@ async function fixProject(result) {
2869
2938
  await appendEnvLine(envPath, `${key}=""`);
2870
2939
  }
2871
2940
  if (config.aiAgents.length > 0) {
2872
- const base = basename(process.cwd()) === config.projectName ? dirname4(process.cwd()) : process.cwd();
2941
+ const base = basename(cwd) === config.projectName ? dirname4(cwd) : cwd;
2873
2942
  await generateAiAgentConfigs(base, config);
2874
2943
  }
2875
2944
  const readme = buildProjectReadme(config);
2876
- await writeTextFile(join16(process.cwd(), "README.md"), readme + "\n");
2945
+ await writeTextFile(join16(cwd, "README.md"), readme + "\n");
2877
2946
  }
2878
2947
 
2879
2948
  // src/cli/commands/doctor.ts
@@ -2913,7 +2982,7 @@ var doctorCommand = new Command6("doctor").option("--fix", "apply non-destructiv
2913
2982
  logger.warn(issue);
2914
2983
  }
2915
2984
  if (options.fix) {
2916
- await fixProject(result);
2985
+ await fixProject(result, cwd);
2917
2986
  logger.info("Applied fixes.");
2918
2987
  }
2919
2988
  });
@@ -2981,27 +3050,32 @@ import { Command as Command11 } from "commander";
2981
3050
  import { readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
2982
3051
  import { join as join18 } from "path";
2983
3052
  var migrateCommand = new Command11("migrate").option("--dry-run", "show planned migration without writing").action(async (options) => {
2984
- const cwd = process.cwd();
2985
- const path = join18(cwd, "stackforge.json");
2986
- const raw = await readFile8(path, "utf8");
2987
- const parsed = JSON.parse(raw);
2988
- const current = parsed._schemaVersion ?? 0;
2989
- if (current === STACKFORGE_SCHEMA_VERSION) {
2990
- logger.info("No migration needed.");
2991
- return;
2992
- }
2993
- logger.info(`Migrating schema ${current} -> ${STACKFORGE_SCHEMA_VERSION}`);
2994
- const migrated = migrateConfig(parsed);
2995
- if (!options.dryRun) {
2996
- await writeFile5(path, JSON.stringify(migrated, null, 2) + "\n", "utf8");
2997
- logger.info("Migration complete.");
3053
+ try {
3054
+ const cwd = process.cwd();
3055
+ const path = join18(cwd, "stackforge.json");
3056
+ const raw = await readFile8(path, "utf8");
3057
+ const parsed = JSON.parse(raw);
3058
+ const current = parsed._schemaVersion ?? 0;
3059
+ if (current === STACKFORGE_SCHEMA_VERSION) {
3060
+ logger.info("No migration needed.");
3061
+ return;
3062
+ }
3063
+ logger.info(`Migrating schema ${current} -> ${STACKFORGE_SCHEMA_VERSION}`);
3064
+ const migrated = migrateConfig(parsed);
3065
+ if (!options.dryRun) {
3066
+ await writeFile5(path, JSON.stringify(migrated, null, 2) + "\n", "utf8");
3067
+ logger.info("Migration complete.");
3068
+ }
3069
+ } catch (err) {
3070
+ logger.error(err instanceof Error ? err.message : String(err));
3071
+ process.exitCode = 1;
2998
3072
  }
2999
3073
  });
3000
3074
 
3001
3075
  // src/cli/commands/list-presets.ts
3002
3076
  import { Command as Command12 } from "commander";
3003
3077
  var listPresetsCommand = new Command12("list-presets").option("--details", "show preset details").action((options) => {
3004
- const presets2 = ["starter", "saas", "ecommerce", "blog", "api"];
3078
+ const presets2 = presetNames;
3005
3079
  for (const name of presets2) {
3006
3080
  const preset = getPreset(name);
3007
3081
  if (!preset) {
@@ -3056,12 +3130,13 @@ var validateCommand = new Command14("validate").description("validate project co
3056
3130
  // src/cli/commands/fix.ts
3057
3131
  import { Command as Command15 } from "commander";
3058
3132
  var fixCommand = new Command15("fix").description("apply safe fixes to project configuration").action(async () => {
3059
- const result = await checkProject(process.cwd());
3133
+ const cwd = process.cwd();
3134
+ const result = await checkProject(cwd);
3060
3135
  if (result.issues.length === 0) {
3061
3136
  logger.info("No issues found.");
3062
3137
  return;
3063
3138
  }
3064
- await fixProject(result);
3139
+ await fixProject(result, cwd);
3065
3140
  logger.info("Applied fixes.");
3066
3141
  });
3067
3142
 
@@ -3123,8 +3198,10 @@ var upgradeCommand = new Command16("upgrade").option("--preset <name>", "preset
3123
3198
  });
3124
3199
 
3125
3200
  // src/cli.ts
3201
+ var require2 = createRequire(import.meta.url);
3202
+ var { version } = require2("../package.json");
3126
3203
  var program = new Command17();
3127
- program.name("create-stackforge").description("Universal full-stack boilerplate generator").version("0.0.0");
3204
+ program.name("create-stackforge").description("Universal full-stack boilerplate generator").version(version);
3128
3205
  program.addCommand(createCommand);
3129
3206
  program.addCommand(listCommand);
3130
3207
  program.addCommand(addCommand);