@tomasko/mb 0.1.1 → 0.1.3

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 (5) hide show
  1. package/README.md +39 -4
  2. package/bin/mb.js +25 -17
  3. package/install.js +16 -108
  4. package/lib.js +143 -0
  5. package/package.json +2 -1
package/README.md CHANGED
@@ -12,10 +12,14 @@ mb tool install bobzhang/zip
12
12
 
13
13
  ## How it works
14
14
 
15
- The package itself contains only a small launcher. On install,
16
- `install.js` downloads the prebuilt `mb` binary for the current platform from
17
- GitHub Releases and writes it to `bin/mb`. The published `bin` entry
18
- (`bin/mb.js`) then spawns it, forwarding arguments and standard IO.
15
+ The package itself contains only a small launcher. On install, `install.js`
16
+ downloads the prebuilt `mb` binary for the current platform from GitHub
17
+ Releases and writes it to `bin/mb`. The published `bin` entry (`bin/mb.js`)
18
+ then spawns it, forwarding arguments, standard IO and the exit code.
19
+
20
+ The postinstall download is best-effort, and the launcher downloads the
21
+ binary on first run if it is missing (for example when installing with
22
+ `--ignore-scripts`, or when the release was not available yet).
19
23
 
20
24
  Supported targets:
21
25
 
@@ -31,6 +35,34 @@ ships a `darwin-x86_64` target, so no such build can be produced.
31
35
  Assets are produced by `.github/workflows/release.yml` when a `v*` tag is
32
36
  pushed.
33
37
 
38
+ ## Install scripts are blocked by default
39
+
40
+ Recent npm (12+) and pnpm (10+) versions do **not** run a dependency's install
41
+ scripts unless the package is approved, so `postinstall` is skipped and
42
+ `bin/mb` is missing right after install:
43
+
44
+ ```
45
+ npm warn install-scripts 1 package had install scripts blocked because they are not covered by allowScripts:
46
+ npm warn install-scripts @tomasko/mb@0.1.3 (postinstall: node install.js)
47
+ mb: native binary not found at .../node_modules/@tomasko/mb/bin/mb
48
+ ```
49
+
50
+ Nothing is broken: `bin/mb.js` downloads the binary on first run. To have it
51
+ downloaded at install time instead, approve the build once:
52
+
53
+ ```bash
54
+ npm install-scripts approve @tomasko/mb # npm 12+ (writes `allowScripts`)
55
+ pnpm approve-builds # pnpm 10+
56
+ ```
57
+
58
+ Either way, re-run the download manually with:
59
+
60
+ ```bash
61
+ npm rebuild @tomasko/mb
62
+ # or
63
+ pnpm rebuild @tomasko/mb
64
+ ```
65
+
34
66
  ## Overrides
35
67
 
36
68
  | Variable | Purpose |
@@ -47,7 +79,10 @@ From this directory:
47
79
 
48
80
  ```bash
49
81
  npm publish --access public
82
+ # or
83
+ pnpm publish --access public
50
84
  ```
51
85
 
86
+ `--access public` is required for scoped packages such as `@tomasko/mb`.
52
87
  Change `name`, `repository` and `homepage` in `package.json` first if you
53
88
  publish under a different scope or repository.
package/bin/mb.js CHANGED
@@ -1,27 +1,35 @@
1
1
  #!/usr/bin/env node
2
- // Thin launcher for the downloaded native `mb` binary.
2
+ // Launcher for the native `mb` binary. Downloads it on first run if the
3
+ // postinstall step did not (e.g. `--ignore-scripts`), then forwards all
4
+ // arguments, stdio and the exit code.
3
5
  "use strict";
4
6
 
5
7
  const { spawnSync } = require("child_process");
6
- const fs = require("fs");
7
- const path = require("path");
8
+ const { ensureBinary, pkg } = require("../lib");
8
9
 
9
- const ext = process.platform === "win32" ? ".exe" : "";
10
- const binary = path.join(__dirname, "mb" + ext);
10
+ async function main() {
11
+ let binary;
12
+ try {
13
+ binary = await ensureBinary({
14
+ log: (line) => process.stderr.write(line + "\n"),
15
+ });
16
+ } catch (err) {
17
+ console.error(`mb: ${err.message}`);
18
+ console.error(
19
+ `mb: run \`npm rebuild ${pkg.name}\` to retry, or set MB_DOWNLOAD_BASE ` +
20
+ `(or MB_REPO/MB_VERSION) to a mirror.`
21
+ );
22
+ process.exit(1);
23
+ }
11
24
 
12
- if (!fs.existsSync(binary)) {
13
- console.error(
14
- `mb: native binary not found at ${binary}\n` +
15
- "mb: reinstall it with `npm rebuild mb` (or `npm install -g mb --force`)."
16
- );
17
- process.exit(1);
18
- }
25
+ const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" });
19
26
 
20
- const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" });
27
+ if (result.error) {
28
+ console.error(`mb: failed to launch ${binary}: ${result.error.message}`);
29
+ process.exit(1);
30
+ }
21
31
 
22
- if (result.error) {
23
- console.error(`mb: failed to launch ${binary}: ${result.error.message}`);
24
- process.exit(1);
32
+ process.exit(typeof result.status === "number" ? result.status : 1);
25
33
  }
26
34
 
27
- process.exit(typeof result.status === "number" ? result.status : 1);
35
+ main();
package/install.js CHANGED
@@ -1,117 +1,25 @@
1
1
  #!/usr/bin/env node
2
- // Downloads the prebuilt `mb` binary for the current platform from GitHub
3
- // Releases and drops it next to this package's launcher (`bin/mb.js`).
2
+ // Postinstall hook: download the prebuilt `mb` binary for this platform.
4
3
  //
5
- // Overridable for forks / private mirrors:
6
- // MB_REPO owner/repo (default: parsed from package.json)
7
- // MB_VERSION release version (default: package.json version)
8
- // MB_DOWNLOAD_BASE full base URL (overrides MB_REPO/MB_VERSION)
9
- // MB_SKIP_DOWNLOAD set to skip fetching
4
+ // This is best-effort: if the download fails the install still succeeds, and
5
+ // `bin/mb.js` retries on first run. Set MB_SKIP_DOWNLOAD=1 to opt out.
10
6
  "use strict";
11
7
 
12
- const fs = require("fs");
13
- const path = require("path");
14
- const https = require("https");
15
- const http = require("http");
8
+ const { ensureBinary, pkg } = require("./lib");
16
9
 
17
- const pkg = require("./package.json");
18
-
19
- const TARGETS = {
20
- "linux-x64": "linux-x86_64",
21
- "linux-arm64": "linux-aarch64",
22
- "darwin-arm64": "darwin-aarch64",
23
- };
24
-
25
- function repoSlug(repo) {
26
- if (!repo) return null;
27
- const url = typeof repo === "string" ? repo : repo.url || "";
28
- const m = url.match(/github\.com[/:]([^/]+)\/([^/.]+)/);
29
- return m ? `${m[1]}/${m[2]}` : null;
30
- }
31
-
32
- function target() {
33
- return TARGETS[`${process.platform}-${process.arch}`];
34
- }
35
-
36
- function downloadUrl(targetName) {
37
- if (process.env.MB_DOWNLOAD_BASE) {
38
- return `${process.env.MB_DOWNLOAD_BASE.replace(/\/$/, "")}/mb-${targetName}`;
39
- }
40
- const repo = process.env.MB_REPO || repoSlug(pkg.repository) || "tomasky/mb";
41
- const version = process.env.MB_VERSION || pkg.version;
42
- return `https://github.com/${repo}/releases/download/v${version}/mb-${targetName}`;
43
- }
44
-
45
- function fetch(url, redirects) {
46
- return new Promise((resolve, reject) => {
47
- if (redirects > 10) return reject(new Error("too many redirects"));
48
- const client = url.startsWith("http:") ? http : https;
49
- const req = client.get(
50
- url,
51
- { headers: { "user-agent": `mb-npm/${pkg.version}` } },
52
- (res) => {
53
- const { statusCode, headers } = res;
54
- if (statusCode >= 300 && statusCode < 400 && headers.location) {
55
- res.resume();
56
- const next = new URL(headers.location, url).toString();
57
- resolve(fetch(next, (redirects || 0) + 1));
58
- return;
59
- }
60
- if (statusCode !== 200) {
61
- res.resume();
62
- reject(new Error(`GET ${url} -> HTTP ${statusCode}`));
63
- return;
64
- }
65
- resolve(res);
66
- }
67
- );
68
- req.on("error", reject);
69
- });
10
+ if (process.env.MB_SKIP_DOWNLOAD) {
11
+ console.log("mb: MB_SKIP_DOWNLOAD set, skipping binary download");
12
+ process.exit(0);
70
13
  }
71
14
 
72
- async function main() {
73
- if (process.env.MB_SKIP_DOWNLOAD) {
74
- console.log("mb: MB_SKIP_DOWNLOAD set, skipping binary download");
75
- return;
76
- }
77
-
78
- const targetName = target();
79
- if (!targetName) {
80
- throw new Error(
81
- `unsupported platform ${process.platform}-${process.arch}; mb ` +
82
- `supports linux-x64, linux-arm64 and darwin-arm64 (the MoonBit ` +
83
- `toolchain no longer ships a darwin-x64 target)`
15
+ ensureBinary({ log: console.log })
16
+ .then(() => process.exit(0))
17
+ .catch((err) => {
18
+ console.warn(`mb: could not download the native binary: ${err.message}`);
19
+ console.warn(
20
+ `mb: it will be downloaded on first run instead. If that also fails, ` +
21
+ `set MB_DOWNLOAD_BASE (or MB_REPO/MB_VERSION) to a mirror and run ` +
22
+ `\`npm rebuild ${pkg.name}\`.`
84
23
  );
85
- }
86
-
87
- const ext = process.platform === "win32" ? ".exe" : "";
88
- const dest = path.join(__dirname, "bin", "mb" + ext);
89
- fs.mkdirSync(path.dirname(dest), { recursive: true });
90
-
91
- if (fs.existsSync(dest) && !process.env.MB_FORCE_DOWNLOAD) {
92
- console.log(`mb: ${dest} already present, skipping download`);
93
- return;
94
- }
95
-
96
- const url = downloadUrl(targetName);
97
- console.log(`mb: downloading ${url}`);
98
- const res = await fetch(url, 0);
99
- await new Promise((resolve, reject) => {
100
- const out = fs.createWriteStream(dest);
101
- res.pipe(out);
102
- out.on("finish", () => out.close(resolve));
103
- out.on("error", reject);
104
- res.on("error", reject);
24
+ process.exit(0);
105
25
  });
106
- fs.chmodSync(dest, 0o755);
107
- console.log(`mb: installed ${dest}`);
108
- }
109
-
110
- main().catch((err) => {
111
- console.error(`mb: failed to install the native binary: ${err.message}`);
112
- console.error(
113
- "mb: set MB_DOWNLOAD_BASE (or MB_REPO/MB_VERSION) to point at a mirror, " +
114
- "or set MB_SKIP_DOWNLOAD=1 to skip."
115
- );
116
- process.exit(1);
117
- });
package/lib.js ADDED
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+
3
+ // Shared install logic for the npm wrapper: works out which prebuilt `mb`
4
+ // binary to use and downloads it from GitHub Releases on demand.
5
+ //
6
+ // Overridable for forks / private mirrors:
7
+ // MB_REPO owner/repo (default: parsed from package.json)
8
+ // MB_VERSION release version (default: package.json version)
9
+ // MB_DOWNLOAD_BASE full base URL (overrides MB_REPO/MB_VERSION)
10
+ // MB_FORCE_DOWNLOAD re-download even if the binary is present
11
+ // MB_SKIP_DOWNLOAD never download
12
+
13
+ const fs = require("fs");
14
+ const path = require("path");
15
+ const https = require("https");
16
+ const http = require("http");
17
+
18
+ const pkg = require("./package.json");
19
+
20
+ // Node `platform-arch` -> MoonBit release target. Intel macOS is absent on
21
+ // purpose: the MoonBit toolchain no longer ships a darwin-x86_64 target.
22
+ const TARGETS = {
23
+ "linux-x64": "linux-x86_64",
24
+ "linux-arm64": "linux-aarch64",
25
+ "darwin-arm64": "darwin-aarch64",
26
+ };
27
+
28
+ function repoSlug(repo) {
29
+ if (!repo) return null;
30
+ const url = typeof repo === "string" ? repo : repo.url || "";
31
+ const m = url.match(/github\.com[/:]([^/]+)\/([^/.]+)/);
32
+ return m ? `${m[1]}/${m[2]}` : null;
33
+ }
34
+
35
+ function targetName() {
36
+ return TARGETS[`${process.platform}-${process.arch}`];
37
+ }
38
+
39
+ function downloadUrl(target) {
40
+ if (process.env.MB_DOWNLOAD_BASE) {
41
+ return `${process.env.MB_DOWNLOAD_BASE.replace(/\/$/, "")}/mb-${target}`;
42
+ }
43
+ const repo = process.env.MB_REPO || repoSlug(pkg.repository) || "tomasky/mb";
44
+ const version = process.env.MB_VERSION || pkg.version;
45
+ return `https://github.com/${repo}/releases/download/v${version}/mb-${target}`;
46
+ }
47
+
48
+ function binaryPath() {
49
+ return path.join(
50
+ __dirname,
51
+ "bin",
52
+ process.platform === "win32" ? "mb.exe" : "mb"
53
+ );
54
+ }
55
+
56
+ function isInstalled() {
57
+ try {
58
+ fs.accessSync(binaryPath(), fs.constants.X_OK);
59
+ return true;
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ function fetch(url, redirects) {
66
+ return new Promise((resolve, reject) => {
67
+ if (redirects > 10) return reject(new Error("too many redirects"));
68
+ const client = url.startsWith("http:") ? http : https;
69
+ const req = client.get(
70
+ url,
71
+ { headers: { "user-agent": `mb-npm/${pkg.version}` } },
72
+ (res) => {
73
+ const { statusCode, headers } = res;
74
+ if (statusCode >= 300 && statusCode < 400 && headers.location) {
75
+ res.resume();
76
+ const next = new URL(headers.location, url).toString();
77
+ resolve(fetch(next, (redirects || 0) + 1));
78
+ return;
79
+ }
80
+ if (statusCode !== 200) {
81
+ res.resume();
82
+ reject(new Error(`GET ${url} -> HTTP ${statusCode}`));
83
+ return;
84
+ }
85
+ resolve(res);
86
+ }
87
+ );
88
+ req.on("error", reject);
89
+ });
90
+ }
91
+
92
+ async function download(target, dest) {
93
+ const url = downloadUrl(target);
94
+ const res = await fetch(url, 0);
95
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
96
+ await new Promise((resolve, reject) => {
97
+ const out = fs.createWriteStream(dest);
98
+ res.pipe(out);
99
+ out.on("finish", () => out.close(resolve));
100
+ out.on("error", reject);
101
+ res.on("error", reject);
102
+ });
103
+ fs.chmodSync(dest, 0o755);
104
+ return url;
105
+ }
106
+
107
+ // Resolves to a ready-to-run binary path, downloading it when missing.
108
+ async function ensureBinary({ force = false, log = () => {} } = {}) {
109
+ const dest = binaryPath();
110
+
111
+ if (!force && !process.env.MB_FORCE_DOWNLOAD && isInstalled()) {
112
+ return dest;
113
+ }
114
+
115
+ if (process.env.MB_SKIP_DOWNLOAD) {
116
+ if (isInstalled()) return dest;
117
+ throw new Error("MB_SKIP_DOWNLOAD is set but no binary is installed");
118
+ }
119
+
120
+ const target = targetName();
121
+ if (!target) {
122
+ throw new Error(
123
+ `unsupported platform ${process.platform}-${process.arch}; mb ` +
124
+ `supports linux-x64, linux-arm64 and darwin-arm64 (the MoonBit ` +
125
+ `toolchain no longer ships a darwin-x64 target)`
126
+ );
127
+ }
128
+
129
+ log(`mb: downloading ${downloadUrl(target)}`);
130
+ await download(target, dest);
131
+ log(`mb: installed ${dest}`);
132
+ return dest;
133
+ }
134
+
135
+ module.exports = {
136
+ pkg,
137
+ TARGETS,
138
+ targetName,
139
+ downloadUrl,
140
+ binaryPath,
141
+ isInstalled,
142
+ ensureBinary,
143
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomasko/mb",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "A MoonBit-native, uv-style toolchain and package manager: manage MoonBit toolchains and install tools from mooncakes.io.",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -12,6 +12,7 @@
12
12
  "files": [
13
13
  "bin/mb.js",
14
14
  "install.js",
15
+ "lib.js",
15
16
  "README.md"
16
17
  ],
17
18
  "engines": {