mz-dev 0.1.0-beta.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Pankaj Koirala
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # mz-dev
2
+
3
+ Network debugging proxy + dashboard for Flutter / mobile development.
4
+
5
+ ```bash
6
+ # One-shot
7
+ npx mz-dev start
8
+
9
+ # Persistent install
10
+ npm install -g mz-dev
11
+ mz start
12
+ ```
13
+
14
+ ## What you get
15
+
16
+ - A local HTTP/HTTPS proxy that captures every request your app
17
+ makes.
18
+ - A web dashboard at `http://localhost:8889` to inspect, replay,
19
+ mock, intercept, and AI-analyse traffic.
20
+ - An MCP server so AI tools can drive the same workflows.
21
+
22
+ ## Supported platforms
23
+
24
+ `darwin-arm64`, `darwin-x64`, `linux-x64`, `linux-arm64`, `windows-x64`.
25
+
26
+ The package itself is platform-agnostic. The `postinstall` step
27
+ downloads the matching prebuilt binary + Flutter web bundle from
28
+ the GitHub Release tagged `v<this package version>`.
29
+
30
+ ## First-launch warnings
31
+
32
+ - **macOS**: The binary is unsigned (we don't yet pay for an Apple
33
+ Developer account). On first run macOS Gatekeeper will refuse to
34
+ open it. Workaround:
35
+ ```bash
36
+ xattr -d com.apple.quarantine "$(npm root -g)/mz-dev/platform/$(node -p "process.platform + '-' + process.arch")/bin/mz"
37
+ ```
38
+ Or right-click the binary in Finder → Open.
39
+ - **Windows**: SmartScreen will prompt "Windows protected your PC"
40
+ on first launch. Click *More info* → *Run anyway*.
41
+ - **Linux**: nothing to do.
42
+
43
+ ## Source
44
+
45
+ <https://github.com/koiralapankaj7/mz_dev>
package/bin/mz.js ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Thin shim that execs the platform-specific `mz` binary the
4
+ * `postinstall` step downloaded.
5
+ *
6
+ * Sets `MZ_DEV_UI_DIR` so the binary finds the bundled Flutter web
7
+ * assets next to itself (`<package>/platform/<arch>/ui/web/`)
8
+ * regardless of the user's current working directory.
9
+ *
10
+ * Forwards stdio + signals so the binary feels native: Ctrl+C, pipes,
11
+ * exit codes all behave as if the user invoked the binary directly.
12
+ */
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const { spawn } = require('child_process');
17
+ const { binaryPath, uiDir, platformDir } = require('../scripts/resolve');
18
+
19
+ let resolvedBinary;
20
+ try {
21
+ resolvedBinary = binaryPath();
22
+ } catch (e) {
23
+ console.error(e.message);
24
+ process.exit(1);
25
+ }
26
+
27
+ if (!fs.existsSync(resolvedBinary)) {
28
+ console.error(
29
+ `mz-dev: binary missing at ${resolvedBinary}. ` +
30
+ `The postinstall step may have failed — try ` +
31
+ `\`npm rebuild mz-dev\` or reinstall the package.`,
32
+ );
33
+ process.exit(1);
34
+ }
35
+
36
+ const env = { ...process.env };
37
+ const ui = uiDir();
38
+ if (fs.existsSync(ui)) {
39
+ env.MZ_DEV_UI_DIR = ui;
40
+ }
41
+
42
+ const child = spawn(resolvedBinary, process.argv.slice(2), {
43
+ stdio: 'inherit',
44
+ env,
45
+ windowsHide: false,
46
+ });
47
+
48
+ // Forward common termination signals so a Ctrl+C in the user's
49
+ // terminal stops the underlying binary (otherwise it hangs as
50
+ // an orphan).
51
+ const SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGQUIT'];
52
+ const signalHandlers = new Map();
53
+ for (const sig of SIGNALS) {
54
+ const handler = () => {
55
+ if (!child.killed) child.kill(sig);
56
+ };
57
+ signalHandlers.set(sig, handler);
58
+ process.on(sig, handler);
59
+ }
60
+
61
+ function removeSignalHandlers() {
62
+ for (const [signal, handler] of signalHandlers) {
63
+ process.removeListener(signal, handler);
64
+ }
65
+ }
66
+
67
+ child.on('error', (err) => {
68
+ removeSignalHandlers();
69
+ console.error(`mz-dev (${platformDir()}): failed to launch binary: ${err.message}`);
70
+ process.exit(1);
71
+ });
72
+
73
+ child.on('exit', (code, signal) => {
74
+ // Restore default signal handling before re-raising; otherwise our own
75
+ // forwarding listener consumes the signal and may incorrectly exit with 0.
76
+ removeSignalHandlers();
77
+ if (signal) {
78
+ // Re-raise the signal in our own process so callers (shells,
79
+ // CI runners) see the same exit reason as the child.
80
+ process.kill(process.pid, signal);
81
+ } else {
82
+ process.exit(code ?? 0);
83
+ }
84
+ });
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "mz-dev",
3
+ "version": "0.1.0-beta.0",
4
+ "description": "Network debugging proxy + dashboard for Flutter / mobile development",
5
+ "keywords": [
6
+ "flutter",
7
+ "network",
8
+ "proxy",
9
+ "debugging",
10
+ "devtools",
11
+ "mock-server",
12
+ "intercept",
13
+ "cli"
14
+ ],
15
+ "homepage": "https://github.com/koiralapankaj7/mz_dev",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/koiralapankaj7/mz_dev.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/koiralapankaj7/mz_dev/issues"
22
+ },
23
+ "license": "MIT",
24
+ "author": "Pankaj Koirala",
25
+ "bin": {
26
+ "mz": "bin/mz.js",
27
+ "mz-dev": "bin/mz.js"
28
+ },
29
+ "files": [
30
+ "bin",
31
+ "scripts",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "scripts": {
36
+ "postinstall": "node scripts/install.js",
37
+ "preuninstall": "node scripts/uninstall.js"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ },
42
+ "dependencies": {
43
+ "tar": "^7.5.22"
44
+ }
45
+ }
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+ const crypto = require('node:crypto');
5
+ const { Readable, Transform } = require('node:stream');
6
+ const { pipeline } = require('node:stream/promises');
7
+
8
+ function expectedChecksum(manifest, archive) {
9
+ const matches = [];
10
+ for (const line of manifest.split('\n')) {
11
+ const match = /^([a-fA-F0-9]{64}) [ *](.+)$/.exec(line.replace(/\r$/, ''));
12
+ if (match && match[2].split('/').pop() === archive) matches.push(match[1].toLowerCase());
13
+ }
14
+ if (matches.length !== 1) throw new Error('Expected exactly one archive checksum in SHA256SUMS.txt');
15
+ return matches[0];
16
+ }
17
+
18
+ async function downloadFile(url, destination, {
19
+ maxBytes = 512 * 1024 * 1024, timeoutMs = 120000, fetchImpl = fetch,
20
+ } = {}) {
21
+ const controller = new AbortController();
22
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
23
+ let response;
24
+ try {
25
+ for (let redirects = 0; ; redirects++) {
26
+ const parsed = new URL(url);
27
+ if (parsed.protocol !== 'https:' || parsed.username || parsed.password) {
28
+ throw new Error('Installer downloads require HTTPS without URL credentials');
29
+ }
30
+ response = await fetchImpl(parsed.href, {
31
+ redirect: 'manual', signal: controller.signal,
32
+ headers: { 'User-Agent': 'mz-dev-installer' },
33
+ });
34
+ if (![301, 302, 303, 307, 308].includes(response.status)) break;
35
+ await response.body?.cancel();
36
+ const location = response.headers.get('location');
37
+ if (!location || redirects >= 5) throw new Error('Invalid or excessive download redirects');
38
+ url = new URL(location, parsed).href;
39
+ }
40
+ if (response.status !== 200 || !response.body) throw new Error(`Download HTTP ${response.status}`);
41
+ const hash = crypto.createHash('sha256');
42
+ let bytes = 0;
43
+ const meter = new Transform({
44
+ transform(chunk, encoding, callback) {
45
+ bytes += chunk.length;
46
+ if (bytes > maxBytes) return callback(new Error('Download exceeds size limit'));
47
+ hash.update(chunk);
48
+ callback(null, chunk);
49
+ },
50
+ });
51
+ await pipeline(Readable.fromWeb(response.body), meter,
52
+ fs.createWriteStream(destination, { flags: 'wx', mode: 0o600 }),
53
+ { signal: controller.signal });
54
+ return hash.digest('hex');
55
+ } finally {
56
+ controller.abort();
57
+ clearTimeout(timer);
58
+ }
59
+ }
60
+
61
+ async function verifiedArchive(baseUrl, archive, directory, options = {}) {
62
+ if (!/^mz-dev-[a-z0-9-]+\.tar\.gz$/.test(archive)) throw new Error('Invalid archive name');
63
+ const manifestPath = path.join(directory, 'SHA256SUMS.txt');
64
+ await downloadFile(`${baseUrl}/SHA256SUMS.txt`, manifestPath,
65
+ { ...options, maxBytes: 1024 * 1024 });
66
+ const expected = expectedChecksum(await fs.promises.readFile(manifestPath, 'utf8'), archive);
67
+ const archivePath = path.join(directory, archive);
68
+ const actual = await downloadFile(`${baseUrl}/${archive}`, archivePath, options);
69
+ if (actual !== expected) throw new Error('Archive SHA-256 checksum mismatch');
70
+ return archivePath;
71
+ }
72
+
73
+ module.exports = { expectedChecksum, downloadFile, verifiedArchive };
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+ const tar = require('tar');
3
+
4
+ function entryFilter(root, { maxEntries = 20000, maxBytes = 1024 * 1024 * 1024 } = {}) {
5
+ let count = 0;
6
+ let bytes = 0;
7
+ const seen = new Set();
8
+ return function (name, entry) {
9
+ const clean = name.replace(/\/$/, '');
10
+ const parts = clean.split('/');
11
+ const key = clean.toLowerCase();
12
+ count++;
13
+ bytes += entry.size;
14
+ if (parts[0] !== root || parts.some(part => !part || part === '.' || part === '..') ||
15
+ /[\\:\x00-\x1f]/.test(name) || !['File', 'Directory'].includes(entry.type) ||
16
+ seen.has(key) || !Number.isSafeInteger(entry.size) || entry.size < 0 ||
17
+ count > maxEntries || bytes > maxBytes) {
18
+ this.abort(new Error('Unsafe or oversized release archive entry'));
19
+ return false;
20
+ }
21
+ seen.add(key);
22
+ return true;
23
+ };
24
+ }
25
+
26
+ async function extractArchive(file, directory, root, limits = {}) {
27
+ // Preflight the complete archive before creating any extracted files.
28
+ await tar.t({ file, strict: true, maxDecompressionRatio: 1000,
29
+ filter: entryFilter(root, limits) });
30
+ await tar.x({ file, cwd: directory, strip: 1, strict: true,
31
+ preserveOwner: false, maxDecompressionRatio: 1000,
32
+ filter: entryFilter(root, limits) });
33
+ }
34
+ module.exports = { extractArchive };
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Postinstall: downloads the platform-specific mz_dev binary +
4
+ * Flutter UI bundle from the matching GitHub Release and unpacks it
5
+ * into `<package>/platform/<arch>/`.
6
+ *
7
+ * Why a downloader instead of bundling every platform's binary in
8
+ * the npm package: keeps the published tarball tiny (~5 KB instead
9
+ * of ~80 MB) and avoids tagging the npm registry with binaries it
10
+ * shouldn't be hosting.
11
+ *
12
+ * Re-running install skips download only for a marked, complete bundle.
13
+ *
14
+ * Failure is non-fatal: we print a clear "download manually from X"
15
+ * pointer and exit 0 so `npm install` itself doesn't break for
16
+ * users who'll fetch the binary another way (offline, corporate
17
+ * proxy, etc.).
18
+ */
19
+ 'use strict';
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+ const os = require('os');
24
+ const { verifiedArchive } = require('./download');
25
+ const { isCompleteInstall, installBundle } = require('./installation');
26
+
27
+ const { extractArchive } = require('./extract');
28
+
29
+ const {
30
+ binaryPath,
31
+ binaryName,
32
+ platformDir,
33
+ platformRoot,
34
+ } = require('./resolve');
35
+ const pkg = require('../package.json');
36
+
37
+ const REPO = 'koiralapankaj7/mz_dev';
38
+
39
+ function logFailure(reason, downloadUrl) {
40
+ console.error(`\nmz-dev: postinstall failed — ${reason}`);
41
+ if (downloadUrl) {
42
+ console.error(
43
+ `mz-dev: you can fetch the binary manually:\n ${downloadUrl}\n` +
44
+ ` and unpack it under:\n ${platformRoot()}\n`,
45
+ );
46
+ }
47
+ // Keep `npm install` green; the shim prints a friendlier error
48
+ // when the user actually tries to run `mz`.
49
+ process.exitCode = 0;
50
+ }
51
+
52
+ let platform;
53
+ try {
54
+ platform = platformDir();
55
+ } catch (e) {
56
+ // Unsupported platform → nothing to install. Exit 0 so installing
57
+ // mz-dev as a transitive dep on an exotic CI doesn't blow up.
58
+ console.warn(`mz-dev: ${e.message}`);
59
+ process.exit(0);
60
+ }
61
+
62
+ const archive = `mz-dev-${platform}.tar.gz`;
63
+ const url = `https://github.com/${REPO}/releases/download/v${pkg.version}/${archive}`;
64
+ const targetDir = platformRoot(platform);
65
+ const finalBinary = binaryPath(platform);
66
+
67
+ if (isCompleteInstall(targetDir, binaryName(), pkg.version)) {
68
+ // Already provisioned (npm cache hit, manual download, etc.).
69
+ // Make sure the perm bit is right just in case.
70
+ if (process.platform !== 'win32') fs.chmodSync(finalBinary, 0o755);
71
+ process.exit(0);
72
+ }
73
+
74
+ (async () => {
75
+ console.log(`mz-dev: downloading ${archive} (v${pkg.version})`);
76
+ let temporary;
77
+ try {
78
+ temporary = fs.mkdtempSync(path.join(os.tmpdir(), 'mz-download-'));
79
+ const verified = await verifiedArchive(url.slice(0, url.lastIndexOf('/')), archive, temporary);
80
+ const backup = await installBundle({
81
+ target: targetDir, binary: binaryName(), version: pkg.version,
82
+ extract: directory => extractArchive(verified, directory, `mz-dev-${platform}`),
83
+ });
84
+ if (backup) console.log(`mz-dev: previous installation retained at ${backup}`);
85
+ } catch (err) {
86
+ logFailure(`download/verification/extraction failed (${err.message})`, url);
87
+ return;
88
+ } finally {
89
+ if (temporary) fs.rmSync(temporary, { recursive: true, force: true });
90
+ }
91
+
92
+ if (!fs.existsSync(finalBinary)) {
93
+ logFailure(
94
+ `archive did not contain bin/${binaryName()} for ${platform}`,
95
+ url,
96
+ );
97
+ return;
98
+ }
99
+ if (process.platform !== 'win32') fs.chmodSync(finalBinary, 0o755);
100
+ console.log(`mz-dev: installed ${platform} binary`);
101
+ })();
@@ -0,0 +1,125 @@
1
+ 'use strict';
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+ const { createHash } = require('node:crypto');
5
+
6
+ function digestFile(filename) {
7
+ const descriptor = fs.openSync(filename, 'r');
8
+ try {
9
+ const hash = createHash('sha256');
10
+ const buffer = Buffer.allocUnsafe(64 * 1024);
11
+ let length;
12
+ while ((length = fs.readSync(descriptor, buffer, 0, buffer.length, null)) > 0) {
13
+ hash.update(buffer.subarray(0, length));
14
+ }
15
+ return hash.digest('hex');
16
+ } finally {
17
+ fs.closeSync(descriptor);
18
+ }
19
+ }
20
+
21
+ function requiredAssets(binary) {
22
+ return [`bin/${binary}`, ...(process.platform === 'win32' ? [] : ['bin/mz-check-supervisor']),
23
+ 'ui/web/index.html', 'ui/web/main.dart.js',
24
+ 'packages/mz_client/pubspec.yaml', 'packages/mz_client/lib/mz_client.dart',
25
+ 'packages/mz_protocol/pubspec.yaml', 'packages/mz_protocol/lib/mz_protocol.dart'];
26
+ }
27
+
28
+ function requiredInventory(directory, binary) {
29
+ return requiredAssets(binary).map(relative => [relative,
30
+ digestFile(path.join(directory, relative))]);
31
+ }
32
+
33
+ // Inventory is derived from regular files, never paths supplied by the marker.
34
+ // This detects accidental damage; it does not authenticate against a local
35
+ // attacker who can rewrite both the libraries and installation marker.
36
+ function nativeInventory(directory) {
37
+ const root = path.join(directory, 'lib');
38
+ if (!fs.existsSync(root)) return [];
39
+ const result = [];
40
+ function visit(relative) {
41
+ const absolute = path.join(root, relative);
42
+ const stat = fs.lstatSync(absolute);
43
+ if (stat.isDirectory()) {
44
+ for (const name of fs.readdirSync(absolute).sort()) {
45
+ visit(relative ? `${relative}/${name}` : name);
46
+ }
47
+ } else if (stat.isFile() && stat.size > 0) {
48
+ result.push([relative, digestFile(absolute)]);
49
+ } else throw new Error('Invalid native library asset');
50
+ }
51
+ visit('');
52
+ return result;
53
+ }
54
+
55
+ function validateBundle(directory, binary) {
56
+ function inspect(entry) {
57
+ const stat = fs.lstatSync(entry);
58
+ if (stat.isDirectory()) {
59
+ for (const child of fs.readdirSync(entry)) inspect(path.join(entry, child));
60
+ } else if (!stat.isFile()) throw new Error('Bundle contains a non-regular entry');
61
+ }
62
+ inspect(directory);
63
+ for (const relative of requiredAssets(binary)) {
64
+ const stat = fs.lstatSync(path.join(directory, relative));
65
+ if (!stat.isFile() || stat.size === 0) throw new Error(`Missing bundle asset: ${relative}`);
66
+ }
67
+ if (process.platform !== 'win32') {
68
+ fs.accessSync(path.join(directory, 'bin/mz-check-supervisor'), fs.constants.X_OK);
69
+ }
70
+ }
71
+
72
+ function isCompleteInstall(directory, binary, version) {
73
+ try {
74
+ const marker = JSON.parse(fs.readFileSync(path.join(directory, '.mz-install.json'), 'utf8'));
75
+ if (marker.version !== version || marker.format !== 2) return false;
76
+ validateBundle(directory, binary);
77
+ if (process.platform !== 'win32') {
78
+ fs.accessSync(path.join(directory, 'bin', binary), fs.constants.X_OK);
79
+ }
80
+ return JSON.stringify(marker.nativeAssets) === JSON.stringify(nativeInventory(directory)) &&
81
+ JSON.stringify(marker.requiredAssets) === JSON.stringify(requiredInventory(directory, binary));
82
+ } catch (_) { return false; }
83
+ }
84
+
85
+ async function installBundle({ target, binary, version, extract, rename = fs.renameSync }) {
86
+ fs.mkdirSync(path.dirname(target), { recursive: true });
87
+ const lock = `${target}.install-lock`;
88
+ fs.mkdirSync(lock); // Refuse a concurrent or interrupted installation.
89
+ let transaction;
90
+ try {
91
+ if (fs.existsSync(target) && !fs.lstatSync(target).isDirectory()) {
92
+ throw new Error('Installation target must be a regular directory');
93
+ }
94
+ transaction = fs.mkdtempSync(path.join(path.dirname(target), '.mz-install-'));
95
+ const staged = path.join(transaction, 'new');
96
+ const previous = path.join(transaction, 'previous');
97
+ fs.mkdirSync(staged);
98
+ await extract(staged);
99
+ validateBundle(staged, binary);
100
+ if (process.platform !== 'win32') fs.chmodSync(path.join(staged, 'bin', binary), 0o755);
101
+ fs.writeFileSync(path.join(staged, '.mz-install.json'), JSON.stringify({
102
+ format: 2, version, nativeAssets: nativeInventory(staged),
103
+ requiredAssets: requiredInventory(staged, binary),
104
+ }));
105
+ const hadPrevious = fs.existsSync(target);
106
+ if (hadPrevious) rename(target, previous);
107
+ try {
108
+ rename(staged, target);
109
+ } catch (error) {
110
+ if (hadPrevious) {
111
+ try { rename(previous, target); }
112
+ catch (_) { throw new Error(`Installation failed; previous files retained at ${previous}`); }
113
+ }
114
+ throw error;
115
+ }
116
+ // Retain previous files rather than destroying a manual install or recovery data.
117
+ return hadPrevious ? previous : null;
118
+ } finally {
119
+ if (transaction && !fs.existsSync(path.join(transaction, 'previous'))) {
120
+ fs.rmSync(transaction, { recursive: true, force: true });
121
+ }
122
+ fs.rmdirSync(lock);
123
+ }
124
+ }
125
+ module.exports = { validateBundle, isCompleteInstall, installBundle };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Maps the running process to a {platform, binary, archive} triple.
3
+ *
4
+ * Single source of truth for "which binary do we run / download".
5
+ * Used by both `bin/mz.js` (to exec) and `scripts/install.js` (to
6
+ * download). Keeping the map here means adding a new platform is a
7
+ * one-line edit.
8
+ */
9
+ 'use strict';
10
+
11
+ const path = require('path');
12
+
13
+ const PLATFORM_MAP = {
14
+ 'darwin:arm64': 'darwin-arm64',
15
+ 'darwin:x64': 'darwin-x64',
16
+ 'linux:x64': 'linux-x64',
17
+ 'linux:arm64': 'linux-arm64',
18
+ 'win32:x64': 'windows-x64',
19
+ };
20
+
21
+ function platformDir() {
22
+ const key = `${process.platform}:${process.arch}`;
23
+ const dir = PLATFORM_MAP[key];
24
+ if (!dir) {
25
+ throw new Error(
26
+ `mz-dev does not have a prebuilt binary for ${key}. ` +
27
+ `Supported: ${Object.keys(PLATFORM_MAP).join(', ')}. ` +
28
+ `File an issue at https://github.com/koiralapankaj7/mz_dev/issues.`,
29
+ );
30
+ }
31
+ return dir;
32
+ }
33
+
34
+ function binaryName() {
35
+ return process.platform === 'win32' ? 'mz.exe' : 'mz';
36
+ }
37
+
38
+ function packageRoot() {
39
+ return path.resolve(__dirname, '..');
40
+ }
41
+
42
+ function platformRoot(platform = platformDir()) {
43
+ return path.join(packageRoot(), 'platform', platform);
44
+ }
45
+
46
+ function binaryPath(platform = platformDir()) {
47
+ return path.join(platformRoot(platform), 'bin', binaryName());
48
+ }
49
+
50
+ function uiDir(platform = platformDir()) {
51
+ return path.join(platformRoot(platform), 'ui', 'web');
52
+ }
53
+
54
+ module.exports = {
55
+ PLATFORM_MAP,
56
+ platformDir,
57
+ binaryName,
58
+ packageRoot,
59
+ platformRoot,
60
+ binaryPath,
61
+ uiDir,
62
+ };
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Sanity-check the resolver without invoking the binary.
4
+ *
5
+ * node release/npm/scripts/smoke.js
6
+ *
7
+ * Prints the platform/binary/UI paths the shim would use on this
8
+ * machine. Useful when adding a new platform to verify the
9
+ * `PLATFORM_MAP` entry resolves correctly before tagging a
10
+ * release.
11
+ */
12
+ 'use strict';
13
+
14
+ const {
15
+ platformDir,
16
+ binaryName,
17
+ binaryPath,
18
+ uiDir,
19
+ } = require('./resolve');
20
+
21
+ console.log(JSON.stringify({
22
+ platform: platformDir(),
23
+ binaryName: binaryName(),
24
+ binaryPath: binaryPath(),
25
+ uiDir: uiDir(),
26
+ }, null, 2));
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Best-effort cleanup of the downloaded `platform/` tree on
4
+ * `npm uninstall`. Quiet — failure here is harmless because the
5
+ * whole package directory is about to be removed anyway.
6
+ */
7
+ 'use strict';
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ const platformRoot = path.join(__dirname, '..', 'platform');
13
+ try {
14
+ fs.rmSync(platformRoot, { recursive: true, force: true });
15
+ } catch (_) {
16
+ /* ignore */
17
+ }