@amritk/nish 0.10.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/LICENSE +21 -0
- package/README.md +553 -0
- package/bin/launcher.js +186 -0
- package/bin/nish +23 -0
- package/bin/packaging.js +220 -0
- package/docs/AI.md +1076 -0
- package/docs/INSTALL.md +468 -0
- package/llms.txt +49 -0
- package/package.json +87 -0
- package/runtime/nish.d.ts +290 -0
- package/runtime/nish.h +340 -0
- package/runtime/nish.mjs +143 -0
- package/runtime/runtime.c +1184 -0
- package/runtime/runtime_os.c +351 -0
- package/runtime/runtime_parallel.c +156 -0
- package/runtime/runtime_wasm.c +99 -0
- package/runtime/shim.mjs +672 -0
- package/scripts/bootstrap.sh +357 -0
- package/scripts/build.sh +279 -0
- package/scripts/changelog-gen.mjs +528 -0
- package/scripts/changelog-section.sh +28 -0
- package/scripts/ci-profile.mjs +187 -0
- package/scripts/codes-registry.js +73 -0
- package/scripts/gen-diagnostic-codes.mjs +199 -0
- package/scripts/gen-pow5-tables.py +45 -0
- package/scripts/nish-compiler.sh +17 -0
- package/scripts/platform-package.mjs +91 -0
- package/scripts/postinstall.mjs +133 -0
- package/scripts/size-report.sh +72 -0
- package/scripts/smoke.sh +94 -0
- package/scripts/verify-binaries.sh +213 -0
- package/std/README.md +185 -0
- package/std/json.ts +402 -0
- package/std/pair.ts +28 -0
- package/std/testing.ts +347 -0
- package/std/text.ts +193 -0
package/bin/launcher.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `nish` command: run the native compiler this machine installed, or say
|
|
3
|
+
* why there is none and stop.
|
|
4
|
+
*
|
|
5
|
+
* This is what `bin.nish` points at, and it exists because npm cannot point a
|
|
6
|
+
* `bin` entry at a dependency that may or may not be installed. The native
|
|
7
|
+
* compiler ships as `@amritk/nish-<asset>` packages declared as
|
|
8
|
+
* `optionalDependencies` with `os` and `cpu` set, so npm installs exactly the
|
|
9
|
+
* one that matches and silently skips the rest (docs/wp12-release.md,
|
|
10
|
+
* "Which compiler the package ships"). Something still has to look at what
|
|
11
|
+
* landed and hand over to it, and that is this file.
|
|
12
|
+
*
|
|
13
|
+
* **Nothing is ever compiled on a user's machine.** The binary is built,
|
|
14
|
+
* `--verify`d and smoke-tested per platform by `release.yml` before the
|
|
15
|
+
* release that carries it exists; installing is a download and an unpack.
|
|
16
|
+
*
|
|
17
|
+
* **A platform with no prebuilt binary is now an error rather than a fallback,
|
|
18
|
+
* and that is a decision rather than an oversight.** Until 0.6.0 this file
|
|
19
|
+
* imported `dist/index.js` -- the TypeScript compiler built from `src/` -- so
|
|
20
|
+
* musl, FreeBSD and 32-bit anything got a working compiler that happened to be
|
|
21
|
+
* slower. `src/` was deleted in R6 (`docs/wp19-stage0-retirement.md`), so there
|
|
22
|
+
* is no second compiler in the package to reach for, and the honest answer is
|
|
23
|
+
* the one below: name the platforms a release carries, say there is nothing to
|
|
24
|
+
* fall back to, and exit non-zero. The alternative -- shipping `self/` and
|
|
25
|
+
* bootstrapping on the user's machine -- was priced in wp12 and turned down,
|
|
26
|
+
* and quietly doing nothing was never on the table: a command that exits 0
|
|
27
|
+
* having compiled nothing is worse than one that refuses.
|
|
28
|
+
*
|
|
29
|
+
* `bin/` rather than the old `dist/` for the same reason: the command may not
|
|
30
|
+
* be a build artifact of the compiler it installs.
|
|
31
|
+
*/
|
|
32
|
+
import { spawnSync } from "node:child_process";
|
|
33
|
+
import fs from "node:fs";
|
|
34
|
+
import path from "node:path";
|
|
35
|
+
import { createRequire } from "node:module";
|
|
36
|
+
import { assetFor, noCompilerMessage, platformPackageName } from "./packaging.js";
|
|
37
|
+
|
|
38
|
+
/** Package root: bin/launcher.js -> `..`, the directory `package.json` sits in. */
|
|
39
|
+
const PKG_ROOT = path.resolve(import.meta.dirname, "..");
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Exit 3, the toolchain code.
|
|
43
|
+
*
|
|
44
|
+
* **It is deliberately the same 3 a missing `clang` answers, and that is worth
|
|
45
|
+
* defending because a wrapper cannot tell the two apart by status alone.** The
|
|
46
|
+
* table in docs/wp12-release.md's "Exit codes and failure modes" reads
|
|
47
|
+
* "toolchain: the C toolchain, or the prebuilt compiler itself, could not be
|
|
48
|
+
* run", which is one band for one shape of failure: nothing is wrong with the
|
|
49
|
+
* program, there is no source position, and what could not be run is a
|
|
50
|
+
* *program* rather than the input. A fourth code would put the launcher in the
|
|
51
|
+
* exit-status contract that `tests/nish/cli.ts` pins for the compiler, for a
|
|
52
|
+
* situation the compiler cannot be in. What tells them apart is the `code`
|
|
53
|
+
* field under `--json` and the first line otherwise, which is why the refusal
|
|
54
|
+
* below is machine-readable. `scripts/postinstall.mjs`'s generated shim answers
|
|
55
|
+
* 3 for its own version of this, so all three spell one situation the same way.
|
|
56
|
+
*/
|
|
57
|
+
const NO_COMPILER = 3;
|
|
58
|
+
|
|
59
|
+
/** This package's own name, or `null` when its `package.json` cannot be read. */
|
|
60
|
+
const packageName = () => {
|
|
61
|
+
try {
|
|
62
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf8"));
|
|
63
|
+
return typeof pkg.name === "string" ? pkg.name : null;
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Whether this invocation asked for machine-readable output.
|
|
71
|
+
*
|
|
72
|
+
* Read off the raw argv rather than parsed, because the launcher does not parse
|
|
73
|
+
* the command line and must not start: every other flag is the compiler's
|
|
74
|
+
* business. `--json` is the one thing it has to notice, since a refusal is a
|
|
75
|
+
* failure the `--json` contract covers (AGENTS.md, "Machine-readable
|
|
76
|
+
* surfaces").
|
|
77
|
+
*/
|
|
78
|
+
const wantsJson = () => process.argv.slice(2).includes("--json");
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Write and exit, without losing what was written.
|
|
82
|
+
*
|
|
83
|
+
* `process.exit` does not wait for a pending write, and stdout/stderr are
|
|
84
|
+
* asynchronous when they are pipes -- which is what they are whenever anything
|
|
85
|
+
* captures this command's output, and the case where the message matters most.
|
|
86
|
+
* `process.exitCode` plus a natural exit flushes, so that is what this does;
|
|
87
|
+
* there is nothing after it to run.
|
|
88
|
+
*/
|
|
89
|
+
const writeAndExit = (stream, text, status) => {
|
|
90
|
+
stream.write(text);
|
|
91
|
+
process.exitCode = status;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Where the native compiler for this machine is, or `null` when this machine
|
|
96
|
+
* has none installed.
|
|
97
|
+
*
|
|
98
|
+
* Resolution goes through the platform package's `package.json` rather than
|
|
99
|
+
* its binary, because a package may expose its own files however it likes but
|
|
100
|
+
* `./package.json` is the one export every package in this project declares.
|
|
101
|
+
* From there the layout is the release tarball's, unchanged: `bin/nish` beside
|
|
102
|
+
* `runtime/`, `scripts/` and `std/`, which is what lets the binary find
|
|
103
|
+
* `build.sh`, the C runtime and the standard library one level up from itself
|
|
104
|
+
* exactly as it does when unpacked by hand.
|
|
105
|
+
*/
|
|
106
|
+
const nativeCompiler = (asset) => {
|
|
107
|
+
const name = packageName();
|
|
108
|
+
if (name === null) return null;
|
|
109
|
+
try {
|
|
110
|
+
const manifest = createRequire(import.meta.url).resolve(`${platformPackageName(name, asset)}/package.json`);
|
|
111
|
+
const binary = path.join(path.dirname(manifest), "bin", "nish");
|
|
112
|
+
return fs.existsSync(binary) ? binary : null;
|
|
113
|
+
} catch {
|
|
114
|
+
// Not installed. On a supported platform that is a partial install; on an
|
|
115
|
+
// unsupported one npm skipped the entry on purpose. `refuse` tells the two
|
|
116
|
+
// apart, because the advice differs.
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Say why there is no compiler to run, and stop.
|
|
123
|
+
*
|
|
124
|
+
* Under `--json` that is one object on stdout and **nothing on stderr**, which
|
|
125
|
+
* is the shape `self/compile.ts`'s own `reportToolchainFailure` uses for the
|
|
126
|
+
* same code: stdout carries objects and nothing else, so a tool reading it does
|
|
127
|
+
* not have to strip a human report out of the stream. Otherwise it is the
|
|
128
|
+
* report on stderr. Either way the status is the same.
|
|
129
|
+
*/
|
|
130
|
+
const refuse = (unstartable) => {
|
|
131
|
+
const why = noCompilerMessage({
|
|
132
|
+
platform: process.platform,
|
|
133
|
+
arch: process.arch,
|
|
134
|
+
// The name comes from this package's own `package.json` or not at all. A
|
|
135
|
+
// literal here would be the scope spelled a second time, against
|
|
136
|
+
// packaging.js's own rule, and a *wrong* package name is worse advice than
|
|
137
|
+
// none: it sends the user to install something that does not exist.
|
|
138
|
+
packageName: packageName(),
|
|
139
|
+
unstartable,
|
|
140
|
+
});
|
|
141
|
+
if (wantsJson()) {
|
|
142
|
+
writeAndExit(
|
|
143
|
+
process.stdout,
|
|
144
|
+
`${JSON.stringify({ severity: "error", code: why.code, message: why.summary })}\n`,
|
|
145
|
+
NO_COMPILER
|
|
146
|
+
);
|
|
147
|
+
} else {
|
|
148
|
+
writeAndExit(process.stderr, why.report, NO_COMPILER);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const asset = assetFor(process.platform, process.arch);
|
|
153
|
+
// No binary for this machine, and none coming: musl, FreeBSD, 32-bit anything.
|
|
154
|
+
// `refuse` sets an exit code rather than exiting, so each of these returns
|
|
155
|
+
// before the next line runs: the sequence below is written as a chain for that
|
|
156
|
+
// reason and `process.exitCode` is what carries the status out.
|
|
157
|
+
if (asset === null) {
|
|
158
|
+
refuse(null);
|
|
159
|
+
} else {
|
|
160
|
+
const binary = nativeCompiler(asset);
|
|
161
|
+
if (binary === null) {
|
|
162
|
+
// A binary exists for this platform and this install does not have it.
|
|
163
|
+
refuse(null);
|
|
164
|
+
} else {
|
|
165
|
+
handOver(binary);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Run the native compiler and give the caller back exactly what it answered. */
|
|
170
|
+
function handOver(binary) {
|
|
171
|
+
const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" });
|
|
172
|
+
if (result.error !== undefined && result.error !== null) {
|
|
173
|
+
// Installed and will not start -- a broken or partial install rather than an
|
|
174
|
+
// unsupported platform. There is nothing to fall back to, so this is a
|
|
175
|
+
// refusal with the reason in it rather than a warning above a slower compile.
|
|
176
|
+
refuse({ binary, reason: result.error.message });
|
|
177
|
+
} else if (result.signal !== null) {
|
|
178
|
+
// Re-raise rather than translating to an exit code, so that a crash or an
|
|
179
|
+
// interrupt reaches the shell as the signal it was. `process.exitCode`
|
|
180
|
+
// cannot express one, and a wrapper that turned SIGINT into exit 130 would
|
|
181
|
+
// make `nish` the one command in a pipeline that did.
|
|
182
|
+
process.kill(process.pid, result.signal);
|
|
183
|
+
} else {
|
|
184
|
+
process.exitCode = result.status ?? 0;
|
|
185
|
+
}
|
|
186
|
+
}
|
package/bin/nish
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The `nish` command, before `scripts/postinstall.mjs` has had a look at it.
|
|
3
|
+
//
|
|
4
|
+
// This file is deliberately tiny and deliberately not the whole story. On a
|
|
5
|
+
// machine that installed one of the prebuilt `@amritk/nish-<asset>` packages,
|
|
6
|
+
// postinstall replaces this file with a one-line `/bin/sh` exec of that
|
|
7
|
+
// package's native compiler, so nothing node-shaped is left in the path --
|
|
8
|
+
// measured at 3.2 ms against 94 ms for this shim, which is almost entirely
|
|
9
|
+
// node's own startup (docs/wp12-release.md, "What the launcher costs").
|
|
10
|
+
//
|
|
11
|
+
// It execs the binary in its own package rather than copying it here, because
|
|
12
|
+
// npm links the command through a symlink and the native compiler resolves
|
|
13
|
+
// `build.sh` and `runtime/` from argv[0] without following one. That section
|
|
14
|
+
// has the failure a copy produces.
|
|
15
|
+
//
|
|
16
|
+
// It has to keep working anyway, because the swap is a postinstall and
|
|
17
|
+
// `npm ci --ignore-scripts` is a normal thing for CI to do, as is a registry
|
|
18
|
+
// proxy or a sandbox that disables scripts outright. So this is the path that
|
|
19
|
+
// is always correct and sometimes slower, and the swap is the optimisation on
|
|
20
|
+
// top of it. Everything it delegates to is in `./launcher.js`, beside this
|
|
21
|
+
// file rather than in `dist/`: the command may not be a build artifact of the
|
|
22
|
+
// compiler it installs, and since 0.6.0 there is no `dist/` to build it into.
|
|
23
|
+
import "./launcher.js";
|
package/bin/packaging.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which prebuilt binary this machine wants, and what the package holding it is
|
|
3
|
+
* called.
|
|
4
|
+
*
|
|
5
|
+
* `npm install -g @amritk/nish` is a download, not a build: the native
|
|
6
|
+
* compiler is already built, verified and smoke-tested per platform by
|
|
7
|
+
* `release.yml`, and it reaches a user as an `optionalDependencies` entry that
|
|
8
|
+
* npm installs only where `os` and `cpu` match -- the esbuild pattern. This
|
|
9
|
+
* module is the one place the three spellings of a platform are converted
|
|
10
|
+
* between, so the launcher, the package generator and the tests cannot drift:
|
|
11
|
+
*
|
|
12
|
+
* node `process.platform` / `process.arch` darwin / arm64
|
|
13
|
+
* this project the `asset` in .github/seed-targets.json aarch64-darwin
|
|
14
|
+
* npm `os` / `cpu` in a package.json darwin / arm64
|
|
15
|
+
*
|
|
16
|
+
* The middle one is the odd spelling and it is not this module's to change:
|
|
17
|
+
* `asset` is the target triple with the vendor and the ABI dropped, which is
|
|
18
|
+
* what the release assets, the seed protocol and `bootstrap.sh` already call a
|
|
19
|
+
* platform. So the rule is a translation between node's two-word name and that
|
|
20
|
+
* one, and `tests/run.js` checks it against `seed-targets.json` rather than
|
|
21
|
+
* trusting the two lists to stay equal.
|
|
22
|
+
*
|
|
23
|
+
* **This is plain JavaScript under `bin/`, and that is deliberate.** It used
|
|
24
|
+
* to be `src/packaging.ts`, compiled into `dist/` by `tsc` -- which made the
|
|
25
|
+
* command a build artifact of the compiler it is supposed to install. `bin/`
|
|
26
|
+
* is what the tarball carries and what `bin.nish` points into, so the launcher
|
|
27
|
+
* and its platform table now live where they ship, need no build step, and
|
|
28
|
+
* survived the deletion of `src/` (`docs/wp19-stage0-retirement.md` R6). The
|
|
29
|
+
* types they lose are not much of a loss for two string maps; what they gain is
|
|
30
|
+
* that `npm pack` ships the same bytes this repository runs.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The architecture half of an `asset`, keyed by node's `process.arch`.
|
|
35
|
+
*
|
|
36
|
+
* Only the architectures a release actually attaches are here. An arch that is
|
|
37
|
+
* missing is not an error -- it means no prebuilt binary exists for this
|
|
38
|
+
* machine, which the launcher answers by saying so and stopping.
|
|
39
|
+
*/
|
|
40
|
+
const ARCH_BY_CPU = {
|
|
41
|
+
x64: "x86_64",
|
|
42
|
+
arm64: "aarch64",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The operating-system half. It is an identity map today, because node's
|
|
47
|
+
* `linux` and `darwin` are already what the triple calls them, and it exists
|
|
48
|
+
* so that the one platform where that stops being true -- a `win32` whose
|
|
49
|
+
* triple says `windows` -- is a line here rather than a special case at the
|
|
50
|
+
* call site.
|
|
51
|
+
*/
|
|
52
|
+
const OS_BY_PLATFORM = {
|
|
53
|
+
linux: "linux",
|
|
54
|
+
darwin: "darwin",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Every asset a release actually attaches, sorted, in `seed-targets.json`'s
|
|
59
|
+
* spelling. This is the list the refusal *prints*.
|
|
60
|
+
*
|
|
61
|
+
* **A literal list rather than the cross product of the two maps above**, which
|
|
62
|
+
* is what it was first written as and which is wrong in a way that only shows up
|
|
63
|
+
* later: the maps multiply out to this list only because the support matrix
|
|
64
|
+
* happens to be full. Add `riscv64: "riscv64"` for linux alone -- one platform,
|
|
65
|
+
* not four -- and a cross product advertises `riscv64-darwin` to a user, and
|
|
66
|
+
* fails the check that compares this against `.github/seed-targets.json` for a
|
|
67
|
+
* launcher that was behaving correctly. The authority is that file; `tests/run.js`
|
|
68
|
+
* compares the two, so a literal is exactly as gated as a derivation and says
|
|
69
|
+
* only what is true.
|
|
70
|
+
*/
|
|
71
|
+
export const SUPPORTED_ASSETS = ["aarch64-darwin", "aarch64-linux", "x86_64-darwin", "x86_64-linux"];
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The `asset` for a node platform/arch pair, or `null` when this project
|
|
75
|
+
* attaches no binary for it.
|
|
76
|
+
*
|
|
77
|
+
* `null` is a supported answer rather than a crash: Alpine on musl, a FreeBSD,
|
|
78
|
+
* a linux/riscv64 all land here. What it is no longer is a *quiet* answer --
|
|
79
|
+
* there is no compiler in the package to fall back to since 0.6.0, so the
|
|
80
|
+
* launcher turns this `null` into a diagnostic naming the four platforms and
|
|
81
|
+
* exits non-zero (`docs/wp12-release.md`, "Which compiler the package ships").
|
|
82
|
+
*/
|
|
83
|
+
export const assetFor = (platform, arch) => {
|
|
84
|
+
const os = OS_BY_PLATFORM[platform];
|
|
85
|
+
const cpu = ARCH_BY_CPU[arch];
|
|
86
|
+
if (os === undefined || cpu === undefined) return null;
|
|
87
|
+
return `${cpu}-${os}`;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** The inverse, for the generator and the tests: an `asset` back to npm's pair. */
|
|
91
|
+
export const targetForAsset = (asset) => {
|
|
92
|
+
const dash = asset.indexOf("-");
|
|
93
|
+
if (dash < 0) return null;
|
|
94
|
+
const arch = asset.slice(0, dash);
|
|
95
|
+
const os = asset.slice(dash + 1);
|
|
96
|
+
const cpu = Object.keys(ARCH_BY_CPU).find((key) => ARCH_BY_CPU[key] === arch);
|
|
97
|
+
const platform = Object.keys(OS_BY_PLATFORM).find((key) => OS_BY_PLATFORM[key] === os);
|
|
98
|
+
if (cpu === undefined || platform === undefined) return null;
|
|
99
|
+
return { asset, os: platform, cpu };
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The package holding the binary for one asset, derived from the main
|
|
104
|
+
* package's own name: `@amritk/nish` + `x86_64-linux` -> `@amritk/nish-x86_64-linux`.
|
|
105
|
+
*
|
|
106
|
+
* Derived rather than written out so that the scope is spelled once. The
|
|
107
|
+
* registry name was settled late (`nish` belongs to somebody else, so this one
|
|
108
|
+
* is scoped -- docs/wp12-release.md "The npm name"), and a list of five literal
|
|
109
|
+
* names would be five places to edit if it ever moves again.
|
|
110
|
+
*/
|
|
111
|
+
export const platformPackageName = (packageName, asset) => `${packageName}-${asset}`;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The diagnostic code every refusal here carries: `NL0002`, the toolchain code.
|
|
115
|
+
*
|
|
116
|
+
* Not a new code, and that is the point. `self/codes.ts` defines `NL0002` as
|
|
117
|
+
* "the toolchain `--link` needs could not be used (exit 3)", and a prebuilt
|
|
118
|
+
* compiler that is absent or will not start is the same class of failure seen
|
|
119
|
+
* one step earlier: no source position, nothing wrong with the program, and the
|
|
120
|
+
* thing that could not be run is a binary rather than the input. Reusing it
|
|
121
|
+
* keeps the launcher out of the diagnostic registry entirely -- a code minted
|
|
122
|
+
* here would be one `self/codes.ts` mirrors for a message the compiler can
|
|
123
|
+
* never print, and `tests/diagnostic_coverage.js` would then want a case
|
|
124
|
+
* provoking a rule that does not exist.
|
|
125
|
+
*/
|
|
126
|
+
export const NO_COMPILER_CODE = "NL0002";
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Why there is no compiler to run, and what to do about it: the one-line
|
|
130
|
+
* `message` a `--json` object carries, and the report a person reads.
|
|
131
|
+
*
|
|
132
|
+
* **Both, from one function, because the contract is that they say the same
|
|
133
|
+
* thing.** `AGENTS.md` promises that a failure with no source position is still
|
|
134
|
+
* one JSON line under `--json` and that "you never have to read stderr to find
|
|
135
|
+
* out why a run failed" -- so the refusal cannot be prose only, and the first
|
|
136
|
+
* version of this file made it prose only, which broke that contract on a path
|
|
137
|
+
* a user reaches with `--no-optional` on a fully supported platform.
|
|
138
|
+
*
|
|
139
|
+
* A pure function rather than `process.stderr.write` calls in the launcher, for
|
|
140
|
+
* one reason: the branch a user on musl or FreeBSD hits is the branch no machine
|
|
141
|
+
* in CI can reach, because every runner this project uses is one of the four
|
|
142
|
+
* supported platforms. Written like this, `tests/run.js` can ask for that exact
|
|
143
|
+
* message on any host and assert what it says, while the branch the harness
|
|
144
|
+
* *can* reach is asserted end to end on the installed command. A diagnostic
|
|
145
|
+
* nothing can test is how the advice in it goes stale.
|
|
146
|
+
*
|
|
147
|
+
* Three situations, and they get different advice because the remedy differs:
|
|
148
|
+
*
|
|
149
|
+
* - `unsupported`: this project publishes no binary for the machine. Nothing
|
|
150
|
+
* about the install is wrong and reinstalling will not help, so the advice
|
|
151
|
+
* is the bootstrap, and INSTALL.md carries the part that needs more than
|
|
152
|
+
* four lines -- including that on musl no released binary may run at all.
|
|
153
|
+
* - `missing`: a binary exists for this platform and the install does not
|
|
154
|
+
* have it. Reinstalling is the remedy.
|
|
155
|
+
* - `unstartable`: it is installed and will not run. A broken or partly
|
|
156
|
+
* written install, named as one rather than reported as an unsupported
|
|
157
|
+
* platform.
|
|
158
|
+
*
|
|
159
|
+
* Every branch names the four platforms, because a user who has just been
|
|
160
|
+
* refused wants to know whether the list is the whole list. It is.
|
|
161
|
+
*/
|
|
162
|
+
export const noCompilerMessage = ({ platform, arch, packageName, unstartable = null }) => {
|
|
163
|
+
const asset = assetFor(platform, arch);
|
|
164
|
+
const published = SUPPORTED_ASSETS.join(" ");
|
|
165
|
+
const preamble =
|
|
166
|
+
" This package installs a prebuilt native compiler. One is published for:\n" +
|
|
167
|
+
` ${published}\n\n` +
|
|
168
|
+
" There is no compiler inside the package to fall back to, and nothing is\n" +
|
|
169
|
+
" compiled on your machine on any path.\n";
|
|
170
|
+
if (asset === null) {
|
|
171
|
+
return {
|
|
172
|
+
code: NO_COMPILER_CODE,
|
|
173
|
+
summary:
|
|
174
|
+
`no prebuilt compiler for ${platform}/${arch}; one is published for ${published}, ` +
|
|
175
|
+
"and there is none inside the package to fall back to. Build one from a released " +
|
|
176
|
+
"`nish` that runs here: NISH_BOOTSTRAP=<nish> scripts/bootstrap.sh in a checkout " +
|
|
177
|
+
"(docs/INSTALL.md)",
|
|
178
|
+
report:
|
|
179
|
+
`nish: no prebuilt compiler for ${platform}/${arch}\n\n${preamble}\n` +
|
|
180
|
+
" To get a compiler for this machine, build one from a released `nish` in a\n" +
|
|
181
|
+
" checkout of the repository:\n" +
|
|
182
|
+
" NISH_BOOTSTRAP=<a released nish that runs here> scripts/bootstrap.sh\n" +
|
|
183
|
+
" docs/INSTALL.md has the detail, including what to do when no released\n" +
|
|
184
|
+
" binary runs on this platform at all.\n",
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (unstartable !== null) {
|
|
188
|
+
return {
|
|
189
|
+
code: NO_COMPILER_CODE,
|
|
190
|
+
summary:
|
|
191
|
+
`${unstartable.binary} could not be started (${unstartable.reason}); that is a broken ` +
|
|
192
|
+
"or partly written install rather than an unsupported platform. Reinstall the " +
|
|
193
|
+
"package, or run `npm rebuild` if the tree moved",
|
|
194
|
+
report:
|
|
195
|
+
`nish: ${unstartable.binary} could not be started (${unstartable.reason})\n\n${preamble}\n` +
|
|
196
|
+
" That is a broken or partly written install rather than an unsupported\n" +
|
|
197
|
+
" platform. Reinstall the package, or run `npm rebuild` if the tree moved.\n",
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// A `packageName` of `null` means this package's own `package.json` could not
|
|
201
|
+
// be read, which is a broken install and not the moment to guess: naming the
|
|
202
|
+
// wrong package sends the user to install something that does not exist, so
|
|
203
|
+
// the sentence loses the name instead of inventing one.
|
|
204
|
+
const named = packageName === null ? null : platformPackageName(packageName, asset);
|
|
205
|
+
const pkg = named === null ? `the platform package for ${asset}` : named;
|
|
206
|
+
return {
|
|
207
|
+
code: NO_COMPILER_CODE,
|
|
208
|
+
summary:
|
|
209
|
+
`the prebuilt compiler for ${asset} is not installed; its package is ${pkg}, which npm ` +
|
|
210
|
+
"installs automatically for this platform. Reinstall the package, or install the pair " +
|
|
211
|
+
"by hand (docs/INSTALL.md)",
|
|
212
|
+
report:
|
|
213
|
+
`nish: the prebuilt compiler for ${asset} is not installed\n\n${preamble}\n` +
|
|
214
|
+
` Its package is ${pkg}, which npm installs\n` +
|
|
215
|
+
" automatically for this platform. An install that skipped it is usually\n" +
|
|
216
|
+
" `--no-optional`, a lockfile without the platform packages, or a registry that\n" +
|
|
217
|
+
" does not carry them yet. Reinstall the package, or install the pair by hand as\n" +
|
|
218
|
+
" docs/INSTALL.md shows.\n",
|
|
219
|
+
};
|
|
220
|
+
};
|