brightspace-mcp-server 2.0.0 → 3.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 +35 -52
- package/build/api/client.d.ts +32 -7
- package/build/api/client.d.ts.map +1 -1
- package/build/api/client.js +93 -27
- package/build/api/client.js.map +1 -1
- package/build/auth/auth-cooldown.d.ts +8 -0
- package/build/auth/auth-cooldown.d.ts.map +1 -1
- package/build/auth/auth-cooldown.js +15 -2
- package/build/auth/auth-cooldown.js.map +1 -1
- package/build/auth/auth-lock.d.ts.map +1 -1
- package/build/auth/auth-lock.js +51 -3
- package/build/auth/auth-lock.js.map +1 -1
- package/build/auth/auth-runner.d.ts +15 -4
- package/build/auth/auth-runner.d.ts.map +1 -1
- package/build/auth/auth-runner.js +128 -109
- package/build/auth/auth-runner.js.map +1 -1
- package/build/auth/browser-auth.d.ts +2 -1
- package/build/auth/browser-auth.d.ts.map +1 -1
- package/build/auth/browser-auth.js +15 -4
- package/build/auth/browser-auth.js.map +1 -1
- package/build/auth/credential-store.d.ts.map +1 -1
- package/build/auth/credential-store.js +12 -2
- package/build/auth/credential-store.js.map +1 -1
- package/build/auth/purdue-sso.d.ts +7 -0
- package/build/auth/purdue-sso.d.ts.map +1 -1
- package/build/auth/purdue-sso.js +46 -0
- package/build/auth/purdue-sso.js.map +1 -1
- package/build/auth/sso-flow.d.ts +2 -1
- package/build/auth/sso-flow.d.ts.map +1 -1
- package/build/auth/sso-flow.js +10 -3
- package/build/auth/sso-flow.js.map +1 -1
- package/build/auth/suny-sso.d.ts +3 -0
- package/build/auth/suny-sso.d.ts.map +1 -1
- package/build/auth/suny-sso.js +2 -0
- package/build/auth/suny-sso.js.map +1 -1
- package/build/auth-cli.js +49 -4
- package/build/auth-cli.js.map +1 -1
- package/build/index.js +13 -61
- package/build/index.js.map +1 -1
- package/build/setup.js +32 -13
- package/build/setup.js.map +1 -1
- package/build/tools/get-assignments.d.ts.map +1 -1
- package/build/tools/get-assignments.js +157 -82
- package/build/tools/get-assignments.js.map +1 -1
- package/build/tools/tool-helpers.d.ts +7 -1
- package/build/tools/tool-helpers.d.ts.map +1 -1
- package/build/tools/tool-helpers.js +96 -11
- package/build/tools/tool-helpers.js.map +1 -1
- package/build/utils/browser-install.d.ts +23 -0
- package/build/utils/browser-install.d.ts.map +1 -0
- package/build/utils/browser-install.js +33 -0
- package/build/utils/browser-install.js.map +1 -0
- package/build/utils/commands.d.ts +26 -0
- package/build/utils/commands.d.ts.map +1 -0
- package/build/utils/commands.js +26 -0
- package/build/utils/commands.js.map +1 -0
- package/build/utils/config.d.ts.map +1 -1
- package/build/utils/config.js +4 -2
- package/build/utils/config.js.map +1 -1
- package/build/utils/download-errors.d.ts +45 -0
- package/build/utils/download-errors.d.ts.map +1 -0
- package/build/utils/download-errors.js +45 -0
- package/build/utils/download-errors.js.map +1 -0
- package/build/utils/download-helpers.d.ts.map +1 -1
- package/build/utils/download-helpers.js +2 -1
- package/build/utils/download-helpers.js.map +1 -1
- package/build/utils/file-validator.d.ts +1 -1
- package/build/utils/file-validator.d.ts.map +1 -1
- package/build/utils/file-validator.js +47 -7
- package/build/utils/file-validator.js.map +1 -1
- package/build/utils/install-sites.d.ts +61 -0
- package/build/utils/install-sites.d.ts.map +1 -0
- package/build/utils/install-sites.js +207 -0
- package/build/utils/install-sites.js.map +1 -0
- package/build/utils/self-update.d.ts +61 -0
- package/build/utils/self-update.d.ts.map +1 -0
- package/build/utils/self-update.js +92 -0
- package/build/utils/self-update.js.map +1 -0
- package/build/utils/update-checker.d.ts +85 -7
- package/build/utils/update-checker.d.ts.map +1 -1
- package/build/utils/update-checker.js +141 -36
- package/build/utils/update-checker.js.map +1 -1
- package/package.json +1 -1
|
@@ -6,6 +6,30 @@
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { fileTypeFromBuffer } from "file-type";
|
|
8
8
|
import sanitizeFilename from "sanitize-filename";
|
|
9
|
+
import { DownloadError } from "./download-errors.js";
|
|
10
|
+
/**
|
|
11
|
+
* file-type reports every OLE2 Compound File Binary container as
|
|
12
|
+
* application/x-cfb. It does not read the CFB directory, so it cannot tell a
|
|
13
|
+
* .doc from a .xls from an .msi. The three legacy Office entries in
|
|
14
|
+
* ALLOWED_MIME_TYPES were therefore unreachable and every legacy Office
|
|
15
|
+
* download failed with a generic error.
|
|
16
|
+
*
|
|
17
|
+
* Allowing application/x-cfb outright is not the fix: .msi installers are the
|
|
18
|
+
* same container, and letting one through under a .doc name is exactly what a
|
|
19
|
+
* magic-byte allowlist exists to prevent. Reconcile against the declared
|
|
20
|
+
* extension instead, which admits the formats the allowlist already intended
|
|
21
|
+
* and still refuses everything else.
|
|
22
|
+
*/
|
|
23
|
+
const CFB_MIME = "application/x-cfb";
|
|
24
|
+
const CFB_EXTENSION_MIMES = {
|
|
25
|
+
".doc": "application/msword",
|
|
26
|
+
".dot": "application/msword",
|
|
27
|
+
".xls": "application/vnd.ms-excel",
|
|
28
|
+
".xlt": "application/vnd.ms-excel",
|
|
29
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
30
|
+
".pot": "application/vnd.ms-powerpoint",
|
|
31
|
+
".pps": "application/vnd.ms-powerpoint",
|
|
32
|
+
};
|
|
9
33
|
/**
|
|
10
34
|
* Maximum file size for downloads (50 MB).
|
|
11
35
|
* Prevents memory exhaustion from malicious large file requests.
|
|
@@ -53,12 +77,20 @@ export const ALLOWED_MIME_TYPES = [
|
|
|
53
77
|
* @throws Error if path traversal detected
|
|
54
78
|
*/
|
|
55
79
|
export function validateDownloadPath(baseDir, filename) {
|
|
56
|
-
//
|
|
57
|
-
|
|
80
|
+
// A filename carrying a bare '%' (say "100% Final.doc") makes
|
|
81
|
+
// decodeURIComponent throw URIError. Brightspace supplies these names, so
|
|
82
|
+
// that is remote input, and the raw name is the right fallback.
|
|
83
|
+
let decoded;
|
|
84
|
+
try {
|
|
85
|
+
decoded = decodeURIComponent(filename);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
decoded = filename;
|
|
89
|
+
}
|
|
58
90
|
// Sanitize filename (removes path separators, null bytes, etc.)
|
|
59
91
|
const sanitized = sanitizeFilename(decoded);
|
|
60
92
|
if (!sanitized || sanitized.length === 0) {
|
|
61
|
-
throw new
|
|
93
|
+
throw new DownloadError("badFilename", "Invalid filename after sanitization");
|
|
62
94
|
}
|
|
63
95
|
// Resolve full path
|
|
64
96
|
const fullPath = path.resolve(baseDir, sanitized);
|
|
@@ -66,7 +98,7 @@ export function validateDownloadPath(baseDir, filename) {
|
|
|
66
98
|
// Verify resolved path is within base directory
|
|
67
99
|
if (!fullPath.startsWith(resolvedBase + path.sep) &&
|
|
68
100
|
fullPath !== resolvedBase) {
|
|
69
|
-
throw new
|
|
101
|
+
throw new DownloadError("pathTraversal", "Path traversal detected");
|
|
70
102
|
}
|
|
71
103
|
return fullPath;
|
|
72
104
|
}
|
|
@@ -79,12 +111,20 @@ export function validateDownloadPath(baseDir, filename) {
|
|
|
79
111
|
* @returns Detected MIME type and extension
|
|
80
112
|
* @throws Error if file type not allowed
|
|
81
113
|
*/
|
|
82
|
-
export async function validateFileType(buffer, allowedTypes = ALLOWED_MIME_TYPES) {
|
|
114
|
+
export async function validateFileType(buffer, allowedTypes = ALLOWED_MIME_TYPES, filename) {
|
|
83
115
|
// Try magic byte detection first
|
|
84
116
|
const detected = await fileTypeFromBuffer(buffer);
|
|
85
117
|
if (detected) {
|
|
118
|
+
if (detected.mime === CFB_MIME) {
|
|
119
|
+
const ext = filename ? path.extname(filename).toLowerCase() : "";
|
|
120
|
+
const resolved = CFB_EXTENSION_MIMES[ext];
|
|
121
|
+
if (resolved && allowedTypes.includes(resolved)) {
|
|
122
|
+
return { mime: resolved, ext: ext.slice(1) };
|
|
123
|
+
}
|
|
124
|
+
throw new DownloadError("unsupportedType", `Compound File Binary with extension '${ext || "none"}' is not an allowed Office format`, CFB_MIME);
|
|
125
|
+
}
|
|
86
126
|
if (!allowedTypes.includes(detected.mime)) {
|
|
87
|
-
throw new
|
|
127
|
+
throw new DownloadError("unsupportedType", `File type '${detected.mime}' not allowed`, detected.mime);
|
|
88
128
|
}
|
|
89
129
|
return { mime: detected.mime, ext: detected.ext };
|
|
90
130
|
}
|
|
@@ -109,7 +149,7 @@ export async function validateFileType(buffer, allowedTypes = ALLOWED_MIME_TYPES
|
|
|
109
149
|
}
|
|
110
150
|
}
|
|
111
151
|
}
|
|
112
|
-
throw new
|
|
152
|
+
throw new DownloadError("undetectableType", "Could not determine file type or type not allowed");
|
|
113
153
|
}
|
|
114
154
|
/**
|
|
115
155
|
* Validate content ID is a positive integer.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-validator.js","sourceRoot":"","sources":["../../src/utils/file-validator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,gBAAgB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"file-validator.js","sourceRoot":"","sources":["../../src/utils/file-validator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,gBAAgB,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD;;;;;;;;;;;;GAYG;AACH,MAAM,QAAQ,GAAG,mBAAmB,CAAC;AACrC,MAAM,mBAAmB,GAA2B;IAClD,MAAM,EAAE,oBAAoB;IAC5B,MAAM,EAAE,oBAAoB;IAC5B,MAAM,EAAE,0BAA0B;IAClC,MAAM,EAAE,0BAA0B;IAClC,MAAM,EAAE,+BAA+B;IACvC,MAAM,EAAE,+BAA+B;IACvC,MAAM,EAAE,+BAA+B;CACxC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,QAAQ;AAEvD;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAa;IAC1C,YAAY;IACZ,iBAAiB;IACjB,yEAAyE,EAAE,QAAQ;IACnF,2EAA2E,EAAE,QAAQ;IACrF,mEAAmE,EAAE,QAAQ;IAC7E,oBAAoB,EAAE,OAAO;IAC7B,+BAA+B,EAAE,OAAO;IACxC,0BAA0B,EAAE,OAAO;IACnC,SAAS;IACT,WAAW;IACX,YAAY;IACZ,WAAW;IACX,YAAY;IACZ,eAAe;IACf,OAAO;IACP,YAAY;IACZ,UAAU;IACV,WAAW;IACX,OAAO;IACP,kBAAkB;IAClB,WAAW;IACX,iBAAiB;IACjB,8BAA8B;IAC9B,QAAQ;IACR,WAAW;IACX,YAAY;IACZ,WAAW;CACZ,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAe,EACf,QAAgB;IAEhB,8DAA8D;IAC9D,0EAA0E;IAC1E,gEAAgE;IAChE,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,QAAQ,CAAC;IACrB,CAAC;IAED,gEAAgE;IAChE,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAE5C,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,aAAa,CAAC,aAAa,EAAE,qCAAqC,CAAC,CAAC;IAChF,CAAC;IAED,oBAAoB;IACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAClD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAE3C,gDAAgD;IAChD,IACE,CAAC,QAAQ,CAAC,UAAU,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC;QAC7C,QAAQ,KAAK,YAAY,EACzB,CAAC;QACD,MAAM,IAAI,aAAa,CAAC,eAAe,EAAE,yBAAyB,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAAc,EACd,eAAyB,kBAAkB,EAC3C,QAAiB;IAEjB,iCAAiC;IACjC,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAElD,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,QAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC/B,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjE,MAAM,QAAQ,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;YAC1C,IAAI,QAAQ,IAAI,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAChD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/C,CAAC;YACD,MAAM,IAAI,aAAa,CACrB,iBAAiB,EACjB,wCAAwC,GAAG,IAAI,MAAM,mCAAmC,EACxF,QAAQ,CACT,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,aAAa,CACrB,iBAAiB,EACjB,cAAc,QAAQ,CAAC,IAAI,eAAe,EAC1C,QAAQ,CAAC,IAAI,CACd,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC;IACpD,CAAC;IAED,4EAA4E;IAC5E,+EAA+E;IAC/E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QACxB,IAAI,OAAO,GAAkB,IAAI,CAAC;QAClC,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACrE,CAAC;QAAC,MAAM,CAAC;YACP,mCAAmC;QACrC,CAAC;QAED,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,KAAK,GACT,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YAChE,MAAM,IAAI,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC,WAAW,EAAE,CAAC;YAC7C,MAAM,MAAM,GACV,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;YAChE,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC;YACjD,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;YAEpC,IAAI,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,IAAI,aAAa,CACrB,kBAAkB,EAClB,mDAAmD,CACpD,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAW;IAC3C,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjD,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW,EAAE,eAAuB;IAClE,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CACb,uBAAuB,eAAe,UAAU,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAC1E,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brightspace MCP Server. Copyright (c) 2026 Rohan Muppa. MIT licensed.
|
|
3
|
+
*
|
|
4
|
+
* Find every copy of this package on the machine and report disagreement.
|
|
5
|
+
*
|
|
6
|
+
* A single machine can easily hold several copies at different versions: one
|
|
7
|
+
* per Node version under a version manager, one per npx cache entry, plus a
|
|
8
|
+
* local checkout. They are installed at different times and nothing keeps them
|
|
9
|
+
* in step. The failure that motivated this had a v2.0.0 MCP server running
|
|
10
|
+
* happily while the auth command typed in a shell resolved to v1.2.6, whose
|
|
11
|
+
* sign-in flow no longer worked, with no way to see the mismatch.
|
|
12
|
+
*
|
|
13
|
+
* This scan needs no network, so unlike the registry check it still works
|
|
14
|
+
* offline, and it catches the problem before anything fails rather than after.
|
|
15
|
+
*
|
|
16
|
+
* It reads package.json files and resolves symlinks. It never spawns a
|
|
17
|
+
* process, never runs npm, and never writes anything.
|
|
18
|
+
*/
|
|
19
|
+
export interface InstallSite {
|
|
20
|
+
kind: "global" | "npx-cache" | "self";
|
|
21
|
+
/** The package directory, i.e. .../node_modules/brightspace-mcp-server */
|
|
22
|
+
dir: string;
|
|
23
|
+
version: string | null;
|
|
24
|
+
/** The PATH entry that resolves here, when a shell command points at it. */
|
|
25
|
+
binPath?: string;
|
|
26
|
+
isSelf: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface InstallScanDeps {
|
|
29
|
+
env?: NodeJS.ProcessEnv;
|
|
30
|
+
platform?: NodeJS.Platform;
|
|
31
|
+
execPath?: string;
|
|
32
|
+
home?: string;
|
|
33
|
+
/** The running package directory, excluded from "other copies". */
|
|
34
|
+
selfDir?: string;
|
|
35
|
+
readVersion?: (packageJsonPath: string) => Promise<string | null>;
|
|
36
|
+
listDir?: (dir: string) => Promise<string[]>;
|
|
37
|
+
realpath?: (path: string) => Promise<string>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Every copy of this package we can find, including the running one.
|
|
41
|
+
* Never throws; anything unreadable is simply omitted.
|
|
42
|
+
*/
|
|
43
|
+
export declare function scanInstallSites(deps?: InstallScanDeps): Promise<InstallSite[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Scan and format in one step. Returns null when there is nothing to say, and
|
|
46
|
+
* never throws, so callers can treat it as advisory.
|
|
47
|
+
*
|
|
48
|
+
* Honors D2L_NO_UPDATE_CHECK, which switches off every version-related notice.
|
|
49
|
+
*/
|
|
50
|
+
export declare function detectSkew(runningVersion: string, deps?: InstallScanDeps): Promise<string | null>;
|
|
51
|
+
/**
|
|
52
|
+
* A warning about copies that disagree with the running version, or null when
|
|
53
|
+
* there is nothing worth saying.
|
|
54
|
+
*
|
|
55
|
+
* Deliberately quiet: it only speaks when another copy exists at a *different*
|
|
56
|
+
* version, since a machine with several copies all in step is fine. Copies a
|
|
57
|
+
* shell command actually resolves to are reported first, because those are the
|
|
58
|
+
* ones a user can trip over.
|
|
59
|
+
*/
|
|
60
|
+
export declare function formatSkewNotice(sites: InstallSite[], runningVersion: string, home?: string): string | null;
|
|
61
|
+
//# sourceMappingURL=install-sites.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-sites.d.ts","sourceRoot":"","sources":["../../src/utils/install-sites.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAaH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,GAAG,WAAW,GAAG,MAAM,CAAC;IACtC,0EAA0E;IAC1E,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAClE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC9C;AAgFD;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAsEzF;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,cAAc,EAAE,MAAM,EACtB,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CASxB;AAMD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,EAAE,EACpB,cAAc,EAAE,MAAM,EACtB,IAAI,GAAE,MAAkB,GACvB,MAAM,GAAG,IAAI,CA4Bf"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brightspace MCP Server. Copyright (c) 2026 Rohan Muppa. MIT licensed.
|
|
3
|
+
*
|
|
4
|
+
* Find every copy of this package on the machine and report disagreement.
|
|
5
|
+
*
|
|
6
|
+
* A single machine can easily hold several copies at different versions: one
|
|
7
|
+
* per Node version under a version manager, one per npx cache entry, plus a
|
|
8
|
+
* local checkout. They are installed at different times and nothing keeps them
|
|
9
|
+
* in step. The failure that motivated this had a v2.0.0 MCP server running
|
|
10
|
+
* happily while the auth command typed in a shell resolved to v1.2.6, whose
|
|
11
|
+
* sign-in flow no longer worked, with no way to see the mismatch.
|
|
12
|
+
*
|
|
13
|
+
* This scan needs no network, so unlike the registry check it still works
|
|
14
|
+
* offline, and it catches the problem before anything fails rather than after.
|
|
15
|
+
*
|
|
16
|
+
* It reads package.json files and resolves symlinks. It never spawns a
|
|
17
|
+
* process, never runs npm, and never writes anything.
|
|
18
|
+
*/
|
|
19
|
+
import { readFile, readdir, realpath as fsRealpath } from "node:fs/promises";
|
|
20
|
+
import { homedir } from "node:os";
|
|
21
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
22
|
+
import { PACKAGE_NAME, GLOBAL_INSTALL_COMMAND, AUTH_COMMAND } from "./commands.js";
|
|
23
|
+
/** Whole-scan budget. A stalled network mount on PATH must not hang startup. */
|
|
24
|
+
const SCAN_BUDGET_MS = 2000;
|
|
25
|
+
/** Most sites named in one notice before it turns into noise. */
|
|
26
|
+
const MAX_REPORTED = 3;
|
|
27
|
+
async function defaultReadVersion(packageJsonPath) {
|
|
28
|
+
try {
|
|
29
|
+
const raw = await readFile(packageJsonPath, "utf-8");
|
|
30
|
+
const version = JSON.parse(raw).version;
|
|
31
|
+
return typeof version === "string" ? version : null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function defaultListDir(dir) {
|
|
38
|
+
try {
|
|
39
|
+
return await readdir(dir);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Global `node_modules` roots to probe.
|
|
47
|
+
*
|
|
48
|
+
* A plain PATH scan is not enough. A GUI-launched MCP client inherits a
|
|
49
|
+
* minimal launchd PATH with no version-manager directory in it, so the stale
|
|
50
|
+
* copy would be invisible. Version-manager roots are therefore enumerated
|
|
51
|
+
* directly, which is what surfaces a copy sitting under a different Node
|
|
52
|
+
* version than the one currently running.
|
|
53
|
+
*/
|
|
54
|
+
async function candidateGlobalRoots(env, platform, execPath, home, listDir) {
|
|
55
|
+
const roots = new Set();
|
|
56
|
+
const isWin = platform === "win32";
|
|
57
|
+
// npm's own global-prefix rule, relative to the running node binary.
|
|
58
|
+
roots.add(isWin
|
|
59
|
+
? join(dirname(execPath), "node_modules")
|
|
60
|
+
: join(dirname(dirname(execPath)), "lib", "node_modules"));
|
|
61
|
+
// Anything on PATH that looks like a bin directory.
|
|
62
|
+
for (const entry of (env.PATH ?? "").split(isWin ? ";" : ":")) {
|
|
63
|
+
if (!entry)
|
|
64
|
+
continue;
|
|
65
|
+
if (isWin)
|
|
66
|
+
roots.add(join(entry, "node_modules"));
|
|
67
|
+
else if (entry.endsWith(`${sep}bin`))
|
|
68
|
+
roots.add(join(dirname(entry), "lib", "node_modules"));
|
|
69
|
+
}
|
|
70
|
+
if (env.npm_config_prefix) {
|
|
71
|
+
roots.add(isWin
|
|
72
|
+
? join(env.npm_config_prefix, "node_modules")
|
|
73
|
+
: join(env.npm_config_prefix, "lib", "node_modules"));
|
|
74
|
+
}
|
|
75
|
+
if (isWin) {
|
|
76
|
+
if (env.APPDATA)
|
|
77
|
+
roots.add(join(env.APPDATA, "npm", "node_modules"));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
roots.add("/usr/local/lib/node_modules");
|
|
81
|
+
roots.add("/opt/homebrew/lib/node_modules");
|
|
82
|
+
}
|
|
83
|
+
// Version managers, one listing each.
|
|
84
|
+
const managers = [
|
|
85
|
+
[join(home, ".nvm", "versions", "node"), ["lib", "node_modules"]],
|
|
86
|
+
[join(home, ".volta", "tools", "image", "node"), ["lib", "node_modules"]],
|
|
87
|
+
[join(home, ".local", "share", "fnm", "node-versions"), ["installation", "lib", "node_modules"]],
|
|
88
|
+
[join(home, ".asdf", "installs", "nodejs"), ["lib", "node_modules"]],
|
|
89
|
+
];
|
|
90
|
+
for (const [base, tail] of managers) {
|
|
91
|
+
for (const version of await listDir(base)) {
|
|
92
|
+
roots.add(join(base, version, ...tail));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return [...roots];
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Every copy of this package we can find, including the running one.
|
|
99
|
+
* Never throws; anything unreadable is simply omitted.
|
|
100
|
+
*/
|
|
101
|
+
export async function scanInstallSites(deps = {}) {
|
|
102
|
+
const { env = process.env, platform = process.platform, execPath = process.execPath, home = homedir(), selfDir, readVersion = defaultReadVersion, listDir = defaultListDir, realpath = fsRealpath, } = deps;
|
|
103
|
+
const found = new Map();
|
|
104
|
+
const record = async (dir, kind, binPath) => {
|
|
105
|
+
const key = resolve(dir);
|
|
106
|
+
const version = await readVersion(join(key, "package.json"));
|
|
107
|
+
if (version === null && !found.has(key))
|
|
108
|
+
return; // nothing actually installed here
|
|
109
|
+
const isSelf = selfDir !== undefined && resolve(selfDir) === key;
|
|
110
|
+
const existing = found.get(key);
|
|
111
|
+
found.set(key, {
|
|
112
|
+
kind: isSelf ? "self" : kind,
|
|
113
|
+
dir: key,
|
|
114
|
+
version: version ?? existing?.version ?? null,
|
|
115
|
+
binPath: binPath ?? existing?.binPath,
|
|
116
|
+
isSelf,
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
const work = (async () => {
|
|
120
|
+
const roots = await candidateGlobalRoots(env, platform, execPath, home, listDir);
|
|
121
|
+
await Promise.allSettled(roots.map((root) => record(join(root, PACKAGE_NAME), "global")));
|
|
122
|
+
// Which copy does the command a user actually types resolve to?
|
|
123
|
+
const isWin = platform === "win32";
|
|
124
|
+
if (!isWin) {
|
|
125
|
+
const shims = ["brightspace-auth", PACKAGE_NAME];
|
|
126
|
+
await Promise.allSettled((env.PATH ?? "").split(":").filter(Boolean).flatMap((entry) => shims.map(async (shim) => {
|
|
127
|
+
const binPath = join(entry, shim);
|
|
128
|
+
try {
|
|
129
|
+
const target = await realpath(binPath);
|
|
130
|
+
const marker = `${sep}node_modules${sep}${PACKAGE_NAME}${sep}`;
|
|
131
|
+
const idx = target.indexOf(marker);
|
|
132
|
+
if (idx === -1)
|
|
133
|
+
return;
|
|
134
|
+
await record(target.slice(0, idx + marker.length - 1), "global", binPath);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// No such shim here.
|
|
138
|
+
}
|
|
139
|
+
})));
|
|
140
|
+
}
|
|
141
|
+
// npx cache entries.
|
|
142
|
+
const npxRoot = join(home, ".npm", "_npx");
|
|
143
|
+
await Promise.allSettled((await listDir(npxRoot)).map((entry) => record(join(npxRoot, entry, "node_modules", PACKAGE_NAME), "npx-cache")));
|
|
144
|
+
})();
|
|
145
|
+
await Promise.race([
|
|
146
|
+
work,
|
|
147
|
+
new Promise((r) => setTimeout(r, SCAN_BUDGET_MS).unref?.()),
|
|
148
|
+
]);
|
|
149
|
+
return [...found.values()];
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Scan and format in one step. Returns null when there is nothing to say, and
|
|
153
|
+
* never throws, so callers can treat it as advisory.
|
|
154
|
+
*
|
|
155
|
+
* Honors D2L_NO_UPDATE_CHECK, which switches off every version-related notice.
|
|
156
|
+
*/
|
|
157
|
+
export async function detectSkew(runningVersion, deps = {}) {
|
|
158
|
+
const env = deps.env ?? process.env;
|
|
159
|
+
if (env.D2L_NO_UPDATE_CHECK)
|
|
160
|
+
return null;
|
|
161
|
+
try {
|
|
162
|
+
const sites = await scanInstallSites(deps);
|
|
163
|
+
return formatSkewNotice(sites, runningVersion, deps.home ?? homedir());
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function shorten(path, home) {
|
|
170
|
+
return path.startsWith(home) ? `~${path.slice(home.length)}` : path;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* A warning about copies that disagree with the running version, or null when
|
|
174
|
+
* there is nothing worth saying.
|
|
175
|
+
*
|
|
176
|
+
* Deliberately quiet: it only speaks when another copy exists at a *different*
|
|
177
|
+
* version, since a machine with several copies all in step is fine. Copies a
|
|
178
|
+
* shell command actually resolves to are reported first, because those are the
|
|
179
|
+
* ones a user can trip over.
|
|
180
|
+
*/
|
|
181
|
+
export function formatSkewNotice(sites, runningVersion, home = homedir()) {
|
|
182
|
+
const mismatched = sites
|
|
183
|
+
.filter((s) => !s.isSelf && s.version !== null && s.version !== runningVersion)
|
|
184
|
+
.sort((a, b) => Number(Boolean(b.binPath)) - Number(Boolean(a.binPath)));
|
|
185
|
+
if (mismatched.length === 0)
|
|
186
|
+
return null;
|
|
187
|
+
const shown = mismatched.slice(0, MAX_REPORTED);
|
|
188
|
+
const lines = shown.map((s) => {
|
|
189
|
+
const where = s.binPath
|
|
190
|
+
? `\`${shorten(s.binPath, home)}\` resolves to v${s.version}`
|
|
191
|
+
: `v${s.version} at ${shorten(s.dir, home)}`;
|
|
192
|
+
return ` - ${where}`;
|
|
193
|
+
});
|
|
194
|
+
const extra = mismatched.length - shown.length;
|
|
195
|
+
if (extra > 0)
|
|
196
|
+
lines.push(` - and ${extra} more`);
|
|
197
|
+
const resolvable = shown.some((s) => s.binPath);
|
|
198
|
+
const advice = resolvable
|
|
199
|
+
? `An out-of-date auth CLI fails during sign-in. Run \`${GLOBAL_INSTALL_COMMAND}\`, or use \`${AUTH_COMMAND}\`.`
|
|
200
|
+
: `Run \`${GLOBAL_INSTALL_COMMAND}\` to bring them in line.`;
|
|
201
|
+
return [
|
|
202
|
+
`Version mismatch. This process is v${runningVersion}, but other copies of ${PACKAGE_NAME} are installed:`,
|
|
203
|
+
...lines,
|
|
204
|
+
advice,
|
|
205
|
+
].join("\n");
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=install-sites.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-sites.js","sourceRoot":"","sources":["../../src/utils/install-sites.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brightspace MCP Server. Copyright (c) 2026 Rohan Muppa. MIT licensed.
|
|
3
|
+
*
|
|
4
|
+
* Self-healing for the auth CLI.
|
|
5
|
+
*
|
|
6
|
+
* The MCP server is launched as `npx -y brightspace-mcp-server@latest`, so it
|
|
7
|
+
* refetches on every client restart and is effectively always current. The
|
|
8
|
+
* auth CLI has no such guarantee: a copy installed once with `npm install -g`
|
|
9
|
+
* stays at that version forever. That is how a machine ended up running a
|
|
10
|
+
* v2.0.0 server alongside a v1.2.6 auth CLI whose sign-in flow no longer
|
|
11
|
+
* worked, with nothing to say so.
|
|
12
|
+
*
|
|
13
|
+
* When a stale CLI notices it is behind, it re-runs itself through
|
|
14
|
+
* `npx -y brightspace-mcp-server@latest auth` and hands back that process's
|
|
15
|
+
* exit code. The user types nothing and is not asked anything; the newest code
|
|
16
|
+
* simply runs. Note this does not rewrite the old copy on disk -- it is
|
|
17
|
+
* bypassed, not upgraded. Overwriting it would mean running `npm install -g`
|
|
18
|
+
* from inside a live process, which is the thing this package deliberately
|
|
19
|
+
* does not do.
|
|
20
|
+
*
|
|
21
|
+
* Opt out with D2L_NO_UPDATE_CHECK.
|
|
22
|
+
*/
|
|
23
|
+
import { type SpawnOptions, type ChildProcess } from "node:child_process";
|
|
24
|
+
/** Set in the child's environment so a re-exec can never recurse. */
|
|
25
|
+
export declare const REEXEC_SENTINEL = "D2L_REEXECED";
|
|
26
|
+
export interface ReexecDeps {
|
|
27
|
+
env?: NodeJS.ProcessEnv;
|
|
28
|
+
argv?: string[];
|
|
29
|
+
installedVersion?: string;
|
|
30
|
+
fetchImpl?: typeof fetch;
|
|
31
|
+
/** npx already resolved @latest, so a re-exec would be pointless. */
|
|
32
|
+
runningFromNpxCache?: boolean;
|
|
33
|
+
platform?: NodeJS.Platform;
|
|
34
|
+
spawnImpl?: (command: string, args: string[], options: SpawnOptions) => ChildProcess;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Flags worth carrying into the re-executed process. Only pass through
|
|
38
|
+
* option flags: the subcommand is supplied explicitly, and forwarding stray
|
|
39
|
+
* positional arguments risks changing what the child actually does.
|
|
40
|
+
*/
|
|
41
|
+
export declare function passthroughFlags(argv: string[]): string[];
|
|
42
|
+
/**
|
|
43
|
+
* Decide, without doing anything, whether a re-exec is warranted.
|
|
44
|
+
* Split out from the spawn so the policy is testable on its own.
|
|
45
|
+
*/
|
|
46
|
+
export declare function shouldReexec(opts: {
|
|
47
|
+
env: NodeJS.ProcessEnv;
|
|
48
|
+
latest: string | null;
|
|
49
|
+
installedVersion: string;
|
|
50
|
+
runningFromNpxCache: boolean;
|
|
51
|
+
}): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Re-run this command through the latest published release when the running
|
|
54
|
+
* copy is stale.
|
|
55
|
+
*
|
|
56
|
+
* Resolves to the child's exit code when a re-exec happened (the caller should
|
|
57
|
+
* exit with it and do no further work), or null when the current process
|
|
58
|
+
* should simply continue.
|
|
59
|
+
*/
|
|
60
|
+
export declare function reexecLatestIfStale(deps?: ReexecDeps): Promise<number | null>;
|
|
61
|
+
//# sourceMappingURL=self-update.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-update.d.ts","sourceRoot":"","sources":["../../src/utils/self-update.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAS,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIjF,qEAAqE;AACrE,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAE9C,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,qEAAqE;IACrE,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,SAAS,CAAC,EAAE,CACV,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,YAAY,KAClB,YAAY,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAEzD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE;IACjC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,OAAO,CAAC;CAC9B,GAAG,OAAO,CAOV;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,GAAE,UAAe,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA4CvF"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brightspace MCP Server. Copyright (c) 2026 Rohan Muppa. MIT licensed.
|
|
3
|
+
*
|
|
4
|
+
* Self-healing for the auth CLI.
|
|
5
|
+
*
|
|
6
|
+
* The MCP server is launched as `npx -y brightspace-mcp-server@latest`, so it
|
|
7
|
+
* refetches on every client restart and is effectively always current. The
|
|
8
|
+
* auth CLI has no such guarantee: a copy installed once with `npm install -g`
|
|
9
|
+
* stays at that version forever. That is how a machine ended up running a
|
|
10
|
+
* v2.0.0 server alongside a v1.2.6 auth CLI whose sign-in flow no longer
|
|
11
|
+
* worked, with nothing to say so.
|
|
12
|
+
*
|
|
13
|
+
* When a stale CLI notices it is behind, it re-runs itself through
|
|
14
|
+
* `npx -y brightspace-mcp-server@latest auth` and hands back that process's
|
|
15
|
+
* exit code. The user types nothing and is not asked anything; the newest code
|
|
16
|
+
* simply runs. Note this does not rewrite the old copy on disk -- it is
|
|
17
|
+
* bypassed, not upgraded. Overwriting it would mean running `npm install -g`
|
|
18
|
+
* from inside a live process, which is the thing this package deliberately
|
|
19
|
+
* does not do.
|
|
20
|
+
*
|
|
21
|
+
* Opt out with D2L_NO_UPDATE_CHECK.
|
|
22
|
+
*/
|
|
23
|
+
import { spawn } from "node:child_process";
|
|
24
|
+
import { PACKAGE_NAME } from "./commands.js";
|
|
25
|
+
import { fetchLatestVersion, isNewerVersion, ownNpxCacheDir } from "./update-checker.js";
|
|
26
|
+
/** Set in the child's environment so a re-exec can never recurse. */
|
|
27
|
+
export const REEXEC_SENTINEL = "D2L_REEXECED";
|
|
28
|
+
/**
|
|
29
|
+
* Flags worth carrying into the re-executed process. Only pass through
|
|
30
|
+
* option flags: the subcommand is supplied explicitly, and forwarding stray
|
|
31
|
+
* positional arguments risks changing what the child actually does.
|
|
32
|
+
*/
|
|
33
|
+
export function passthroughFlags(argv) {
|
|
34
|
+
return argv.slice(2).filter((arg) => arg.startsWith("-"));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Decide, without doing anything, whether a re-exec is warranted.
|
|
38
|
+
* Split out from the spawn so the policy is testable on its own.
|
|
39
|
+
*/
|
|
40
|
+
export function shouldReexec(opts) {
|
|
41
|
+
const { env, latest, installedVersion, runningFromNpxCache } = opts;
|
|
42
|
+
if (env[REEXEC_SENTINEL])
|
|
43
|
+
return false; // we are already the child
|
|
44
|
+
if (env.D2L_NO_UPDATE_CHECK)
|
|
45
|
+
return false;
|
|
46
|
+
if (runningFromNpxCache)
|
|
47
|
+
return false; // npx just fetched this
|
|
48
|
+
if (latest === null)
|
|
49
|
+
return false; // registry unreachable, carry on offline
|
|
50
|
+
return isNewerVersion(latest, installedVersion);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Re-run this command through the latest published release when the running
|
|
54
|
+
* copy is stale.
|
|
55
|
+
*
|
|
56
|
+
* Resolves to the child's exit code when a re-exec happened (the caller should
|
|
57
|
+
* exit with it and do no further work), or null when the current process
|
|
58
|
+
* should simply continue.
|
|
59
|
+
*/
|
|
60
|
+
export async function reexecLatestIfStale(deps = {}) {
|
|
61
|
+
const { env = process.env, argv = process.argv, installedVersion, fetchImpl = fetch, runningFromNpxCache = ownNpxCacheDir() !== null, platform = process.platform, spawnImpl = spawn, } = deps;
|
|
62
|
+
if (installedVersion === undefined)
|
|
63
|
+
return null;
|
|
64
|
+
// Cheap local checks first, so an opted-out or npx-launched run never even
|
|
65
|
+
// touches the network.
|
|
66
|
+
if (env[REEXEC_SENTINEL] || env.D2L_NO_UPDATE_CHECK || runningFromNpxCache)
|
|
67
|
+
return null;
|
|
68
|
+
const latest = await fetchLatestVersion(fetchImpl);
|
|
69
|
+
if (!shouldReexec({ env, latest, installedVersion, runningFromNpxCache }))
|
|
70
|
+
return null;
|
|
71
|
+
const args = ["-y", `${PACKAGE_NAME}@latest`, "auth", ...passthroughFlags(argv)];
|
|
72
|
+
console.error(`\nThis copy is v${installedVersion}; v${latest} is available. ` +
|
|
73
|
+
`Running the newer version instead.\n`);
|
|
74
|
+
return await new Promise((resolve) => {
|
|
75
|
+
let child;
|
|
76
|
+
try {
|
|
77
|
+
child = spawnImpl(platform === "win32" ? "npx.cmd" : "npx", args, {
|
|
78
|
+
stdio: "inherit",
|
|
79
|
+
env: { ...env, [REEXEC_SENTINEL]: "1" },
|
|
80
|
+
shell: platform === "win32",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
resolve(null); // could not launch npx, fall back to running ourselves
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// If npx itself cannot start, run the stale copy rather than failing.
|
|
88
|
+
child.on("error", () => resolve(null));
|
|
89
|
+
child.on("exit", (code) => resolve(code ?? 1));
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=self-update.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-update.js","sourceRoot":"","sources":["../../src/utils/self-update.ts"],"names":[],"mappings":"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"}
|