-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathpreinstall.js
More file actions
132 lines (120 loc) · 5.7 KB
/
Copy pathpreinstall.js
File metadata and controls
132 lines (120 loc) · 5.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/**
* Install-time preflight. // implements XSPEC-365 R1
*
* Runs before any native dependency is compiled and says, in advance, what
* this machine is about to be asked to build and what happens if it can't.
* The ordering is not incidental — it is the whole point, and it was measured
* rather than assumed: a package's own `preinstall` runs before its
* dependencies' `install` scripts (where `node-gyp` lives), both when the
* package is the install target and when it is a dependency of someone else's
* project. See XSPEC-365 appendix A.
*
* **What it checks, and what it deliberately does not.** It compares this
* platform against the prebuilt-binary coverage declared in
* `language-support.js`. It does *not* try to detect whether a C/C++ toolchain
* is present. That was a considered choice: toolchain detection is exactly
* what fails confusingly here. `node-gyp` itself hard-codes support for Visual
* Studio 2017/2019/2022 and reports a machine with a complete VS 2026 MSVC
* install as `unknown version "undefined"`; and the `VCTools` workload lists
* the actual compiler as Recommended rather than Required, so a machine can
* report a C++ workload while having no compiler. A second, worse detector in
* an install script would add noise, not signal. "Does this platform have a
* prebuilt binary" is deterministic, needs nothing installed to answer, and is
* the same question `node-gyp-build` will ask a moment later.
*
* **It never fails the install.** Every path exits 0, including the failure
* paths of this script itself. Blocking installation would leave the user with
* no `egr` at all, when the actual consequence is losing one language out of
* thirteen.
*
* **This is best-effort, and is NOT how the information reaches users.**
* Measured on 2026-08-04 with the command people actually type
* (`npm install -g engramgraph`, no `--foreground-scripts`): this notice
* appeared zero times in 242 lines of output. npm suppresses lifecycle-script
* output by default, and npm >= 11 additionally holds those scripts behind an
* approval gate, so on a fresh machine the script may not run at all. Every
* test of this file had passed, because every test invoked it the way it was
* written rather than the way users install — the same mistake, in miniature,
* that this whole release is about.
*
* It is kept because it costs nothing and does surface where script output is
* shown (CI logs, `--foreground-scripts`, `npm_config_foreground_scripts`),
* and it is the only thing that can speak *before* a compile. But the channel
* users actually see is `egr doctor` and the skipped-language line at index
* time — a command someone types cannot be suppressed by a package manager.
*/
import { compiledFromSourceOn, currentPlatform } from "./language-support.js";
const DOCS =
"https://github.com/AsiaOstrich/EngramGraph#native-dependencies-and-platform-support";
/**
* The preflight message for `platform`, or `null` when nothing there needs
* compiling.
*
* Exported and pure so `test/preinstall.test.ts` can assert what each platform
* is told without running an install. An install-time message that is only
* ever exercised during an install is a message nobody checks: it renders on
* exactly the machines where something is going wrong, which are the machines
* whose output nobody is reading.
*
* @param {string} [platform] Defaults to the running platform.
* @returns {string | null}
*/
export function preflightMessage(platform = currentPlatform()) {
const fromSource = compiledFromSourceOn(platform);
if (fromSource.length === 0) return null; // fully prebuilt here; say nothing
const lines = [
"",
`engramgraph: ${fromSource.length === 1 ? "one native dependency has" : `${fromSource.length} native dependencies have`} no prebuilt binary for ${platform},`,
"so npm is about to compile from source:",
"",
];
for (const dep of fromSource) {
const what =
dep.kind === "grammar"
? `provides ${dep.languages.join(", ")} support`
: dep.role;
lines.push(` ${dep.package} — ${what}`);
}
const grammarsOnly = fromSource.every((d) => d.kind === "grammar");
const languages = fromSource.flatMap((d) => d.languages);
lines.push(
"",
"Compiling needs a C/C++ toolchain and Python on this machine.",
);
if (grammarsOnly) {
lines.push(
"",
"If they are missing the build will fail, and that is expected, not fatal:",
`these grammars are optional dependencies, so installation continues and`,
`every other language still works. Only ${languages.join(", ")} indexing`,
"would be unavailable, and egr will say so when you index.",
);
} else {
// A non-grammar native dependency (the graph database) is not optional —
// be honest that this one is load-bearing rather than implying the same
// graceful degradation.
lines.push(
"",
"At least one of these is required, not optional — if its build fails,",
"the installation will not produce a working egr.",
);
}
lines.push("", `Platform notes and setup steps: ${DOCS}`, "");
return lines.join("\n");
}
/**
* Only speak when run as the install hook, so importing this module from a
* test doesn't print to the terminal.
*/
const RUN_AS_HOOK = process.argv[1]?.endsWith("preinstall.js") ?? false;
if (RUN_AS_HOOK) {
try {
const message = preflightMessage();
if (message !== null) console.error(message);
} catch (err) {
// A preflight that breaks the install is worse than no preflight. Report
// and get out of the way — npm proceeds exactly as it would have.
const reason = err instanceof Error ? err.message : String(err);
console.error(`engramgraph: install preflight skipped (${reason})`);
}
}