@grainulation/grainulation 1.0.1 → 1.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/README.md +1 -1
- package/bin/grainulation.js +13 -14
- package/lib/doctor.js +67 -104
- package/lib/ecosystem.js +57 -65
- package/lib/pm.js +44 -69
- package/lib/router.js +179 -215
- package/lib/server.mjs +23 -34
- package/lib/setup.js +42 -51
- package/package.json +1 -2
- package/public/index.html +1 -1
package/lib/server.mjs
CHANGED
|
@@ -12,16 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { execSync } from 'node:child_process';
|
|
15
|
-
import {
|
|
16
|
-
existsSync,
|
|
17
|
-
mkdirSync,
|
|
18
|
-
readdirSync,
|
|
19
|
-
readFileSync,
|
|
20
|
-
renameSync,
|
|
21
|
-
statSync,
|
|
22
|
-
watchFile,
|
|
23
|
-
writeFileSync,
|
|
24
|
-
} from 'node:fs';
|
|
15
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
|
|
25
16
|
import { createServer } from 'node:http';
|
|
26
17
|
import { createRequire } from 'node:module';
|
|
27
18
|
import { dirname, extname, join, resolve } from 'node:path';
|
|
@@ -186,7 +177,7 @@ function detectTool(pkg) {
|
|
|
186
177
|
// 1. Global npm
|
|
187
178
|
try {
|
|
188
179
|
const out = execSync(`npm list -g ${pkg} --depth=0 2>/dev/null`, { stdio: 'pipe', encoding: 'utf-8' });
|
|
189
|
-
const match = out.match(new RegExp(escapeRegex(pkg)
|
|
180
|
+
const match = out.match(new RegExp(`${escapeRegex(pkg)}@(\\S+)`));
|
|
190
181
|
if (match) return { installed: true, version: match[1], method: 'global' };
|
|
191
182
|
} catch {
|
|
192
183
|
/* not found */
|
|
@@ -261,7 +252,7 @@ function runDoctor() {
|
|
|
261
252
|
// Environment checks
|
|
262
253
|
checks.push({
|
|
263
254
|
name: 'Node.js',
|
|
264
|
-
status: parseInt(nodeVersion.slice(1)) >= 18 ? 'pass' : 'warning',
|
|
255
|
+
status: parseInt(nodeVersion.slice(1), 10) >= 18 ? 'pass' : 'warning',
|
|
265
256
|
detail: nodeVersion,
|
|
266
257
|
category: 'environment',
|
|
267
258
|
});
|
|
@@ -334,16 +325,15 @@ function scaffold(targetDir, options = {}) {
|
|
|
334
325
|
mkdirSync(dir, { recursive: true });
|
|
335
326
|
|
|
336
327
|
// claims.json (atomic write-then-rename)
|
|
337
|
-
const claimsData =
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const tmpClaims = join(dir, 'claims.json.tmp.' + process.pid);
|
|
328
|
+
const claimsData = `${JSON.stringify(
|
|
329
|
+
{
|
|
330
|
+
claims: [],
|
|
331
|
+
meta: { created: new Date().toISOString(), tool: 'grainulation' },
|
|
332
|
+
},
|
|
333
|
+
null,
|
|
334
|
+
2,
|
|
335
|
+
)}\n`;
|
|
336
|
+
const tmpClaims = join(dir, `claims.json.tmp.${process.pid}`);
|
|
347
337
|
writeFileSync(tmpClaims, claimsData);
|
|
348
338
|
renameSync(tmpClaims, join(dir, 'claims.json'));
|
|
349
339
|
|
|
@@ -356,16 +346,15 @@ function scaffold(targetDir, options = {}) {
|
|
|
356
346
|
|
|
357
347
|
// orchard.json (if multi-sprint, atomic write-then-rename)
|
|
358
348
|
if (options.includeOrchard) {
|
|
359
|
-
const orchardData =
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const tmpOrchard = join(dir, 'orchard.json.tmp.' + process.pid);
|
|
349
|
+
const orchardData = `${JSON.stringify(
|
|
350
|
+
{
|
|
351
|
+
sprints: [],
|
|
352
|
+
settings: { sync_interval: 'manual' },
|
|
353
|
+
},
|
|
354
|
+
null,
|
|
355
|
+
2,
|
|
356
|
+
)}\n`;
|
|
357
|
+
const tmpOrchard = join(dir, `orchard.json.tmp.${process.pid}`);
|
|
369
358
|
writeFileSync(tmpOrchard, orchardData);
|
|
370
359
|
renameSync(tmpOrchard, join(dir, 'orchard.json'));
|
|
371
360
|
}
|
|
@@ -443,7 +432,7 @@ table{width:100%;border-collapse:collapse}th,td{padding:8px 12px;border-bottom:1
|
|
|
443
432
|
th{color:#9ca3af}code{background:#1e293b;padding:2px 6px;border-radius:4px;font-size:13px}</style></head>
|
|
444
433
|
<body><h1>grainulation API</h1><p>${ROUTES.length} endpoints</p>
|
|
445
434
|
<table><tr><th>Method</th><th>Path</th><th>Description</th></tr>
|
|
446
|
-
${ROUTES.map((r) =>
|
|
435
|
+
${ROUTES.map((r) => `<tr><td><code>${r.method}</code></td><td><code>${r.path}</code></td><td>${r.description}</td></tr>`).join('')}
|
|
447
436
|
</table></body></html>`;
|
|
448
437
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
449
438
|
res.end(html);
|
|
@@ -588,7 +577,7 @@ ${ROUTES.map((r) => '<tr><td><code>' + r.method + '</code></td><td><code>' + r.p
|
|
|
588
577
|
|
|
589
578
|
// ── Static files ──
|
|
590
579
|
const filePath = url.pathname === '/' ? '/index.html' : url.pathname;
|
|
591
|
-
const resolved = resolve(PUBLIC_DIR,
|
|
580
|
+
const resolved = resolve(PUBLIC_DIR, `.${filePath}`);
|
|
592
581
|
|
|
593
582
|
if (!resolved.startsWith(PUBLIC_DIR)) {
|
|
594
583
|
res.writeHead(403);
|
package/lib/setup.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const readline = require(
|
|
2
|
-
const { execSync } = require(
|
|
3
|
-
const { getInstallable, getCategories } = require(
|
|
4
|
-
const { getVersion } = require(
|
|
1
|
+
const readline = require('node:readline');
|
|
2
|
+
const { execSync } = require('node:child_process');
|
|
3
|
+
const { getInstallable, getCategories } = require('./ecosystem');
|
|
4
|
+
const { getVersion } = require('./doctor');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Interactive setup wizard.
|
|
@@ -12,24 +12,24 @@ const { getVersion } = require("./doctor");
|
|
|
12
12
|
|
|
13
13
|
const ROLES = [
|
|
14
14
|
{
|
|
15
|
-
name:
|
|
16
|
-
description:
|
|
17
|
-
tools: [
|
|
15
|
+
name: 'Researcher',
|
|
16
|
+
description: 'Run research sprints, grow evidence, write briefs',
|
|
17
|
+
tools: ['wheat'],
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
|
-
name:
|
|
21
|
-
description:
|
|
22
|
-
tools: [
|
|
20
|
+
name: 'Researcher + Dashboard',
|
|
21
|
+
description: 'Research sprints with real-time permission dashboard',
|
|
22
|
+
tools: ['wheat', 'farmer'],
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
|
-
name:
|
|
26
|
-
description:
|
|
27
|
-
tools: [
|
|
25
|
+
name: 'Team Lead',
|
|
26
|
+
description: 'Coordinate multiple sprints, review analytics',
|
|
27
|
+
tools: ['wheat', 'farmer', 'orchard', 'harvest'],
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
|
-
name:
|
|
31
|
-
description:
|
|
32
|
-
tools: [
|
|
30
|
+
name: 'Full Ecosystem',
|
|
31
|
+
description: 'Everything. All 7 tools.',
|
|
32
|
+
tools: ['wheat', 'farmer', 'barn', 'mill', 'silo', 'harvest', 'orchard'],
|
|
33
33
|
},
|
|
34
34
|
];
|
|
35
35
|
|
|
@@ -45,24 +45,24 @@ async function run() {
|
|
|
45
45
|
output: process.stdout,
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
console.log(
|
|
49
|
-
console.log(
|
|
50
|
-
console.log(
|
|
51
|
-
console.log(
|
|
48
|
+
console.log('');
|
|
49
|
+
console.log(' \x1b[1;33mgrainulation setup\x1b[0m');
|
|
50
|
+
console.log(' What are you trying to do?');
|
|
51
|
+
console.log('');
|
|
52
52
|
|
|
53
53
|
for (let i = 0; i < ROLES.length; i++) {
|
|
54
54
|
const role = ROLES[i];
|
|
55
55
|
console.log(` \x1b[1m${i + 1}.\x1b[0m ${role.name}`);
|
|
56
56
|
console.log(` \x1b[2m${role.description}\x1b[0m`);
|
|
57
|
-
console.log(` Tools: ${role.tools.join(
|
|
58
|
-
console.log(
|
|
57
|
+
console.log(` Tools: ${role.tools.join(', ')}`);
|
|
58
|
+
console.log('');
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
const answer = await ask(rl,
|
|
61
|
+
const answer = await ask(rl, ' Choose (1-4): ');
|
|
62
62
|
const choice = parseInt(answer, 10);
|
|
63
63
|
|
|
64
|
-
if (choice < 1 || choice > ROLES.length || isNaN(choice)) {
|
|
65
|
-
console.log(
|
|
64
|
+
if (choice < 1 || choice > ROLES.length || Number.isNaN(choice)) {
|
|
65
|
+
console.log('\n \x1b[31mInvalid choice.\x1b[0m\n');
|
|
66
66
|
rl.close();
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
@@ -70,9 +70,9 @@ async function run() {
|
|
|
70
70
|
const role = ROLES[choice - 1];
|
|
71
71
|
const toInstall = [];
|
|
72
72
|
|
|
73
|
-
console.log(
|
|
73
|
+
console.log('');
|
|
74
74
|
console.log(` \x1b[1mSetting up: ${role.name}\x1b[0m`);
|
|
75
|
-
console.log(
|
|
75
|
+
console.log('');
|
|
76
76
|
|
|
77
77
|
for (const toolName of role.tools) {
|
|
78
78
|
const tool = getInstallable().find((t) => t.name === toolName);
|
|
@@ -80,9 +80,7 @@ async function run() {
|
|
|
80
80
|
|
|
81
81
|
const version = getVersion(tool.package);
|
|
82
82
|
if (version) {
|
|
83
|
-
console.log(
|
|
84
|
-
` \x1b[32m\u2713\x1b[0m ${tool.name} already installed (${version})`,
|
|
85
|
-
);
|
|
83
|
+
console.log(` \x1b[32m\u2713\x1b[0m ${tool.name} already installed (${version})`);
|
|
86
84
|
} else {
|
|
87
85
|
toInstall.push(tool);
|
|
88
86
|
console.log(` \x1b[33m+\x1b[0m ${tool.name} will be installed`);
|
|
@@ -90,44 +88,37 @@ async function run() {
|
|
|
90
88
|
}
|
|
91
89
|
|
|
92
90
|
if (toInstall.length === 0) {
|
|
93
|
-
console.log(
|
|
94
|
-
console.log(
|
|
95
|
-
console.log(
|
|
96
|
-
" Run \x1b[1mnpx @grainulation/wheat init\x1b[0m to start a research sprint.",
|
|
97
|
-
);
|
|
91
|
+
console.log('');
|
|
92
|
+
console.log(' \x1b[32mEverything is already installed.\x1b[0m');
|
|
93
|
+
console.log(' Run \x1b[1mnpx @grainulation/wheat init\x1b[0m to start a research sprint.');
|
|
98
94
|
rl.close();
|
|
99
95
|
return;
|
|
100
96
|
}
|
|
101
97
|
|
|
102
|
-
console.log(
|
|
103
|
-
const confirm = await ask(
|
|
104
|
-
rl,
|
|
105
|
-
` Install ${toInstall.length} package(s)? (y/N): `,
|
|
106
|
-
);
|
|
98
|
+
console.log('');
|
|
99
|
+
const confirm = await ask(rl, ` Install ${toInstall.length} package(s)? (y/N): `);
|
|
107
100
|
|
|
108
|
-
if (confirm.toLowerCase() !==
|
|
109
|
-
console.log(
|
|
101
|
+
if (confirm.toLowerCase() !== 'y') {
|
|
102
|
+
console.log('\n \x1b[2mAborted.\x1b[0m\n');
|
|
110
103
|
rl.close();
|
|
111
104
|
return;
|
|
112
105
|
}
|
|
113
106
|
|
|
114
|
-
console.log(
|
|
107
|
+
console.log('');
|
|
115
108
|
for (const tool of toInstall) {
|
|
116
109
|
console.log(` Installing ${tool.package}...`);
|
|
117
110
|
try {
|
|
118
|
-
execSync(`npm install -g ${tool.package}`, { stdio:
|
|
111
|
+
execSync(`npm install -g ${tool.package}`, { stdio: 'pipe' });
|
|
119
112
|
console.log(` \x1b[32m\u2713\x1b[0m ${tool.name} installed`);
|
|
120
113
|
} catch (err) {
|
|
121
|
-
console.log(
|
|
122
|
-
` \x1b[31m\u2717\x1b[0m ${tool.name} failed: ${err.message}`,
|
|
123
|
-
);
|
|
114
|
+
console.log(` \x1b[31m\u2717\x1b[0m ${tool.name} failed: ${err.message}`);
|
|
124
115
|
}
|
|
125
116
|
}
|
|
126
117
|
|
|
127
|
-
console.log(
|
|
128
|
-
console.log(
|
|
129
|
-
console.log(
|
|
130
|
-
console.log(
|
|
118
|
+
console.log('');
|
|
119
|
+
console.log(' \x1b[32mSetup complete.\x1b[0m');
|
|
120
|
+
console.log(' Start with: npx @grainulation/wheat init');
|
|
121
|
+
console.log('');
|
|
131
122
|
rl.close();
|
|
132
123
|
}
|
|
133
124
|
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -440,7 +440,7 @@ body {
|
|
|
440
440
|
</main>
|
|
441
441
|
</div>
|
|
442
442
|
<footer class="footer">
|
|
443
|
-
<span>grainulation v1.0.
|
|
443
|
+
<span>grainulation v1.0.2 -- @grainulation/grainulation</span>
|
|
444
444
|
<div class="footer-links">
|
|
445
445
|
<a href="http://localhost:9091">wheat</a>
|
|
446
446
|
<a href="http://localhost:9090">farmer</a>
|