brightspace-mcp-server 5.0.4 → 5.0.5

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.
Files changed (2) hide show
  1. package/build/update.js +107 -14
  2. package/package.json +1 -1
package/build/update.js CHANGED
@@ -50,6 +50,74 @@ export function samePath(a, b) {
50
50
  };
51
51
  return canonical(a) === canonical(b);
52
52
  }
53
+ const STABLE_VERSION = /^(\d+)\.(\d+)\.(\d+)$/;
54
+ const COMMIT_SHA = /^[0-9a-f]{40}$/;
55
+ function versionTriple(version) {
56
+ const m = STABLE_VERSION.exec(version.trim().replace(/^v/, ""));
57
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
58
+ }
59
+ function compareTriples(a, b) {
60
+ for (let i = 0; i < 3; i++) {
61
+ if (a[i] !== b[i])
62
+ return a[i] - b[i];
63
+ }
64
+ return 0;
65
+ }
66
+ /**
67
+ * Whether `nodeVersion` meets an `engines.node` range. Only plain
68
+ * comparator sets (">=20.0.0", ">=20 <23") are understood; anything else
69
+ * yields null so the caller refuses instead of guessing.
70
+ */
71
+ function satisfiesNodeRange(range, nodeVersion) {
72
+ const have = versionTriple(nodeVersion);
73
+ const comparators = range.trim().split(/\s+/);
74
+ if (!have || comparators.length === 0)
75
+ return null;
76
+ for (const comparator of comparators) {
77
+ const m = /^(>=|<=|>|<|=)?v?(\d+)(?:\.(\d+))?(?:\.(\d+))?$/.exec(comparator);
78
+ if (!m)
79
+ return null;
80
+ const want = [Number(m[2]), Number(m[3] ?? 0), Number(m[4] ?? 0)];
81
+ const cmp = compareTriples(have, want);
82
+ const ok = { ">=": cmp >= 0, "<=": cmp <= 0, ">": cmp > 0, "<": cmp < 0, "=": cmp === 0 }[m[1] ?? "="];
83
+ if (!ok)
84
+ return false;
85
+ }
86
+ return true;
87
+ }
88
+ /**
89
+ * Decide which commit a checkout should move to, from what npm reports as the
90
+ * `latest` release. Only a stable version, published from a known commit, whose
91
+ * Node.js requirement this runtime meets, is a target; anything else is refused
92
+ * so the update fails closed.
93
+ */
94
+ export function chooseRelease(registry, nodeVersion) {
95
+ const info = (registry ?? {});
96
+ const { version, gitHead } = info;
97
+ if (typeof version !== "string" || !versionTriple(version)) {
98
+ return { ok: false, reason: "npm did not report a stable release version." };
99
+ }
100
+ if (typeof gitHead !== "string" || !COMMIT_SHA.test(gitHead)) {
101
+ return { ok: false, reason: `npm does not record which commit ${version} was published from.` };
102
+ }
103
+ const nodeRange = info.engines?.node;
104
+ if (nodeRange !== undefined) {
105
+ const satisfied = typeof nodeRange === "string" ? satisfiesNodeRange(nodeRange, nodeVersion) : null;
106
+ if (satisfied === null) {
107
+ return { ok: false, reason: `Cannot check ${version}'s Node.js requirement (${String(nodeRange)}).` };
108
+ }
109
+ if (!satisfied) {
110
+ return { ok: false, reason: `${version} requires Node.js ${nodeRange}; this is Node.js ${nodeVersion}.` };
111
+ }
112
+ }
113
+ return { ok: true, version, commit: gitHead };
114
+ }
115
+ function fail(...lines) {
116
+ console.error(red(` Error: ${lines[0]}`));
117
+ for (const line of lines.slice(1))
118
+ console.error(` ${line}`);
119
+ process.exit(1);
120
+ }
53
121
  export function main() {
54
122
  console.log("");
55
123
  console.log(bold("=== Brightspace MCP Server — Update ==="));
@@ -88,18 +156,44 @@ export function main() {
88
156
  console.log(dim(" The update will proceed, but your local changes may conflict."));
89
157
  console.log("");
90
158
  }
91
- // Fetch latest from remote
92
- console.log(dim(" Fetching latest changes..."));
159
+ // Resolve the latest published release: the checkout follows what npm
160
+ // ships, never the unreleased tip of main.
161
+ console.log(dim(" Checking the latest release on npm..."));
162
+ let registry;
163
+ try {
164
+ registry = JSON.parse(run("npm view brightspace-mcp-server@latest version gitHead engines --json", { silent: true }));
165
+ }
166
+ catch {
167
+ fail("Could not read the latest release from npm.", "Check your network connection and try again.");
168
+ return;
169
+ }
170
+ const release = chooseRelease(registry, process.version);
171
+ if (!release.ok) {
172
+ fail("No release this checkout can safely update to.", release.reason);
173
+ return;
174
+ }
175
+ const { version: targetVersion, commit } = release;
176
+ // Fetch, then confirm the published commit really is that release on main.
177
+ console.log(dim(` Fetching release ${targetVersion}...`));
93
178
  try {
94
179
  run("git fetch origin main", { silent: true });
95
180
  }
96
181
  catch {
97
- console.error(red(" Error: Failed to fetch from remote."));
98
- console.error(" Check your network connection and try again.");
99
- process.exit(1);
182
+ fail("Failed to fetch from remote.", "Check your network connection and try again.");
183
+ return;
184
+ }
185
+ try {
186
+ run(`git merge-base --is-ancestor ${commit} origin/main`, { silent: true });
187
+ const released = JSON.parse(run(`git show ${commit}:package.json`, { silent: true }));
188
+ if (released.version !== targetVersion)
189
+ throw new Error("version mismatch");
190
+ }
191
+ catch {
192
+ fail(`Could not verify release ${targetVersion} (commit ${commit.slice(0, 7)}) on origin/main.`, "Nothing was changed.");
193
+ return;
100
194
  }
101
195
  // Check if we're behind
102
- const behindCount = run("git rev-list --count HEAD..origin/main", { silent: true });
196
+ const behindCount = run(`git rev-list --count HEAD..${commit}`, { silent: true });
103
197
  if (behindCount === "0") {
104
198
  console.log("");
105
199
  console.log(green(" Already up to date!"));
@@ -108,22 +202,21 @@ export function main() {
108
202
  }
109
203
  // Show what's new
110
204
  console.log("");
111
- console.log(bold(` ${behindCount} new commit${behindCount === "1" ? "" : "s"}:`));
205
+ console.log(bold(` ${behindCount} new commit${behindCount === "1" ? "" : "s"} in ${targetVersion}:`));
112
206
  console.log("");
113
- const log = run("git log HEAD..origin/main --oneline", { silent: true });
207
+ const log = run(`git log HEAD..${commit} --oneline`, { silent: true });
114
208
  for (const line of log.split("\n")) {
115
209
  console.log(` ${dim("•")} ${line}`);
116
210
  }
117
211
  console.log("");
118
- // Pull changes
119
- console.log(dim(" Pulling changes..."));
212
+ // Move to the release
213
+ console.log(dim(` Updating to ${targetVersion}...`));
120
214
  try {
121
- run("git pull origin main", { silent: true });
215
+ run(`git merge --ff-only ${commit}`, { silent: true });
122
216
  }
123
217
  catch {
124
- console.error(red(" Error: Failed to pull changes."));
125
- console.error(" You may have merge conflicts. Resolve them manually and try again.");
126
- process.exit(1);
218
+ fail(`Failed to update to ${targetVersion}.`, "Your checkout has local commits or changes in the way. Resolve them and try again.");
219
+ return;
127
220
  }
128
221
  // Install dependencies
129
222
  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.4",
3
+ "version": "5.0.5",
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",