create-stackforge 0.0.1 → 0.1.1
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 +590 -56
- package/dist/cli.js +275 -198
- package/dist/cli.js.map +1 -1
- package/dist/{npm-registry-F7EVX3RR.js → npm-registry-NY37YK2P.js} +3 -3
- package/dist/npm-registry-NY37YK2P.js.map +1 -0
- package/package.json +45 -2
- package/templates/auth/nextauth-route.js +6 -0
- package/dist/npm-registry-F7EVX3RR.js.map +0 -1
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/)
|
|
671
|
-
const filtered = lines.filter((line) =>
|
|
672
|
-
|
|
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",
|
|
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
|
|
1361
|
-
await
|
|
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
|
|
1507
|
-
await
|
|
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'
|
|
1951
|
-
"case 'stackforge_orm'
|
|
1952
|
-
"case 'stackforge_api'
|
|
1953
|
-
"case 'stackforge_auth'
|
|
1954
|
-
"case 'stackforge_email'
|
|
1955
|
-
"case 'stackforge_storage'
|
|
1956
|
-
"case 'stackforge_payments'
|
|
1957
|
-
"case 'stackforge_analytics'
|
|
1958
|
-
"case 'stackforge_error-tracking'
|
|
1959
|
-
].join("
|
|
1960
|
-
const
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
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
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
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
|
|
2379
|
-
if (!
|
|
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
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
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
|
|
2593
|
-
if (!
|
|
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
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
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
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
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
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
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
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
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
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
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
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
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
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
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(
|
|
2732
|
-
const match =
|
|
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:
|
|
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(
|
|
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(
|
|
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
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
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 =
|
|
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
|
|
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,9 +3198,11 @@ 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(
|
|
3128
|
-
program.addCommand(createCommand);
|
|
3204
|
+
program.name("create-stackforge").description("Universal full-stack boilerplate generator").version(version);
|
|
3205
|
+
program.addCommand(createCommand, { isDefault: true });
|
|
3129
3206
|
program.addCommand(listCommand);
|
|
3130
3207
|
program.addCommand(addCommand);
|
|
3131
3208
|
program.addCommand(removeCommand);
|