brightspace-mcp-server 5.0.4 → 5.0.6
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/build/update.js +126 -19
- package/package.json +1 -1
package/build/update.js
CHANGED
|
@@ -15,7 +15,6 @@ dotenv.config({ quiet: true });
|
|
|
15
15
|
// ANSI color helpers
|
|
16
16
|
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
17
17
|
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
18
|
-
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
19
18
|
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
20
19
|
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
21
20
|
const projectRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -50,6 +49,80 @@ export function samePath(a, b) {
|
|
|
50
49
|
};
|
|
51
50
|
return canonical(a) === canonical(b);
|
|
52
51
|
}
|
|
52
|
+
/** Stop before anything is modified, saying why and what to do. */
|
|
53
|
+
function refuse(reason, remedy) {
|
|
54
|
+
console.error(red(` Error: ${reason}`));
|
|
55
|
+
console.error(` ${remedy}`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
const STABLE_VERSION = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
59
|
+
const COMMIT_SHA = /^[0-9a-f]{40}$/;
|
|
60
|
+
function versionTriple(version) {
|
|
61
|
+
const m = STABLE_VERSION.exec(version.trim().replace(/^v/, ""));
|
|
62
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
63
|
+
}
|
|
64
|
+
function compareTriples(a, b) {
|
|
65
|
+
for (let i = 0; i < 3; i++) {
|
|
66
|
+
if (a[i] !== b[i])
|
|
67
|
+
return a[i] - b[i];
|
|
68
|
+
}
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Whether `nodeVersion` meets an `engines.node` range. Only plain
|
|
73
|
+
* comparator sets (">=20.0.0", ">=20 <23") are understood; anything else
|
|
74
|
+
* yields null so the caller refuses instead of guessing.
|
|
75
|
+
*/
|
|
76
|
+
function satisfiesNodeRange(range, nodeVersion) {
|
|
77
|
+
const have = versionTriple(nodeVersion);
|
|
78
|
+
const comparators = range.trim().split(/\s+/);
|
|
79
|
+
if (!have || comparators.length === 0)
|
|
80
|
+
return null;
|
|
81
|
+
for (const comparator of comparators) {
|
|
82
|
+
const m = /^(>=|<=|>|<|=)?v?(\d+)(?:\.(\d+))?(?:\.(\d+))?$/.exec(comparator);
|
|
83
|
+
if (!m)
|
|
84
|
+
return null;
|
|
85
|
+
const want = [Number(m[2]), Number(m[3] ?? 0), Number(m[4] ?? 0)];
|
|
86
|
+
const cmp = compareTriples(have, want);
|
|
87
|
+
const ok = { ">=": cmp >= 0, "<=": cmp <= 0, ">": cmp > 0, "<": cmp < 0, "=": cmp === 0 }[m[1] ?? "="];
|
|
88
|
+
if (!ok)
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Decide which commit a checkout should move to, from what npm reports as the
|
|
95
|
+
* `latest` release. Only a stable version, published from a known commit, whose
|
|
96
|
+
* Node.js requirement this runtime meets, is a target; anything else is refused
|
|
97
|
+
* so the update fails closed.
|
|
98
|
+
*/
|
|
99
|
+
export function chooseRelease(registry, nodeVersion) {
|
|
100
|
+
const info = (registry ?? {});
|
|
101
|
+
const { version, gitHead } = info;
|
|
102
|
+
if (typeof version !== "string" || !versionTriple(version)) {
|
|
103
|
+
return { ok: false, reason: "npm did not report a stable release version." };
|
|
104
|
+
}
|
|
105
|
+
if (typeof gitHead !== "string" || !COMMIT_SHA.test(gitHead)) {
|
|
106
|
+
return { ok: false, reason: `npm does not record which commit ${version} was published from.` };
|
|
107
|
+
}
|
|
108
|
+
const nodeRange = info.engines?.node;
|
|
109
|
+
if (nodeRange !== undefined) {
|
|
110
|
+
const satisfied = typeof nodeRange === "string" ? satisfiesNodeRange(nodeRange, nodeVersion) : null;
|
|
111
|
+
if (satisfied === null) {
|
|
112
|
+
return { ok: false, reason: `Cannot check ${version}'s Node.js requirement (${String(nodeRange)}).` };
|
|
113
|
+
}
|
|
114
|
+
if (!satisfied) {
|
|
115
|
+
return { ok: false, reason: `${version} requires Node.js ${nodeRange}; this is Node.js ${nodeVersion}.` };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { ok: true, version, commit: gitHead };
|
|
119
|
+
}
|
|
120
|
+
function fail(...lines) {
|
|
121
|
+
console.error(red(` Error: ${lines[0]}`));
|
|
122
|
+
for (const line of lines.slice(1))
|
|
123
|
+
console.error(` ${line}`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
53
126
|
export function main() {
|
|
54
127
|
console.log("");
|
|
55
128
|
console.log(bold("=== Brightspace MCP Server — Update ==="));
|
|
@@ -81,25 +154,59 @@ export function main() {
|
|
|
81
154
|
const currentVersion = getVersion();
|
|
82
155
|
console.log(` Current version: ${bold(currentVersion)}`);
|
|
83
156
|
console.log("");
|
|
84
|
-
//
|
|
157
|
+
// The update fast-forwards the checked-out branch to origin/main, so it only
|
|
158
|
+
// runs where that cannot touch local work: a clean working tree on main.
|
|
85
159
|
const status = run("git status --porcelain", { silent: true });
|
|
86
160
|
if (status) {
|
|
87
|
-
|
|
88
|
-
console.log(dim(" The update will proceed, but your local changes may conflict."));
|
|
89
|
-
console.log("");
|
|
161
|
+
refuse("You have uncommitted changes.", "Commit or stash them, then run the update again.");
|
|
90
162
|
}
|
|
91
|
-
|
|
92
|
-
|
|
163
|
+
const branch = run("git branch --show-current", { silent: true });
|
|
164
|
+
if (branch !== "main") {
|
|
165
|
+
refuse(branch ? `You are on branch '${branch}', not main.` : "HEAD is detached, not on main.", "Run 'git switch main', then run the update again.");
|
|
166
|
+
}
|
|
167
|
+
// Resolve the latest published release: the checkout follows what npm
|
|
168
|
+
// ships, never the unreleased tip of main.
|
|
169
|
+
console.log(dim(" Checking the latest release on npm..."));
|
|
170
|
+
let registry;
|
|
171
|
+
try {
|
|
172
|
+
registry = JSON.parse(run("npm view brightspace-mcp-server@latest version gitHead engines --json", { silent: true }));
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
fail("Could not read the latest release from npm.", "Check your network connection and try again.");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const release = chooseRelease(registry, process.version);
|
|
179
|
+
if (!release.ok) {
|
|
180
|
+
fail("No release this checkout can safely update to.", release.reason);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const { version: targetVersion, commit } = release;
|
|
184
|
+
// Fetch, then confirm the published commit really is that release on main.
|
|
185
|
+
console.log(dim(` Fetching release ${targetVersion}...`));
|
|
93
186
|
try {
|
|
94
187
|
run("git fetch origin main", { silent: true });
|
|
95
188
|
}
|
|
96
189
|
catch {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
190
|
+
fail("Failed to fetch from remote.", "Check your network connection and try again.");
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
run(`git merge-base --is-ancestor ${commit} origin/main`, { silent: true });
|
|
195
|
+
const released = JSON.parse(run(`git show ${commit}:package.json`, { silent: true }));
|
|
196
|
+
if (released.version !== targetVersion)
|
|
197
|
+
throw new Error("version mismatch");
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
fail(`Could not verify release ${targetVersion} (commit ${commit.slice(0, 7)}) on origin/main.`, "Nothing was changed.");
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// Local commits on main that origin/main lacks would need a merge or rebase.
|
|
204
|
+
const aheadCount = run("git rev-list --count origin/main..HEAD", { silent: true });
|
|
205
|
+
if (aheadCount !== "0") {
|
|
206
|
+
refuse(`Your main has ${aheadCount} commit${aheadCount === "1" ? "" : "s"} that origin/main does not.`, "Move them to another branch or reconcile main with origin/main yourself, then run the update again.");
|
|
100
207
|
}
|
|
101
208
|
// Check if we're behind
|
|
102
|
-
const behindCount = run(
|
|
209
|
+
const behindCount = run(`git rev-list --count HEAD..${commit}`, { silent: true });
|
|
103
210
|
if (behindCount === "0") {
|
|
104
211
|
console.log("");
|
|
105
212
|
console.log(green(" Already up to date!"));
|
|
@@ -108,22 +215,22 @@ export function main() {
|
|
|
108
215
|
}
|
|
109
216
|
// Show what's new
|
|
110
217
|
console.log("");
|
|
111
|
-
console.log(bold(` ${behindCount} new commit${behindCount === "1" ? "" : "s"}:`));
|
|
218
|
+
console.log(bold(` ${behindCount} new commit${behindCount === "1" ? "" : "s"} in ${targetVersion}:`));
|
|
112
219
|
console.log("");
|
|
113
|
-
const log = run(
|
|
220
|
+
const log = run(`git log HEAD..${commit} --oneline`, { silent: true });
|
|
114
221
|
for (const line of log.split("\n")) {
|
|
115
222
|
console.log(` ${dim("•")} ${line}`);
|
|
116
223
|
}
|
|
117
224
|
console.log("");
|
|
118
|
-
//
|
|
119
|
-
|
|
225
|
+
// Fast-forward only, to the verified release: never a merge or rebase,
|
|
226
|
+
// whatever git's pull settings say, and no pull fallback if it fails.
|
|
227
|
+
console.log(dim(` Updating to ${targetVersion}...`));
|
|
120
228
|
try {
|
|
121
|
-
run(
|
|
229
|
+
run(`git merge --ff-only ${commit}`, { silent: true });
|
|
122
230
|
}
|
|
123
231
|
catch {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
process.exit(1);
|
|
232
|
+
fail(`Could not fast-forward to ${targetVersion}.`, "Nothing was changed. Check 'git status' and try again.");
|
|
233
|
+
return;
|
|
127
234
|
}
|
|
128
235
|
// Install dependencies
|
|
129
236
|
console.log(dim(" Installing dependencies..."));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brightspace-mcp-server",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.6",
|
|
4
4
|
"mcpName": "io.github.RohanMuppa/brightspace",
|
|
5
5
|
"description": "MCP server for Brightspace (D2L). Check grades, due dates, assignments, announcements, syllabus, rosters and more via Claude, ChatGPT, Cursor, Windsurf, or any MCP client.",
|
|
6
6
|
"type": "module",
|