mz-dev 0.1.0-beta.0 → 0.1.0-beta.14
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 +38 -26
- package/bin/mz.js +5 -6
- package/package.json +5 -17
- package/scripts/resolve.js +38 -17
- package/scripts/download.js +0 -73
- package/scripts/extract.js +0 -34
- package/scripts/install.js +0 -101
- package/scripts/installation.js +0 -125
- package/scripts/smoke.js +0 -26
- package/scripts/uninstall.js +0 -17
package/README.md
CHANGED
|
@@ -1,45 +1,57 @@
|
|
|
1
1
|
# mz-dev
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A local-first developer workbench that runs your coding agents the way you
|
|
4
|
+
would. It sees your app's traffic and runtime, knows your tickets and designs,
|
|
5
|
+
and works to your standards.
|
|
4
6
|
|
|
5
7
|
```bash
|
|
6
|
-
# One-shot
|
|
7
|
-
npx mz-dev start
|
|
8
|
+
# One-shot (prereleases are published under the `beta` dist-tag)
|
|
9
|
+
npx mz-dev@beta start
|
|
8
10
|
|
|
9
11
|
# Persistent install
|
|
10
|
-
npm install -g mz-dev
|
|
12
|
+
npm install -g mz-dev@beta
|
|
11
13
|
mz start
|
|
12
14
|
```
|
|
13
15
|
|
|
16
|
+
Pin an exact version (`mz-dev@0.1.0-beta.N`) when reporting issues.
|
|
17
|
+
|
|
14
18
|
## What you get
|
|
15
19
|
|
|
16
|
-
- A local HTTP
|
|
17
|
-
|
|
18
|
-
- A web dashboard at `http://localhost:8889`
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
- A local HTTP(S) proxy and traffic inspector, with intercept rules and a
|
|
21
|
+
mock server.
|
|
22
|
+
- A web dashboard at `http://localhost:8889` for traffic, the API client,
|
|
23
|
+
environments, credentials and Vault, attached Flutter apps and the Jira
|
|
24
|
+
workspace.
|
|
25
|
+
- An MCP server (`mz mcp`, or `http://127.0.0.1:8892/mcp` while `mz start`
|
|
26
|
+
runs) so coding agents can use the same tools.
|
|
21
27
|
|
|
22
28
|
## Supported platforms
|
|
23
29
|
|
|
24
|
-
`darwin-arm64
|
|
30
|
+
macOS on Apple silicon (`darwin-arm64`). Node.js 18 or newer runs the `mz`
|
|
31
|
+
launcher.
|
|
32
|
+
|
|
33
|
+
Other platforms aren't published yet; on them `mz` says so and exits.
|
|
34
|
+
|
|
35
|
+
## How the install works
|
|
25
36
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
`mz-dev` is a small launcher. The binary, the dashboard and the Dart packages
|
|
38
|
+
for your apps (`mz_client`) come in a separate package for your platform,
|
|
39
|
+
`mz-dev-darwin-arm64`, which npm installs automatically as an optional
|
|
40
|
+
dependency. Nothing is downloaded when the package installs, and there are no
|
|
41
|
+
install scripts.
|
|
29
42
|
|
|
30
|
-
|
|
43
|
+
If you install with optional dependencies turned off (`--omit=optional`),
|
|
44
|
+
`mz` tells you to reinstall without that flag.
|
|
31
45
|
|
|
32
|
-
|
|
33
|
-
|
|
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.
|
|
46
|
+
`mz update --check` tells you when a newer version is published on your
|
|
47
|
+
channel (set `MZ_NO_UPDATE_CHECK=1` to turn update checks off).
|
|
42
48
|
|
|
43
|
-
##
|
|
49
|
+
## First launch on macOS
|
|
44
50
|
|
|
45
|
-
|
|
51
|
+
Beta binaries are not yet signed by Apple. Files that npm installs are not
|
|
52
|
+
quarantined, so macOS normally runs `mz` without asking. If it ever reports
|
|
53
|
+
that `mz` "cannot be opened", clear the quarantine flag:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
xattr -d com.apple.quarantine "$(npm root -g)/mz-dev-darwin-arm64/bin/mz"
|
|
57
|
+
```
|
package/bin/mz.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Thin shim that execs the
|
|
4
|
-
* `
|
|
3
|
+
* Thin shim that execs the `mz` binary from the platform package npm
|
|
4
|
+
* installed alongside (`mz-dev-darwin-arm64`, …; see scripts/resolve.js).
|
|
5
5
|
*
|
|
6
6
|
* Sets `MZ_DEV_UI_DIR` so the binary finds the bundled Flutter web
|
|
7
|
-
* assets
|
|
8
|
-
*
|
|
7
|
+
* assets in that package (`ui/web/`) regardless of the user's current
|
|
8
|
+
* working directory.
|
|
9
9
|
*
|
|
10
10
|
* Forwards stdio + signals so the binary feels native: Ctrl+C, pipes,
|
|
11
11
|
* exit codes all behave as if the user invoked the binary directly.
|
|
@@ -27,8 +27,7 @@ try {
|
|
|
27
27
|
if (!fs.existsSync(resolvedBinary)) {
|
|
28
28
|
console.error(
|
|
29
29
|
`mz-dev: binary missing at ${resolvedBinary}. ` +
|
|
30
|
-
|
|
31
|
-
`\`npm rebuild mz-dev\` or reinstall the package.`,
|
|
30
|
+
'Reinstall the package: npm install -g mz-dev',
|
|
32
31
|
);
|
|
33
32
|
process.exit(1);
|
|
34
33
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mz-dev",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.0-beta.14",
|
|
4
|
+
"description": "Local-first developer workbench that runs your coding agents the way you would",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"flutter",
|
|
7
7
|
"network",
|
|
@@ -12,14 +12,6 @@
|
|
|
12
12
|
"intercept",
|
|
13
13
|
"cli"
|
|
14
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
15
|
"license": "MIT",
|
|
24
16
|
"author": "Pankaj Koirala",
|
|
25
17
|
"bin": {
|
|
@@ -28,18 +20,14 @@
|
|
|
28
20
|
},
|
|
29
21
|
"files": [
|
|
30
22
|
"bin",
|
|
31
|
-
"scripts",
|
|
23
|
+
"scripts/resolve.js",
|
|
32
24
|
"README.md",
|
|
33
25
|
"LICENSE"
|
|
34
26
|
],
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"preuninstall": "node scripts/uninstall.js"
|
|
27
|
+
"optionalDependencies": {
|
|
28
|
+
"mz-dev-darwin-arm64": "0.1.0-beta.14"
|
|
38
29
|
},
|
|
39
30
|
"engines": {
|
|
40
31
|
"node": ">=18"
|
|
41
|
-
},
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"tar": "^7.5.22"
|
|
44
32
|
}
|
|
45
33
|
}
|
package/scripts/resolve.js
CHANGED
|
@@ -1,36 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Finds the platform package that carries the `mz` binary.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* one
|
|
4
|
+
* `mz-dev` itself is a small launcher. Each supported platform's binary,
|
|
5
|
+
* dashboard and companion packages ship as their own npm package
|
|
6
|
+
* (`mz-dev-darwin-arm64`, …) listed in `optionalDependencies`; npm installs
|
|
7
|
+
* only the one whose `os`/`cpu` match the machine. Nothing is downloaded at
|
|
8
|
+
* install time.
|
|
8
9
|
*/
|
|
9
10
|
'use strict';
|
|
10
11
|
|
|
12
|
+
const fs = require('fs');
|
|
11
13
|
const path = require('path');
|
|
12
14
|
|
|
15
|
+
// Platforms with a published package. Adding one: build it with
|
|
16
|
+
// `release/manual-release.sh npm`, publish it, and list it here and in
|
|
17
|
+
// package.json `optionalDependencies`.
|
|
13
18
|
const PLATFORM_MAP = {
|
|
14
19
|
'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
|
};
|
|
20
21
|
|
|
21
|
-
function platformDir() {
|
|
22
|
-
const
|
|
23
|
-
const dir = PLATFORM_MAP[key];
|
|
22
|
+
function platformDir(platform = process.platform, arch = process.arch) {
|
|
23
|
+
const dir = PLATFORM_MAP[`${platform}:${arch}`];
|
|
24
24
|
if (!dir) {
|
|
25
25
|
throw new Error(
|
|
26
|
-
`mz-dev
|
|
27
|
-
`
|
|
28
|
-
`File an issue at https://github.com/koiralapankaj7/mz_dev/issues.`,
|
|
26
|
+
`mz-dev has no prebuilt binary for ${platform}-${arch} yet. ` +
|
|
27
|
+
`Available: ${Object.values(PLATFORM_MAP).join(', ')}.`,
|
|
29
28
|
);
|
|
30
29
|
}
|
|
31
30
|
return dir;
|
|
32
31
|
}
|
|
33
32
|
|
|
33
|
+
function platformPackageName(platform = platformDir()) {
|
|
34
|
+
return `mz-dev-${platform}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
34
37
|
function binaryName() {
|
|
35
38
|
return process.platform === 'win32' ? 'mz.exe' : 'mz';
|
|
36
39
|
}
|
|
@@ -39,8 +42,23 @@ function packageRoot() {
|
|
|
39
42
|
return path.resolve(__dirname, '..');
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
/**
|
|
46
|
+
* The installed platform package's directory. Resolved from this package so
|
|
47
|
+
* it works for global installs, `npx` and local `node_modules` alike.
|
|
48
|
+
*/
|
|
42
49
|
function platformRoot(platform = platformDir()) {
|
|
43
|
-
|
|
50
|
+
const name = platformPackageName(platform);
|
|
51
|
+
try {
|
|
52
|
+
return path.dirname(
|
|
53
|
+
require.resolve(`${name}/package.json`, { paths: [packageRoot()] }),
|
|
54
|
+
);
|
|
55
|
+
} catch (_) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`mz-dev: ${name} is not installed. npm skips it when optional ` +
|
|
58
|
+
'dependencies are disabled (--omit=optional, --no-optional). ' +
|
|
59
|
+
`Reinstall with them enabled: npm install -g mz-dev`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
44
62
|
}
|
|
45
63
|
|
|
46
64
|
function binaryPath(platform = platformDir()) {
|
|
@@ -48,12 +66,15 @@ function binaryPath(platform = platformDir()) {
|
|
|
48
66
|
}
|
|
49
67
|
|
|
50
68
|
function uiDir(platform = platformDir()) {
|
|
51
|
-
|
|
69
|
+
const root = platformRoot(platform);
|
|
70
|
+
const web = path.join(root, 'ui', 'web');
|
|
71
|
+
return fs.existsSync(web) ? web : path.join(root, 'ui');
|
|
52
72
|
}
|
|
53
73
|
|
|
54
74
|
module.exports = {
|
|
55
75
|
PLATFORM_MAP,
|
|
56
76
|
platformDir,
|
|
77
|
+
platformPackageName,
|
|
57
78
|
binaryName,
|
|
58
79
|
packageRoot,
|
|
59
80
|
platformRoot,
|
package/scripts/download.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
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 };
|
package/scripts/extract.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
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 };
|
package/scripts/install.js
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
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
|
-
})();
|
package/scripts/installation.js
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
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 };
|
package/scripts/smoke.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
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));
|
package/scripts/uninstall.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
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
|
-
}
|