@prajwolkc/stk 0.5.0 → 0.5.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/dist/commands/init.js +246 -30
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -1,15 +1,165 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { writeFileSync, readFileSync, existsSync } from "fs";
|
|
4
5
|
import { basename } from "path";
|
|
5
6
|
import { CONFIG_FILE, KNOWN_SERVICES } from "../lib/config.js";
|
|
6
7
|
import { templates, listTemplates } from "../templates/index.js";
|
|
8
|
+
import { ingestProject, loadBrainStore, saveBrainStore, pullFromCloud, pushToCloud } from "../services/brain.js";
|
|
7
9
|
const DEPLOY_PROVIDERS = ["railway", "vercel", "fly", "render", "aws"];
|
|
10
|
+
/** Scan project files to detect what stack is being used */
|
|
11
|
+
function detectStackFromFiles() {
|
|
12
|
+
const detected = {};
|
|
13
|
+
const stack = [];
|
|
14
|
+
let projectType = "unknown";
|
|
15
|
+
// 1. Check env vars (existing behavior)
|
|
16
|
+
const envChecks = {
|
|
17
|
+
railway: ["RAILWAY_API_TOKEN"],
|
|
18
|
+
vercel: ["VERCEL_TOKEN"],
|
|
19
|
+
fly: ["FLY_API_TOKEN"],
|
|
20
|
+
render: ["RENDER_API_KEY"],
|
|
21
|
+
aws: ["AWS_ACCESS_KEY_ID"],
|
|
22
|
+
database: ["DATABASE_URL"],
|
|
23
|
+
mongodb: ["MONGODB_URL", "MONGO_URL"],
|
|
24
|
+
redis: ["REDIS_URL"],
|
|
25
|
+
supabase: ["SUPABASE_URL"],
|
|
26
|
+
r2: ["CLOUDFLARE_ACCOUNT_ID"],
|
|
27
|
+
stripe: ["STRIPE_SECRET_KEY"],
|
|
28
|
+
};
|
|
29
|
+
for (const [service, vars] of Object.entries(envChecks)) {
|
|
30
|
+
if (vars.some((v) => process.env[v])) {
|
|
31
|
+
detected[service] = true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// 2. Scan package.json files for dependencies
|
|
35
|
+
const pkgPaths = [
|
|
36
|
+
"package.json",
|
|
37
|
+
"node-backend/package.json",
|
|
38
|
+
"backend/package.json",
|
|
39
|
+
"server/package.json",
|
|
40
|
+
"api/package.json",
|
|
41
|
+
"frontend/package.json",
|
|
42
|
+
"web/package.json",
|
|
43
|
+
"client/package.json",
|
|
44
|
+
];
|
|
45
|
+
const allDeps = {};
|
|
46
|
+
for (const p of pkgPaths) {
|
|
47
|
+
if (existsSync(p)) {
|
|
48
|
+
try {
|
|
49
|
+
const pkg = JSON.parse(readFileSync(p, "utf-8"));
|
|
50
|
+
Object.assign(allDeps, pkg.dependencies ?? {}, pkg.devDependencies ?? {});
|
|
51
|
+
}
|
|
52
|
+
catch { /* skip */ }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Detect framework
|
|
56
|
+
if (allDeps["next"]) {
|
|
57
|
+
stack.push("Next.js");
|
|
58
|
+
projectType = "fullstack";
|
|
59
|
+
}
|
|
60
|
+
else if (allDeps["react"]) {
|
|
61
|
+
stack.push("React");
|
|
62
|
+
}
|
|
63
|
+
if (allDeps["vue"]) {
|
|
64
|
+
stack.push("Vue");
|
|
65
|
+
}
|
|
66
|
+
if (allDeps["angular"] || allDeps["@angular/core"]) {
|
|
67
|
+
stack.push("Angular");
|
|
68
|
+
}
|
|
69
|
+
if (allDeps["express"]) {
|
|
70
|
+
stack.push("Express");
|
|
71
|
+
projectType = projectType === "unknown" ? "api" : projectType;
|
|
72
|
+
}
|
|
73
|
+
if (allDeps["fastify"]) {
|
|
74
|
+
stack.push("Fastify");
|
|
75
|
+
projectType = projectType === "unknown" ? "api" : projectType;
|
|
76
|
+
}
|
|
77
|
+
if (allDeps["hono"]) {
|
|
78
|
+
stack.push("Hono");
|
|
79
|
+
}
|
|
80
|
+
// Detect ORM/DB
|
|
81
|
+
if (allDeps["prisma"] || allDeps["@prisma/client"]) {
|
|
82
|
+
stack.push("Prisma");
|
|
83
|
+
detected.database = true;
|
|
84
|
+
}
|
|
85
|
+
if (allDeps["mongoose"]) {
|
|
86
|
+
stack.push("Mongoose");
|
|
87
|
+
detected.mongodb = true;
|
|
88
|
+
}
|
|
89
|
+
if (allDeps["typeorm"]) {
|
|
90
|
+
stack.push("TypeORM");
|
|
91
|
+
detected.database = true;
|
|
92
|
+
}
|
|
93
|
+
if (allDeps["drizzle-orm"]) {
|
|
94
|
+
stack.push("Drizzle");
|
|
95
|
+
detected.database = true;
|
|
96
|
+
}
|
|
97
|
+
if (allDeps["@supabase/supabase-js"]) {
|
|
98
|
+
stack.push("Supabase SDK");
|
|
99
|
+
detected.supabase = true;
|
|
100
|
+
}
|
|
101
|
+
// Detect billing/payments
|
|
102
|
+
if (allDeps["stripe"] || allDeps["@stripe/stripe-js"]) {
|
|
103
|
+
stack.push("Stripe");
|
|
104
|
+
detected.stripe = true;
|
|
105
|
+
}
|
|
106
|
+
// Detect queue/cache
|
|
107
|
+
if (allDeps["bullmq"] || allDeps["bull"] || allDeps["ioredis"]) {
|
|
108
|
+
stack.push("Redis/BullMQ");
|
|
109
|
+
detected.redis = true;
|
|
110
|
+
}
|
|
111
|
+
// Detect auth
|
|
112
|
+
if (allDeps["jsonwebtoken"])
|
|
113
|
+
stack.push("JWT Auth");
|
|
114
|
+
if (allDeps["passport"])
|
|
115
|
+
stack.push("Passport.js");
|
|
116
|
+
if (allDeps["next-auth"] || allDeps["@auth/core"])
|
|
117
|
+
stack.push("Auth.js");
|
|
118
|
+
// 3. Scan for config files that indicate deploy providers
|
|
119
|
+
if (existsSync("railway.json") || existsSync("railway.toml"))
|
|
120
|
+
detected.railway = true;
|
|
121
|
+
if (existsSync("vercel.json") || existsSync(".vercel"))
|
|
122
|
+
detected.vercel = true;
|
|
123
|
+
if (existsSync("fly.toml"))
|
|
124
|
+
detected.fly = true;
|
|
125
|
+
if (existsSync("render.yaml"))
|
|
126
|
+
detected.render = true;
|
|
127
|
+
if (existsSync("Dockerfile"))
|
|
128
|
+
stack.push("Docker");
|
|
129
|
+
// 4. Scan for Prisma schema
|
|
130
|
+
const prismaPaths = [
|
|
131
|
+
"prisma/schema.prisma",
|
|
132
|
+
"node-backend/prisma/schema.prisma",
|
|
133
|
+
"backend/prisma/schema.prisma",
|
|
134
|
+
"src/prisma/schema.prisma",
|
|
135
|
+
];
|
|
136
|
+
for (const p of prismaPaths) {
|
|
137
|
+
if (existsSync(p)) {
|
|
138
|
+
detected.database = true;
|
|
139
|
+
if (!stack.includes("Prisma"))
|
|
140
|
+
stack.push("Prisma");
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// 5. Detect monorepo vs single
|
|
145
|
+
const hasMultiplePkgs = pkgPaths.filter(p => existsSync(p)).length > 1;
|
|
146
|
+
if (hasMultiplePkgs)
|
|
147
|
+
projectType = "fullstack";
|
|
148
|
+
// 6. Detect if frontend-only (static)
|
|
149
|
+
if (stack.length > 0 && !allDeps["express"] && !allDeps["fastify"] && !allDeps["hono"] && !allDeps["next"]) {
|
|
150
|
+
if (allDeps["react"] || allDeps["vue"] || allDeps["angular"]) {
|
|
151
|
+
if (projectType === "unknown")
|
|
152
|
+
projectType = "static";
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { detected, stack, projectType };
|
|
156
|
+
}
|
|
8
157
|
export const initCommand = new Command("init")
|
|
9
|
-
.description("Initialize stk config
|
|
158
|
+
.description("Initialize stk config — auto-detects stack, ingests knowledge, syncs brain")
|
|
10
159
|
.option("--force", "overwrite existing config")
|
|
11
160
|
.option("-t, --template <name>", `use a starter template (${listTemplates().join(", ")})`)
|
|
12
161
|
.option("--list-templates", "show available templates")
|
|
162
|
+
.option("--skip-brain", "skip brain ingest and sync")
|
|
13
163
|
.action(async (opts) => {
|
|
14
164
|
// List templates
|
|
15
165
|
if (opts.listTemplates) {
|
|
@@ -52,31 +202,24 @@ export const initCommand = new Command("init")
|
|
|
52
202
|
console.log(` ${icon} ${enabled ? chalk.white(name) : chalk.dim(name)}`);
|
|
53
203
|
}
|
|
54
204
|
console.log();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
console.log();
|
|
205
|
+
// Run brain steps even for template init
|
|
206
|
+
if (!opts.skipBrain) {
|
|
207
|
+
await runBrainSteps(projectName);
|
|
208
|
+
}
|
|
60
209
|
return;
|
|
61
210
|
}
|
|
62
|
-
// Auto-detect
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
r2: ["CLOUDFLARE_ACCOUNT_ID"],
|
|
75
|
-
stripe: ["STRIPE_SECRET_KEY"],
|
|
76
|
-
};
|
|
77
|
-
for (const [service, vars] of Object.entries(envChecks)) {
|
|
78
|
-
if (vars.some((v) => process.env[v])) {
|
|
79
|
-
detected[service] = true;
|
|
211
|
+
// ─── Smart Auto-detect Init ───
|
|
212
|
+
console.log();
|
|
213
|
+
const detectSpinner = ora(" Scanning project...").start();
|
|
214
|
+
const { detected, stack, projectType } = detectStackFromFiles();
|
|
215
|
+
detectSpinner.succeed(" Project scanned");
|
|
216
|
+
// Show detected stack
|
|
217
|
+
if (stack.length > 0) {
|
|
218
|
+
console.log();
|
|
219
|
+
console.log(chalk.bold(" Detected stack:"));
|
|
220
|
+
console.log(` ${chalk.cyan(stack.join(" + "))}`);
|
|
221
|
+
if (projectType !== "unknown") {
|
|
222
|
+
console.log(` ${chalk.dim(`Project type: ${projectType}`)}`);
|
|
80
223
|
}
|
|
81
224
|
}
|
|
82
225
|
const config = {
|
|
@@ -94,18 +237,91 @@ export const initCommand = new Command("init")
|
|
|
94
237
|
console.log();
|
|
95
238
|
console.log(` ${chalk.green("✓")} Created ${chalk.bold(CONFIG_FILE)}`);
|
|
96
239
|
console.log();
|
|
97
|
-
console.log(chalk.bold("
|
|
240
|
+
console.log(chalk.bold(" Services:"));
|
|
98
241
|
const serviceNames = Object.entries(config.services);
|
|
99
242
|
for (const [name, enabled] of serviceNames) {
|
|
100
243
|
const icon = enabled ? chalk.green("✓") : chalk.dim("○");
|
|
101
244
|
const label = enabled
|
|
102
245
|
? chalk.white(name)
|
|
103
|
-
: chalk.dim(
|
|
246
|
+
: chalk.dim(name);
|
|
104
247
|
console.log(` ${icon} ${label}`);
|
|
105
248
|
}
|
|
249
|
+
// ─── Brain Steps ───
|
|
250
|
+
if (!opts.skipBrain) {
|
|
251
|
+
await runBrainSteps(projectName);
|
|
252
|
+
}
|
|
106
253
|
console.log();
|
|
107
|
-
console.log(chalk.dim(`
|
|
108
|
-
console.log(chalk.dim(` Or try: ${chalk.white("stk init --template saas")} for a pre-configured stack.`));
|
|
109
|
-
console.log(chalk.dim(` Then run ${chalk.white("stk health")} to verify.`));
|
|
254
|
+
console.log(chalk.dim(` Run ${chalk.white("stk health")} to verify services.`));
|
|
110
255
|
console.log();
|
|
111
256
|
});
|
|
257
|
+
/** Ingest project, pull cloud knowledge, push new knowledge */
|
|
258
|
+
async function runBrainSteps(projectName) {
|
|
259
|
+
console.log();
|
|
260
|
+
console.log(chalk.bold(" Brain setup:"));
|
|
261
|
+
// Step 1: Ingest project
|
|
262
|
+
const ingestSpinner = ora(" Scanning project architecture...").start();
|
|
263
|
+
try {
|
|
264
|
+
const { entries, filesScanned } = ingestProject(process.cwd());
|
|
265
|
+
if (entries.length > 0) {
|
|
266
|
+
const store = loadBrainStore();
|
|
267
|
+
store.projects[projectName] = {
|
|
268
|
+
ingestedAt: new Date().toISOString(),
|
|
269
|
+
projectPath: process.cwd(),
|
|
270
|
+
entries,
|
|
271
|
+
};
|
|
272
|
+
saveBrainStore(store);
|
|
273
|
+
ingestSpinner.succeed(` Ingested ${chalk.white(entries.length)} knowledge entries from ${filesScanned.length} files`);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
ingestSpinner.warn(" No project files found to ingest");
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
ingestSpinner.warn(" Ingest skipped (no recognizable files)");
|
|
281
|
+
}
|
|
282
|
+
// Step 2: Pull cloud brain (learn from other projects)
|
|
283
|
+
const pullSpinner = ora(" Pulling knowledge from cloud brain...").start();
|
|
284
|
+
try {
|
|
285
|
+
const pullResult = await pullFromCloud();
|
|
286
|
+
if (pullResult.errors.length > 0) {
|
|
287
|
+
pullSpinner.warn(" Cloud brain not available (set SUPABASE_URL + SUPABASE_SERVICE_KEY to enable)");
|
|
288
|
+
}
|
|
289
|
+
else if (pullResult.pulled > 0) {
|
|
290
|
+
pullSpinner.succeed(` Pulled ${chalk.white(pullResult.pulled)} entries from cloud (learned from other projects)`);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
pullSpinner.succeed(" Cloud brain in sync");
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
pullSpinner.warn(" Cloud sync skipped (no connection)");
|
|
298
|
+
}
|
|
299
|
+
// Step 3: Push new knowledge to cloud
|
|
300
|
+
const pushSpinner = ora(" Sharing knowledge to cloud brain...").start();
|
|
301
|
+
try {
|
|
302
|
+
const pushResult = await pushToCloud();
|
|
303
|
+
if (pushResult.errors.length > 0) {
|
|
304
|
+
pushSpinner.warn(" Push skipped");
|
|
305
|
+
}
|
|
306
|
+
else if (pushResult.pushed > 0) {
|
|
307
|
+
pushSpinner.succeed(` Pushed ${chalk.white(pushResult.pushed)} entries to cloud`);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
pushSpinner.succeed(" All knowledge already shared");
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
pushSpinner.warn(" Push skipped (no connection)");
|
|
315
|
+
}
|
|
316
|
+
// Show what we know from other projects
|
|
317
|
+
const store = loadBrainStore();
|
|
318
|
+
const otherProjects = Object.keys(store.projects).filter(p => p !== projectName);
|
|
319
|
+
if (otherProjects.length > 0) {
|
|
320
|
+
console.log();
|
|
321
|
+
console.log(chalk.bold(" Knowledge available from other projects:"));
|
|
322
|
+
for (const proj of otherProjects) {
|
|
323
|
+
const p = store.projects[proj];
|
|
324
|
+
console.log(` ${chalk.green("●")} ${chalk.white(proj)} — ${p.entries.length} entries`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prajwolkc/stk",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "One CLI to deploy, monitor, debug, and learn about your entire stack. Infrastructure monitoring, knowledge base brain, deploy watching, and GitHub issues — all from one command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|