@grainulation/grainulation 1.0.0 → 1.0.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 +46 -62
- package/bin/grainulation.js +14 -15
- package/lib/doctor.js +173 -60
- package/lib/ecosystem.js +65 -59
- package/lib/pm.js +100 -59
- package/lib/router.js +241 -183
- package/lib/server.mjs +165 -49
- package/lib/setup.js +47 -43
- package/package.json +15 -4
- package/public/grainulation-tokens.css +75 -80
package/lib/router.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
5
|
-
const path = require('node:path');
|
|
6
|
-
const { getByName, getInstallable } = require('./ecosystem');
|
|
1
|
+
const { execSync, spawn } = require("node:child_process");
|
|
2
|
+
const { existsSync } = require("node:fs");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
const { getByName, getInstallable } = require("./ecosystem");
|
|
7
5
|
|
|
8
6
|
/**
|
|
9
7
|
* Intent-based router.
|
|
@@ -11,15 +9,13 @@ const { getByName, getInstallable } = require('./ecosystem');
|
|
|
11
9
|
* Given a command or nothing at all, figure out where to send the user.
|
|
12
10
|
*/
|
|
13
11
|
|
|
14
|
-
const DELEGATE_COMMANDS = new Set(
|
|
15
|
-
getInstallable().map((t) => t.name)
|
|
16
|
-
);
|
|
12
|
+
const DELEGATE_COMMANDS = new Set(getInstallable().map((t) => t.name));
|
|
17
13
|
|
|
18
14
|
function overviewData() {
|
|
19
|
-
const { TOOLS } = require(
|
|
15
|
+
const { TOOLS } = require("./ecosystem");
|
|
20
16
|
return {
|
|
21
|
-
name:
|
|
22
|
-
description:
|
|
17
|
+
name: "grainulation",
|
|
18
|
+
description: "Structured research for decisions that satisfice.",
|
|
23
19
|
tools: TOOLS.map((tool) => ({
|
|
24
20
|
name: tool.name,
|
|
25
21
|
package: tool.package,
|
|
@@ -27,51 +23,63 @@ function overviewData() {
|
|
|
27
23
|
category: tool.category,
|
|
28
24
|
installed: isInstalled(tool.package),
|
|
29
25
|
})),
|
|
30
|
-
commands: [
|
|
26
|
+
commands: ["up", "down", "ps", "init", "status", "doctor", "<tool>"],
|
|
31
27
|
};
|
|
32
28
|
}
|
|
33
29
|
|
|
34
30
|
function overview() {
|
|
35
|
-
const { TOOLS } = require(
|
|
31
|
+
const { TOOLS } = require("./ecosystem");
|
|
36
32
|
const lines = [
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
"",
|
|
34
|
+
" \x1b[1;33mgrainulation\x1b[0m",
|
|
35
|
+
" Structured research for decisions that satisfice.",
|
|
36
|
+
"",
|
|
37
|
+
" \x1b[2mEcosystem:\x1b[0m",
|
|
38
|
+
"",
|
|
43
39
|
];
|
|
44
40
|
|
|
45
41
|
for (const tool of TOOLS) {
|
|
46
42
|
const installed = isInstalled(tool.package);
|
|
47
|
-
const marker = installed ?
|
|
48
|
-
const port = tool.port ? `:${tool.port}` :
|
|
49
|
-
lines.push(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
lines.push(
|
|
55
|
-
lines.push(
|
|
56
|
-
lines.push(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
lines.push(
|
|
60
|
-
lines.push(
|
|
61
|
-
lines.push(
|
|
62
|
-
lines.push(
|
|
63
|
-
lines.push(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
lines.push(
|
|
67
|
-
|
|
68
|
-
|
|
43
|
+
const marker = installed ? "\x1b[32m+\x1b[0m" : "\x1b[2m-\x1b[0m";
|
|
44
|
+
const port = tool.port ? `:${tool.port}` : "";
|
|
45
|
+
lines.push(
|
|
46
|
+
` ${marker} \x1b[1m${tool.name.padEnd(12)}\x1b[0m ${tool.role.padEnd(28)} \x1b[2m${port}\x1b[0m`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
lines.push("");
|
|
51
|
+
lines.push(" \x1b[2mProcess management:\x1b[0m");
|
|
52
|
+
lines.push(
|
|
53
|
+
" grainulation up [tools] Start tool servers (default: farmer + wheat)",
|
|
54
|
+
);
|
|
55
|
+
lines.push(" grainulation down Stop all running tools");
|
|
56
|
+
lines.push(" grainulation ps Show running tools, ports, health");
|
|
57
|
+
lines.push("");
|
|
58
|
+
lines.push(" \x1b[2mWorkflow:\x1b[0m");
|
|
59
|
+
lines.push(
|
|
60
|
+
" grainulation init Detect context and start a research sprint",
|
|
61
|
+
);
|
|
62
|
+
lines.push(
|
|
63
|
+
" grainulation status Cross-tool status: sprints, claims, services",
|
|
64
|
+
);
|
|
65
|
+
lines.push(
|
|
66
|
+
" grainulation doctor Check ecosystem health (install detection)",
|
|
67
|
+
);
|
|
68
|
+
lines.push(" grainulation <tool> Delegate to a grainulation tool");
|
|
69
|
+
lines.push("");
|
|
70
|
+
lines.push(" \x1b[2mStart here:\x1b[0m");
|
|
71
|
+
lines.push(" grainulation up && grainulation init");
|
|
72
|
+
lines.push("");
|
|
73
|
+
|
|
74
|
+
return lines.join("\n");
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
function isInstalled(packageName) {
|
|
72
78
|
try {
|
|
73
|
-
if (packageName ===
|
|
74
|
-
execSync(`npm list -g ${packageName} --depth=0 2>/dev/null`, {
|
|
79
|
+
if (packageName === "grainulation") return true;
|
|
80
|
+
execSync(`npm list -g ${packageName} --depth=0 2>/dev/null`, {
|
|
81
|
+
stdio: "pipe",
|
|
82
|
+
});
|
|
75
83
|
return true;
|
|
76
84
|
} catch {
|
|
77
85
|
try {
|
|
@@ -88,20 +96,21 @@ function isInstalled(packageName) {
|
|
|
88
96
|
* Returns the bin path if found, null otherwise.
|
|
89
97
|
*/
|
|
90
98
|
function findSourceBin(tool) {
|
|
91
|
-
const shortName = tool.package.replace(/^@[^/]+\//,
|
|
99
|
+
const shortName = tool.package.replace(/^@[^/]+\//, "");
|
|
92
100
|
const candidates = [
|
|
93
|
-
path.join(__dirname,
|
|
94
|
-
path.join(process.cwd(),
|
|
101
|
+
path.join(__dirname, "..", "..", shortName),
|
|
102
|
+
path.join(process.cwd(), "..", shortName),
|
|
95
103
|
];
|
|
96
104
|
for (const dir of candidates) {
|
|
97
105
|
try {
|
|
98
|
-
const pkgPath = path.join(dir,
|
|
106
|
+
const pkgPath = path.join(dir, "package.json");
|
|
99
107
|
if (!existsSync(pkgPath)) continue;
|
|
100
|
-
const pkg = JSON.parse(require(
|
|
108
|
+
const pkg = JSON.parse(require("node:fs").readFileSync(pkgPath, "utf-8"));
|
|
101
109
|
if (pkg.name !== tool.package) continue;
|
|
102
110
|
// Find the bin entry
|
|
103
111
|
if (pkg.bin) {
|
|
104
|
-
const binFile =
|
|
112
|
+
const binFile =
|
|
113
|
+
typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin)[0];
|
|
105
114
|
if (binFile) {
|
|
106
115
|
const binPath = path.resolve(dir, binFile);
|
|
107
116
|
if (existsSync(binPath)) return binPath;
|
|
@@ -129,21 +138,23 @@ function delegate(toolName, args) {
|
|
|
129
138
|
cmd = process.execPath;
|
|
130
139
|
cmdArgs = [sourceBin, ...args];
|
|
131
140
|
} else {
|
|
132
|
-
cmd =
|
|
141
|
+
cmd = "npx";
|
|
133
142
|
cmdArgs = [tool.package, ...args];
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
const child = spawn(cmd, cmdArgs, {
|
|
137
|
-
stdio:
|
|
138
|
-
shell: cmd ===
|
|
146
|
+
stdio: "inherit",
|
|
147
|
+
shell: cmd === "npx",
|
|
139
148
|
});
|
|
140
149
|
|
|
141
|
-
child.on(
|
|
150
|
+
child.on("close", (code) => {
|
|
142
151
|
process.exit(code ?? 0);
|
|
143
152
|
});
|
|
144
153
|
|
|
145
|
-
child.on(
|
|
146
|
-
console.error(
|
|
154
|
+
child.on("error", (err) => {
|
|
155
|
+
console.error(
|
|
156
|
+
`\x1b[31mgrainulation: failed to run ${tool.package}: ${err.message}\x1b[0m`,
|
|
157
|
+
);
|
|
147
158
|
console.error(`Try: npm install -g ${tool.package}`);
|
|
148
159
|
process.exit(1);
|
|
149
160
|
});
|
|
@@ -156,75 +167,90 @@ function delegate(toolName, args) {
|
|
|
156
167
|
function init(args, opts) {
|
|
157
168
|
const json = opts && opts.json;
|
|
158
169
|
const cwd = process.cwd();
|
|
159
|
-
const hasClaims = existsSync(path.join(cwd,
|
|
160
|
-
const hasCompilation = existsSync(path.join(cwd,
|
|
161
|
-
const hasGit = existsSync(path.join(cwd,
|
|
162
|
-
const hasPkg = existsSync(path.join(cwd,
|
|
170
|
+
const hasClaims = existsSync(path.join(cwd, "claims.json"));
|
|
171
|
+
const hasCompilation = existsSync(path.join(cwd, "compilation.json"));
|
|
172
|
+
const hasGit = existsSync(path.join(cwd, ".git"));
|
|
173
|
+
const hasPkg = existsSync(path.join(cwd, "package.json"));
|
|
163
174
|
|
|
164
175
|
if (json) {
|
|
165
176
|
// Pass --json through to wheat init
|
|
166
177
|
if (hasClaims && hasCompilation) {
|
|
167
|
-
console.log(
|
|
178
|
+
console.log(
|
|
179
|
+
JSON.stringify({
|
|
180
|
+
status: "exists",
|
|
181
|
+
directory: cwd,
|
|
182
|
+
hasClaims,
|
|
183
|
+
hasCompilation,
|
|
184
|
+
}),
|
|
185
|
+
);
|
|
168
186
|
return;
|
|
169
187
|
}
|
|
170
188
|
if (hasClaims) {
|
|
171
|
-
delegate(
|
|
189
|
+
delegate("wheat", ["compile", "--json", ...args]);
|
|
172
190
|
return;
|
|
173
191
|
}
|
|
174
|
-
const { detect } = require(
|
|
175
|
-
const wheatInfo = detect(
|
|
192
|
+
const { detect } = require("./doctor");
|
|
193
|
+
const wheatInfo = detect("@grainulation/wheat");
|
|
176
194
|
if (!wheatInfo) {
|
|
177
|
-
console.log(
|
|
195
|
+
console.log(
|
|
196
|
+
JSON.stringify({
|
|
197
|
+
status: "missing",
|
|
198
|
+
tool: "wheat",
|
|
199
|
+
install: "npm install -g @grainulation/wheat",
|
|
200
|
+
}),
|
|
201
|
+
);
|
|
178
202
|
return;
|
|
179
203
|
}
|
|
180
|
-
delegate(
|
|
204
|
+
delegate("wheat", ["init", "--json", ...args]);
|
|
181
205
|
return;
|
|
182
206
|
}
|
|
183
207
|
|
|
184
|
-
console.log(
|
|
185
|
-
console.log(
|
|
186
|
-
console.log(
|
|
208
|
+
console.log("");
|
|
209
|
+
console.log(" \x1b[1;33mgrainulation init\x1b[0m");
|
|
210
|
+
console.log("");
|
|
187
211
|
|
|
188
212
|
// Context detection
|
|
189
|
-
console.log(
|
|
213
|
+
console.log(" \x1b[2mDetected context:\x1b[0m");
|
|
190
214
|
console.log(` Directory ${cwd}`);
|
|
191
|
-
console.log(` Git repo ${hasGit ?
|
|
192
|
-
console.log(` package.json ${hasPkg ?
|
|
193
|
-
console.log(` claims.json ${hasClaims ?
|
|
194
|
-
console.log(
|
|
215
|
+
console.log(` Git repo ${hasGit ? "yes" : "no"}`);
|
|
216
|
+
console.log(` package.json ${hasPkg ? "yes" : "no"}`);
|
|
217
|
+
console.log(` claims.json ${hasClaims ? "yes (existing sprint)" : "no"}`);
|
|
218
|
+
console.log("");
|
|
195
219
|
|
|
196
220
|
if (hasClaims && hasCompilation) {
|
|
197
|
-
console.log(
|
|
198
|
-
console.log(
|
|
199
|
-
console.log(
|
|
200
|
-
console.log(
|
|
221
|
+
console.log(" An active sprint already exists in this directory.");
|
|
222
|
+
console.log(" To continue, use: grainulation wheat compile");
|
|
223
|
+
console.log(" To start fresh, remove claims.json first.");
|
|
224
|
+
console.log("");
|
|
201
225
|
return;
|
|
202
226
|
}
|
|
203
227
|
|
|
204
228
|
if (hasClaims) {
|
|
205
|
-
console.log(
|
|
206
|
-
|
|
207
|
-
|
|
229
|
+
console.log(
|
|
230
|
+
" Found claims.json but no compilation. Routing to wheat compile.",
|
|
231
|
+
);
|
|
232
|
+
console.log("");
|
|
233
|
+
delegate("wheat", ["compile", ...args]);
|
|
208
234
|
return;
|
|
209
235
|
}
|
|
210
236
|
|
|
211
237
|
// Check if wheat is available before delegating
|
|
212
|
-
const { detect } = require(
|
|
213
|
-
const wheatInfo = detect(
|
|
238
|
+
const { detect } = require("./doctor");
|
|
239
|
+
const wheatInfo = detect("@grainulation/wheat");
|
|
214
240
|
if (!wheatInfo) {
|
|
215
|
-
console.log(
|
|
216
|
-
console.log(
|
|
217
|
-
console.log(
|
|
218
|
-
console.log(
|
|
219
|
-
console.log(
|
|
220
|
-
console.log(
|
|
241
|
+
console.log(" wheat is not installed. Install it first:");
|
|
242
|
+
console.log(" npm install -g @grainulation/wheat");
|
|
243
|
+
console.log("");
|
|
244
|
+
console.log(" Or run interactively:");
|
|
245
|
+
console.log(" grainulation setup");
|
|
246
|
+
console.log("");
|
|
221
247
|
return;
|
|
222
248
|
}
|
|
223
249
|
|
|
224
250
|
// Route to wheat init
|
|
225
|
-
console.log(
|
|
226
|
-
console.log(
|
|
227
|
-
delegate(
|
|
251
|
+
console.log(" Routing to wheat init...");
|
|
252
|
+
console.log("");
|
|
253
|
+
delegate("wheat", ["init", ...args]);
|
|
228
254
|
}
|
|
229
255
|
|
|
230
256
|
/**
|
|
@@ -232,7 +258,7 @@ function init(args, opts) {
|
|
|
232
258
|
*/
|
|
233
259
|
function statusData() {
|
|
234
260
|
const cwd = process.cwd();
|
|
235
|
-
const { detect } = require(
|
|
261
|
+
const { detect } = require("./doctor");
|
|
236
262
|
const installable = getInstallable();
|
|
237
263
|
|
|
238
264
|
const tools = [];
|
|
@@ -247,33 +273,36 @@ function statusData() {
|
|
|
247
273
|
});
|
|
248
274
|
}
|
|
249
275
|
|
|
250
|
-
const hasClaims = existsSync(path.join(cwd,
|
|
251
|
-
const hasCompilation = existsSync(path.join(cwd,
|
|
276
|
+
const hasClaims = existsSync(path.join(cwd, "claims.json"));
|
|
277
|
+
const hasCompilation = existsSync(path.join(cwd, "compilation.json"));
|
|
252
278
|
let sprint = null;
|
|
253
279
|
|
|
254
280
|
if (hasClaims) {
|
|
255
281
|
try {
|
|
256
|
-
const claimsRaw = require(
|
|
282
|
+
const claimsRaw = require("node:fs").readFileSync(
|
|
283
|
+
path.join(cwd, "claims.json"),
|
|
284
|
+
"utf-8",
|
|
285
|
+
);
|
|
257
286
|
const claims = JSON.parse(claimsRaw);
|
|
258
|
-
const claimList = Array.isArray(claims) ? claims :
|
|
287
|
+
const claimList = Array.isArray(claims) ? claims : claims.claims || [];
|
|
259
288
|
const byType = {};
|
|
260
289
|
for (const c of claimList) {
|
|
261
|
-
const t = c.type ||
|
|
290
|
+
const t = c.type || "unknown";
|
|
262
291
|
byType[t] = (byType[t] || 0) + 1;
|
|
263
292
|
}
|
|
264
293
|
sprint = { claims: claimList.length, byType, compiled: hasCompilation };
|
|
265
294
|
} catch {
|
|
266
|
-
sprint = { error:
|
|
295
|
+
sprint = { error: "claims.json found but could not be parsed" };
|
|
267
296
|
}
|
|
268
297
|
}
|
|
269
298
|
|
|
270
299
|
let farmerPidValue = null;
|
|
271
|
-
const farmerPidPath = path.join(cwd,
|
|
272
|
-
const farmerPidAlt = path.join(cwd,
|
|
300
|
+
const farmerPidPath = path.join(cwd, "dashboard", ".farmer.pid");
|
|
301
|
+
const farmerPidAlt = path.join(cwd, ".farmer.pid");
|
|
273
302
|
for (const pidFile of [farmerPidPath, farmerPidAlt]) {
|
|
274
303
|
if (existsSync(pidFile)) {
|
|
275
304
|
try {
|
|
276
|
-
const pid = require(
|
|
305
|
+
const pid = require("node:fs").readFileSync(pidFile, "utf-8").trim();
|
|
277
306
|
process.kill(Number(pid), 0);
|
|
278
307
|
farmerPidValue = Number(pid);
|
|
279
308
|
} catch {
|
|
@@ -298,83 +327,88 @@ function status(opts) {
|
|
|
298
327
|
}
|
|
299
328
|
|
|
300
329
|
const cwd = process.cwd();
|
|
301
|
-
const { TOOLS } = require(
|
|
302
|
-
const { detect } = require(
|
|
330
|
+
const { TOOLS } = require("./ecosystem");
|
|
331
|
+
const { detect } = require("./doctor");
|
|
303
332
|
|
|
304
|
-
console.log(
|
|
305
|
-
console.log(
|
|
306
|
-
console.log(
|
|
333
|
+
console.log("");
|
|
334
|
+
console.log(" \x1b[1;33mgrainulation status\x1b[0m");
|
|
335
|
+
console.log("");
|
|
307
336
|
|
|
308
337
|
// Installed tools
|
|
309
|
-
console.log(
|
|
338
|
+
console.log(" \x1b[2mInstalled tools:\x1b[0m");
|
|
310
339
|
const installable = getInstallable();
|
|
311
340
|
let installedCount = 0;
|
|
312
341
|
for (const tool of installable) {
|
|
313
342
|
const result = detect(tool.package);
|
|
314
343
|
if (result) {
|
|
315
344
|
installedCount++;
|
|
316
|
-
console.log(
|
|
345
|
+
console.log(
|
|
346
|
+
` \x1b[32m+\x1b[0m ${tool.name.padEnd(12)} v${result.version} \x1b[2m(${result.method})\x1b[0m`,
|
|
347
|
+
);
|
|
317
348
|
}
|
|
318
349
|
}
|
|
319
350
|
if (installedCount === 0) {
|
|
320
|
-
console.log(
|
|
351
|
+
console.log(" \x1b[2m(none)\x1b[0m");
|
|
321
352
|
}
|
|
322
|
-
console.log(
|
|
353
|
+
console.log("");
|
|
323
354
|
|
|
324
355
|
// Active sprint detection
|
|
325
|
-
console.log(
|
|
356
|
+
console.log(" \x1b[2mCurrent directory:\x1b[0m");
|
|
326
357
|
console.log(` ${cwd}`);
|
|
327
|
-
console.log(
|
|
358
|
+
console.log("");
|
|
328
359
|
|
|
329
|
-
const hasClaims = existsSync(path.join(cwd,
|
|
330
|
-
const hasCompilation = existsSync(path.join(cwd,
|
|
360
|
+
const hasClaims = existsSync(path.join(cwd, "claims.json"));
|
|
361
|
+
const hasCompilation = existsSync(path.join(cwd, "compilation.json"));
|
|
331
362
|
|
|
332
363
|
if (hasClaims) {
|
|
333
364
|
try {
|
|
334
|
-
const claimsRaw = require(
|
|
365
|
+
const claimsRaw = require("node:fs").readFileSync(
|
|
366
|
+
path.join(cwd, "claims.json"),
|
|
367
|
+
"utf-8",
|
|
368
|
+
);
|
|
335
369
|
const claims = JSON.parse(claimsRaw);
|
|
336
|
-
const claimList = Array.isArray(claims) ? claims :
|
|
370
|
+
const claimList = Array.isArray(claims) ? claims : claims.claims || [];
|
|
337
371
|
const total = claimList.length;
|
|
338
372
|
const byType = {};
|
|
339
373
|
for (const c of claimList) {
|
|
340
|
-
const t = c.type ||
|
|
374
|
+
const t = c.type || "unknown";
|
|
341
375
|
byType[t] = (byType[t] || 0) + 1;
|
|
342
376
|
}
|
|
343
|
-
console.log(
|
|
377
|
+
console.log(" \x1b[2mActive sprint:\x1b[0m");
|
|
344
378
|
console.log(` Claims ${total}`);
|
|
345
379
|
const typeStr = Object.entries(byType)
|
|
346
380
|
.map(([k, v]) => `${k}: ${v}`)
|
|
347
|
-
.join(
|
|
381
|
+
.join(", ");
|
|
348
382
|
if (typeStr) {
|
|
349
383
|
console.log(` Breakdown ${typeStr}`);
|
|
350
384
|
}
|
|
351
385
|
if (hasCompilation) {
|
|
352
|
-
console.log(
|
|
386
|
+
console.log(" Compiled yes");
|
|
353
387
|
} else {
|
|
354
|
-
console.log(
|
|
388
|
+
console.log(" Compiled no (run: grainulation wheat compile)");
|
|
355
389
|
}
|
|
356
390
|
} catch {
|
|
357
|
-
console.log(
|
|
358
|
-
console.log(
|
|
391
|
+
console.log(" \x1b[2mActive sprint:\x1b[0m");
|
|
392
|
+
console.log(" claims.json found but could not be parsed");
|
|
359
393
|
}
|
|
360
394
|
} else {
|
|
361
|
-
console.log(
|
|
362
|
-
console.log(
|
|
395
|
+
console.log(" \x1b[2mNo active sprint in this directory.\x1b[0m");
|
|
396
|
+
console.log(" Start one with: grainulation init");
|
|
363
397
|
}
|
|
364
398
|
|
|
365
399
|
// Check for running farmer (look for .farmer.pid)
|
|
366
|
-
const farmerPid = path.join(cwd,
|
|
367
|
-
const farmerPidAlt = path.join(cwd,
|
|
400
|
+
const farmerPid = path.join(cwd, "dashboard", ".farmer.pid");
|
|
401
|
+
const farmerPidAlt = path.join(cwd, ".farmer.pid");
|
|
368
402
|
let farmerRunning = false;
|
|
369
403
|
for (const pidFile of [farmerPid, farmerPidAlt]) {
|
|
370
404
|
if (existsSync(pidFile)) {
|
|
371
405
|
try {
|
|
372
|
-
const pid = require(
|
|
406
|
+
const pid = require("node:fs").readFileSync(pidFile, "utf-8").trim();
|
|
373
407
|
// Check if process is still running
|
|
374
408
|
process.kill(Number(pid), 0);
|
|
375
409
|
farmerRunning = true;
|
|
376
|
-
console.log(
|
|
377
|
-
console.log(
|
|
410
|
+
console.log("");
|
|
411
|
+
console.log(" \x1b[2mRunning services:\x1b[0m");
|
|
378
412
|
console.log(` farmer pid ${pid}`);
|
|
379
413
|
} catch {
|
|
380
414
|
// PID file exists but process is not running
|
|
@@ -384,11 +418,11 @@ function status(opts) {
|
|
|
384
418
|
}
|
|
385
419
|
|
|
386
420
|
if (!farmerRunning) {
|
|
387
|
-
console.log(
|
|
388
|
-
console.log(
|
|
421
|
+
console.log("");
|
|
422
|
+
console.log(" \x1b[2mNo running services detected.\x1b[0m");
|
|
389
423
|
}
|
|
390
424
|
|
|
391
|
-
console.log(
|
|
425
|
+
console.log("");
|
|
392
426
|
}
|
|
393
427
|
|
|
394
428
|
function route(args, opts) {
|
|
@@ -407,37 +441,37 @@ function route(args, opts) {
|
|
|
407
441
|
}
|
|
408
442
|
|
|
409
443
|
// Built-in commands
|
|
410
|
-
if (command ===
|
|
411
|
-
require(
|
|
444
|
+
if (command === "doctor") {
|
|
445
|
+
require("./doctor").run({ json });
|
|
412
446
|
return;
|
|
413
447
|
}
|
|
414
|
-
if (command ===
|
|
415
|
-
require(
|
|
448
|
+
if (command === "setup") {
|
|
449
|
+
require("./setup").run();
|
|
416
450
|
return;
|
|
417
451
|
}
|
|
418
|
-
if (command ===
|
|
452
|
+
if (command === "init") {
|
|
419
453
|
init(rest, { json });
|
|
420
454
|
return;
|
|
421
455
|
}
|
|
422
|
-
if (command ===
|
|
456
|
+
if (command === "status") {
|
|
423
457
|
status({ json });
|
|
424
458
|
return;
|
|
425
459
|
}
|
|
426
460
|
|
|
427
461
|
// Process management
|
|
428
|
-
if (command ===
|
|
462
|
+
if (command === "up") {
|
|
429
463
|
pmUp(rest, { json });
|
|
430
464
|
return;
|
|
431
465
|
}
|
|
432
|
-
if (command ===
|
|
466
|
+
if (command === "down") {
|
|
433
467
|
pmDown(rest, { json });
|
|
434
468
|
return;
|
|
435
469
|
}
|
|
436
|
-
if (command ===
|
|
470
|
+
if (command === "ps") {
|
|
437
471
|
pmPs({ json });
|
|
438
472
|
return;
|
|
439
473
|
}
|
|
440
|
-
if (command ===
|
|
474
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
441
475
|
if (json) {
|
|
442
476
|
console.log(JSON.stringify(overviewData()));
|
|
443
477
|
} else {
|
|
@@ -445,8 +479,8 @@ function route(args, opts) {
|
|
|
445
479
|
}
|
|
446
480
|
return;
|
|
447
481
|
}
|
|
448
|
-
if (command ===
|
|
449
|
-
const pkg = require(
|
|
482
|
+
if (command === "--version" || command === "-v") {
|
|
483
|
+
const pkg = require("../package.json");
|
|
450
484
|
if (json) {
|
|
451
485
|
console.log(JSON.stringify({ version: pkg.version }));
|
|
452
486
|
} else {
|
|
@@ -457,7 +491,7 @@ function route(args, opts) {
|
|
|
457
491
|
|
|
458
492
|
// Delegate to a tool — pass --json through
|
|
459
493
|
if (DELEGATE_COMMANDS.has(command)) {
|
|
460
|
-
const delegateArgs = json ? [
|
|
494
|
+
const delegateArgs = json ? ["--json", ...rest] : rest;
|
|
461
495
|
delegate(command, delegateArgs);
|
|
462
496
|
return;
|
|
463
497
|
}
|
|
@@ -475,8 +509,8 @@ function route(args, opts) {
|
|
|
475
509
|
// --- Process management commands ---
|
|
476
510
|
|
|
477
511
|
function pmUp(args, opts) {
|
|
478
|
-
const pm = require(
|
|
479
|
-
const toolNames = args.filter(a => !a.startsWith(
|
|
512
|
+
const pm = require("./pm");
|
|
513
|
+
const toolNames = args.filter((a) => !a.startsWith("-"));
|
|
480
514
|
const results = pm.up(toolNames.length > 0 ? toolNames : undefined);
|
|
481
515
|
|
|
482
516
|
if (opts && opts.json) {
|
|
@@ -484,39 +518,45 @@ function pmUp(args, opts) {
|
|
|
484
518
|
return;
|
|
485
519
|
}
|
|
486
520
|
|
|
487
|
-
console.log(
|
|
488
|
-
console.log(
|
|
489
|
-
console.log(
|
|
521
|
+
console.log("");
|
|
522
|
+
console.log(" \x1b[1;33mgrainulation up\x1b[0m");
|
|
523
|
+
console.log("");
|
|
490
524
|
|
|
491
525
|
for (const r of results) {
|
|
492
526
|
if (r.error) {
|
|
493
527
|
console.log(` \x1b[31mx\x1b[0m ${r.name.padEnd(12)} ${r.error}`);
|
|
494
528
|
} else if (r.alreadyRunning) {
|
|
495
|
-
console.log(
|
|
529
|
+
console.log(
|
|
530
|
+
` \x1b[33m~\x1b[0m ${r.name.padEnd(12)} already running (pid ${r.pid}, port ${r.port})`,
|
|
531
|
+
);
|
|
496
532
|
} else {
|
|
497
|
-
console.log(
|
|
533
|
+
console.log(
|
|
534
|
+
` \x1b[32m+\x1b[0m ${r.name.padEnd(12)} started (pid ${r.pid}, port ${r.port})`,
|
|
535
|
+
);
|
|
498
536
|
}
|
|
499
537
|
}
|
|
500
538
|
|
|
501
|
-
console.log(
|
|
539
|
+
console.log("");
|
|
502
540
|
|
|
503
541
|
// Wait a moment then probe health
|
|
504
542
|
setTimeout(async () => {
|
|
505
543
|
const statuses = await pm.ps();
|
|
506
|
-
const running = statuses.filter(s => s.alive);
|
|
544
|
+
const running = statuses.filter((s) => s.alive);
|
|
507
545
|
if (running.length > 0) {
|
|
508
|
-
console.log(
|
|
546
|
+
console.log(" \x1b[2mHealth check:\x1b[0m");
|
|
509
547
|
for (const s of running) {
|
|
510
|
-
console.log(
|
|
548
|
+
console.log(
|
|
549
|
+
` \x1b[32m+\x1b[0m ${s.name.padEnd(12)} :${s.port} (${s.latencyMs}ms)`,
|
|
550
|
+
);
|
|
511
551
|
}
|
|
512
|
-
console.log(
|
|
552
|
+
console.log("");
|
|
513
553
|
}
|
|
514
554
|
}, 2000);
|
|
515
555
|
}
|
|
516
556
|
|
|
517
557
|
function pmDown(args, opts) {
|
|
518
|
-
const pm = require(
|
|
519
|
-
const toolNames = args.filter(a => !a.startsWith(
|
|
558
|
+
const pm = require("./pm");
|
|
559
|
+
const toolNames = args.filter((a) => !a.startsWith("-"));
|
|
520
560
|
const results = pm.down(toolNames.length > 0 ? toolNames : undefined);
|
|
521
561
|
|
|
522
562
|
if (opts && opts.json) {
|
|
@@ -524,27 +564,29 @@ function pmDown(args, opts) {
|
|
|
524
564
|
return;
|
|
525
565
|
}
|
|
526
566
|
|
|
527
|
-
console.log(
|
|
528
|
-
console.log(
|
|
529
|
-
console.log(
|
|
567
|
+
console.log("");
|
|
568
|
+
console.log(" \x1b[1;33mgrainulation down\x1b[0m");
|
|
569
|
+
console.log("");
|
|
530
570
|
|
|
531
571
|
let stoppedAny = false;
|
|
532
572
|
for (const r of results) {
|
|
533
573
|
if (r.stopped) {
|
|
534
574
|
stoppedAny = true;
|
|
535
|
-
console.log(
|
|
575
|
+
console.log(
|
|
576
|
+
` \x1b[31m-\x1b[0m ${r.name.padEnd(12)} stopped (pid ${r.pid})`,
|
|
577
|
+
);
|
|
536
578
|
}
|
|
537
579
|
}
|
|
538
580
|
|
|
539
581
|
if (!stoppedAny) {
|
|
540
|
-
console.log(
|
|
582
|
+
console.log(" \x1b[2mNo running tools to stop.\x1b[0m");
|
|
541
583
|
}
|
|
542
584
|
|
|
543
|
-
console.log(
|
|
585
|
+
console.log("");
|
|
544
586
|
}
|
|
545
587
|
|
|
546
588
|
async function pmPs(opts) {
|
|
547
|
-
const pm = require(
|
|
589
|
+
const pm = require("./pm");
|
|
548
590
|
const statuses = await pm.ps();
|
|
549
591
|
|
|
550
592
|
if (opts && opts.json) {
|
|
@@ -552,35 +594,51 @@ async function pmPs(opts) {
|
|
|
552
594
|
return;
|
|
553
595
|
}
|
|
554
596
|
|
|
555
|
-
console.log(
|
|
556
|
-
console.log(
|
|
557
|
-
console.log(
|
|
597
|
+
console.log("");
|
|
598
|
+
console.log(" \x1b[1;33mgrainulation ps\x1b[0m");
|
|
599
|
+
console.log("");
|
|
558
600
|
|
|
559
|
-
const running = statuses.filter(s => s.alive);
|
|
560
|
-
const stopped = statuses.filter(s => !s.alive);
|
|
601
|
+
const running = statuses.filter((s) => s.alive);
|
|
602
|
+
const stopped = statuses.filter((s) => !s.alive);
|
|
561
603
|
|
|
562
604
|
if (running.length > 0) {
|
|
563
|
-
console.log(
|
|
605
|
+
console.log(" \x1b[2mRunning:\x1b[0m");
|
|
564
606
|
for (const s of running) {
|
|
565
|
-
const pidStr = s.pid ? `pid ${s.pid}` :
|
|
566
|
-
const latency = s.latencyMs ? `${s.latencyMs}ms` :
|
|
567
|
-
console.log(
|
|
607
|
+
const pidStr = s.pid ? `pid ${s.pid}` : "unknown pid";
|
|
608
|
+
const latency = s.latencyMs ? `${s.latencyMs}ms` : "";
|
|
609
|
+
console.log(
|
|
610
|
+
` \x1b[32m+\x1b[0m ${s.name.padEnd(12)} :${String(s.port).padEnd(6)} ${pidStr.padEnd(14)} ${latency}`,
|
|
611
|
+
);
|
|
568
612
|
}
|
|
569
|
-
console.log(
|
|
613
|
+
console.log("");
|
|
570
614
|
}
|
|
571
615
|
|
|
572
616
|
if (stopped.length > 0) {
|
|
573
|
-
console.log(
|
|
617
|
+
console.log(" \x1b[2mStopped:\x1b[0m");
|
|
574
618
|
for (const s of stopped) {
|
|
575
619
|
console.log(` \x1b[2m- ${s.name.padEnd(12)} :${s.port}\x1b[0m`);
|
|
576
620
|
}
|
|
577
|
-
console.log(
|
|
621
|
+
console.log("");
|
|
578
622
|
}
|
|
579
623
|
|
|
580
624
|
if (running.length === 0) {
|
|
581
|
-
console.log(
|
|
582
|
-
|
|
625
|
+
console.log(
|
|
626
|
+
" \x1b[2mNo tools running. Start with: grainulation up\x1b[0m",
|
|
627
|
+
);
|
|
628
|
+
console.log("");
|
|
583
629
|
}
|
|
584
630
|
}
|
|
585
631
|
|
|
586
|
-
module.exports = {
|
|
632
|
+
module.exports = {
|
|
633
|
+
route,
|
|
634
|
+
overview,
|
|
635
|
+
overviewData,
|
|
636
|
+
isInstalled,
|
|
637
|
+
delegate,
|
|
638
|
+
init,
|
|
639
|
+
status,
|
|
640
|
+
statusData,
|
|
641
|
+
pmUp,
|
|
642
|
+
pmDown,
|
|
643
|
+
pmPs,
|
|
644
|
+
};
|