@deeeed/metamask-harness 0.25.0 → 0.26.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/CHANGELOG.md +19 -0
- package/adapters/extension/build-lavamoat.sh +57 -0
- package/adapters/extension/configure-runtime-manifest.cjs +58 -0
- package/adapters/extension/inject.mjs +3 -0
- package/adapters/extension/launch-browser.cjs +7 -0
- package/adapters/extension/lib/product-config.cjs +116 -0
- package/adapters/extension/lib/product-config.d.cts +16 -0
- package/adapters/extension/live.sh +19 -4
- package/adapters/extension/reattach.sh +1 -0
- package/adapters/extension/sync-webpack-dist.cjs +17 -0
- package/adapters/manifest.json +25 -1
- package/adapters/shared/ensure-runner-deps.sh +6 -55
- package/adapters/shared/runner-deps-ready.mjs +70 -0
- package/dist/adapters/extension/product-config.js +20 -71
- package/dist/adapters/extension/runtime-decision.js +24 -0
- package/dist/commands/launch/extension.js +2 -2
- package/dist/commands/parse-args.js +1 -0
- package/dist/commands/runtime-launch.js +15 -8
- package/library/actions/extension/perps/perps.mjs +1 -3
- package/library/actions/shared/analytics/assert_events.mjs +69 -11
- package/library/manifests/extension.action-manifest.json +1 -1
- package/library/manifests/mobile.action-manifest.json +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.26.0 - 2026-07-31
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Allow analytics event property assertions to use `$exists`, `$type`, and `$gt` predicates for dynamic runtime values.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Avoid direct `globalThis.chrome` access when reading Extension Perps runtime state, so state preparation works with LavaMoat scuttling enabled.
|
|
14
|
+
- Add a harness-owned non-test LavaMoat build mode for production-like Extension proof, while continuing to configure runtime snapshots with the validated Infura project ID. This avoids the placeholder credentials embedded by Extension `--test` builds.
|
|
15
|
+
- Keep macOS awake for the lifetime of a harness-launched Extension browser so long live recipes are not invalidated by display-lock compositor suspension.
|
|
16
|
+
- Retry post-interaction CDP settlement in an isolated world when navigation replaces the page context.
|
|
17
|
+
|
|
18
|
+
## 0.25.1 - 2026-07-30
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Source-checkout dependency readiness works under restrictive caller umasks and uses an isolated npm cache instead of inheriting a broken global cache.
|
|
23
|
+
|
|
5
24
|
## 0.25.0 - 2026-07-30
|
|
6
25
|
|
|
7
26
|
### Added
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# build-lavamoat.sh — one-shot production-like Extension build for live proof
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
TARGET="$PWD"
|
|
6
|
+
RUNTIME_DIR="temp/recipe/runtime"
|
|
7
|
+
while [ "$#" -gt 0 ]; do
|
|
8
|
+
case "$1" in
|
|
9
|
+
--target) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; TARGET="$2"; shift 2 ;;
|
|
10
|
+
--runtime-dir) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; RUNTIME_DIR="$2"; shift 2 ;;
|
|
11
|
+
-h|--help) echo "Usage: build-lavamoat.sh [--target <metamask-extension>] [--runtime-dir <relative path>]"; exit 0 ;;
|
|
12
|
+
*) echo "Unknown arg: $1" >&2; exit 2 ;;
|
|
13
|
+
esac
|
|
14
|
+
done
|
|
15
|
+
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
for _arn in "$SCRIPT_DIR/lib/activate-repo-node.sh" "$SCRIPT_DIR/../shared/activate-repo-node.sh"; do
|
|
18
|
+
[ -f "$_arn" ] && { # shellcheck disable=SC1090
|
|
19
|
+
. "$_arn"
|
|
20
|
+
break
|
|
21
|
+
}
|
|
22
|
+
done
|
|
23
|
+
unset _arn
|
|
24
|
+
|
|
25
|
+
TARGET="$(cd "$TARGET" && pwd)"
|
|
26
|
+
cd "$TARGET"
|
|
27
|
+
if command -v require_repo_node >/dev/null 2>&1; then
|
|
28
|
+
require_repo_node "$TARGET"
|
|
29
|
+
elif command -v activate_repo_node >/dev/null 2>&1; then
|
|
30
|
+
activate_repo_node "$TARGET"
|
|
31
|
+
else
|
|
32
|
+
echo "build-lavamoat: activate-repo-node.sh missing from harness; run: mm-harness install" >&2
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
for pid_file in "$RUNTIME_DIR/webpack.pid" "$RUNTIME_DIR/recipe-harness-webpack.pid"; do
|
|
37
|
+
[ -f "$pid_file" ] || continue
|
|
38
|
+
pid="$(cat "$pid_file" 2>/dev/null || true)"
|
|
39
|
+
if [ -n "$pid" ] && kill -0 "$pid" >/dev/null 2>&1; then
|
|
40
|
+
echo "build-lavamoat: a webpack watcher already owns this checkout (pid $pid). Next: stop the slot watcher and retry." >&2
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
rm -f "$pid_file"
|
|
44
|
+
done
|
|
45
|
+
|
|
46
|
+
# A one-shot build has no watch health to report. Remove logs left by a dead
|
|
47
|
+
# watcher so readiness does not mistake an old failure for the current build.
|
|
48
|
+
rm -f "$RUNTIME_DIR/webpack.log" "$RUNTIME_DIR/recipe-harness-webpack.log"
|
|
49
|
+
|
|
50
|
+
echo "[recipe-harness] clean non-test LavaMoat build"
|
|
51
|
+
rm -rf node_modules/.cache/webpack
|
|
52
|
+
yarn webpack:lavamoat:build
|
|
53
|
+
test -f dist/chrome/manifest.json || {
|
|
54
|
+
echo "build-lavamoat: build completed without dist/chrome/manifest.json" >&2
|
|
55
|
+
exit 1
|
|
56
|
+
}
|
|
57
|
+
echo "[recipe-harness] non-test LavaMoat build completed"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const {
|
|
7
|
+
resolveExtensionInfuraProjectId,
|
|
8
|
+
} = require('./lib/product-config.cjs');
|
|
9
|
+
|
|
10
|
+
const usage =
|
|
11
|
+
'Usage: configure-runtime-manifest.cjs --target <checkout> --manifest <runtime manifest>';
|
|
12
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
13
|
+
process.stdout.write(`${usage}\n`);
|
|
14
|
+
process.exit(0);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseArgs(argv) {
|
|
18
|
+
const args = {};
|
|
19
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
20
|
+
const flag = argv[index];
|
|
21
|
+
if (!flag.startsWith('--') || index + 1 >= argv.length) {
|
|
22
|
+
throw new Error(usage);
|
|
23
|
+
}
|
|
24
|
+
args[flag.slice(2)] = argv[index + 1];
|
|
25
|
+
index += 1;
|
|
26
|
+
}
|
|
27
|
+
return args;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const args = parseArgs(process.argv.slice(2));
|
|
31
|
+
if (!args.target || !args.manifest) {
|
|
32
|
+
throw new Error(usage);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const target = path.resolve(args.target);
|
|
36
|
+
const manifestPath = path.resolve(args.manifest);
|
|
37
|
+
const resolution = resolveExtensionInfuraProjectId(target, process.env);
|
|
38
|
+
if (resolution.kind === 'not-required') {
|
|
39
|
+
process.stdout.write(
|
|
40
|
+
'Runtime manifest credentials are not required by this checkout.\n',
|
|
41
|
+
);
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
if (resolution.kind !== 'configured') {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Extension runtime has no usable INFURA_PROJECT_ID for ${target}. Next: run mm-harness doctor --adapter extension --target ${JSON.stringify(target)} --fix.`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
51
|
+
manifest._flags ??= {};
|
|
52
|
+
manifest._flags.testing ??= {};
|
|
53
|
+
manifest._flags.testing.infuraProjectId = resolution.value;
|
|
54
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, {
|
|
55
|
+
mode: 0o600,
|
|
56
|
+
});
|
|
57
|
+
fs.chmodSync(manifestPath, 0o600);
|
|
58
|
+
process.stdout.write('Configured runtime Infura credentials through manifest flags.\n');
|
|
@@ -99,8 +99,10 @@ copyFile(path.join(runnerDir, 'adapters/extension/console-tail.mjs'), path.join(
|
|
|
99
99
|
copyFile(path.join(runnerDir, 'adapters/extension/launch.sh'), path.join(harnessDir, 'scripts/launch.sh'));
|
|
100
100
|
copyFile(path.join(runnerDir, 'adapters/extension/live.sh'), path.join(harnessDir, 'scripts/live.sh'));
|
|
101
101
|
copyFile(path.join(runnerDir, 'adapters/extension/start-watch.sh'), path.join(harnessDir, 'scripts/start-watch.sh'));
|
|
102
|
+
copyFile(path.join(runnerDir, 'adapters/extension/build-lavamoat.sh'), path.join(harnessDir, 'scripts/build-lavamoat.sh'));
|
|
102
103
|
copyFile(path.join(runnerDir, 'adapters/extension/launch-webpack.cjs'), path.join(harnessDir, 'scripts/launch-webpack.cjs'));
|
|
103
104
|
copyFile(path.join(runnerDir, 'adapters/extension/sync-webpack-dist.cjs'), path.join(harnessDir, 'scripts/sync-webpack-dist.cjs'));
|
|
105
|
+
copyFile(path.join(runnerDir, 'adapters/extension/configure-runtime-manifest.cjs'), path.join(harnessDir, 'scripts/configure-runtime-manifest.cjs'));
|
|
104
106
|
copyFile(path.join(runnerDir, 'adapters/extension/stamp-runtime-title.cjs'), path.join(harnessDir, 'scripts/stamp-runtime-title.cjs'));
|
|
105
107
|
copyFile(path.join(runnerDir, 'adapters/extension/stop-viewers.sh'), path.join(harnessDir, 'scripts/stop-viewers.sh'));
|
|
106
108
|
copyFile(path.join(runnerDir, 'adapters/extension/snapshot-dist.sh'), path.join(harnessDir, 'scripts/snapshot-dist.sh'));
|
|
@@ -127,6 +129,7 @@ copyFile(path.join(runnerDir, 'adapters/extension/lib/extension-id.cjs'), path.j
|
|
|
127
129
|
copyFile(path.join(runnerDir, 'adapters/extension/lib/slot-title.cjs'), path.join(harnessDir, 'scripts/lib/slot-title.cjs'));
|
|
128
130
|
copyFile(path.join(runnerDir, 'adapters/extension/lib/chrome-args.cjs'), path.join(harnessDir, 'scripts/lib/chrome-args.cjs'));
|
|
129
131
|
copyFile(path.join(runnerDir, 'adapters/extension/lib/macos-focus.cjs'), path.join(harnessDir, 'scripts/lib/macos-focus.cjs'));
|
|
132
|
+
copyFile(path.join(runnerDir, 'adapters/extension/lib/product-config.cjs'), path.join(harnessDir, 'scripts/lib/product-config.cjs'));
|
|
130
133
|
makeExecutableTree(path.join(harnessDir, 'scripts'));
|
|
131
134
|
fs.writeFileSync(path.join(harnessDir, 'installed-scripts.sha256'), `${dirContentHash(path.join(harnessDir, 'scripts'))}\n`);
|
|
132
135
|
|
|
@@ -144,6 +144,13 @@ try {
|
|
|
144
144
|
fs.closeSync(logFd);
|
|
145
145
|
}
|
|
146
146
|
fs.writeFileSync(args['chrome-pid'], `${browserPid}\n`);
|
|
147
|
+
if (process.platform === 'darwin') {
|
|
148
|
+
const wake = spawn('caffeinate', ['-dimsu', '-w', String(browserPid)], {
|
|
149
|
+
detached: true,
|
|
150
|
+
stdio: 'ignore',
|
|
151
|
+
});
|
|
152
|
+
wake.unref();
|
|
153
|
+
}
|
|
147
154
|
if (process.env.MM_HARNESS_FOCUS_BROWSER !== '1') {
|
|
148
155
|
restoreMacFrontmostProcess(previousFrontmostPid);
|
|
149
156
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
function isConfiguredInfuraId(value, placeholder) {
|
|
7
|
+
return (
|
|
8
|
+
value.length > 0 &&
|
|
9
|
+
value !== placeholder &&
|
|
10
|
+
value !== 'true' &&
|
|
11
|
+
value !== 'false' &&
|
|
12
|
+
value !== 'null'
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readConfigSnapshot(file) {
|
|
17
|
+
let descriptor;
|
|
18
|
+
try {
|
|
19
|
+
descriptor = fs.openSync(
|
|
20
|
+
file,
|
|
21
|
+
fs.constants.O_RDONLY |
|
|
22
|
+
fs.constants.O_NOFOLLOW |
|
|
23
|
+
fs.constants.O_NONBLOCK,
|
|
24
|
+
);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (error.code === 'ENOENT') return { kind: 'missing' };
|
|
27
|
+
if (error.code === 'ELOOP') {
|
|
28
|
+
return { kind: 'invalid', reason: 'symlink' };
|
|
29
|
+
}
|
|
30
|
+
return { kind: 'invalid', reason: 'unreadable' };
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
if (!fs.fstatSync(descriptor).isFile()) {
|
|
34
|
+
return { kind: 'invalid', reason: 'not-regular' };
|
|
35
|
+
}
|
|
36
|
+
return { kind: 'file', text: fs.readFileSync(descriptor, 'utf8') };
|
|
37
|
+
} finally {
|
|
38
|
+
fs.closeSync(descriptor);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readInfuraProjectId(text) {
|
|
43
|
+
const lines = text.replace(/\r\n?/gu, '\n');
|
|
44
|
+
const pattern =
|
|
45
|
+
/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gmu;
|
|
46
|
+
let result;
|
|
47
|
+
for (const match of lines.matchAll(pattern)) {
|
|
48
|
+
if (match[1] !== 'INFURA_PROJECT_ID') continue;
|
|
49
|
+
let value = (match[2] ?? '').trim();
|
|
50
|
+
const quote = value[0];
|
|
51
|
+
value = value.replace(/^(['"`])([\s\S]*)\1$/u, '$2');
|
|
52
|
+
if (quote === '"') {
|
|
53
|
+
value = value.replace(/\\n/gu, '\n').replace(/\\r/gu, '\r');
|
|
54
|
+
}
|
|
55
|
+
result = value;
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveExtensionInfuraProjectId(target, environment = process.env) {
|
|
61
|
+
const template = readConfigSnapshot(path.join(target, '.metamaskrc.dist'));
|
|
62
|
+
if (template.kind === 'missing') return { kind: 'not-required' };
|
|
63
|
+
if (template.kind === 'invalid') {
|
|
64
|
+
return {
|
|
65
|
+
kind: 'invalid-file',
|
|
66
|
+
name: '.metamaskrc.dist',
|
|
67
|
+
reason: template.reason,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const placeholder = readInfuraProjectId(template.text) ?? '';
|
|
71
|
+
|
|
72
|
+
if (environment.INFURA_PROJECT_ID !== undefined) {
|
|
73
|
+
const value = environment.INFURA_PROJECT_ID;
|
|
74
|
+
return isConfiguredInfuraId(value, placeholder)
|
|
75
|
+
? { kind: 'configured', source: 'environment', value }
|
|
76
|
+
: { kind: 'invalid-value', source: 'environment' };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const production = readConfigSnapshot(path.join(target, '.metamaskprodrc'));
|
|
80
|
+
if (production.kind === 'invalid') {
|
|
81
|
+
return {
|
|
82
|
+
kind: 'invalid-file',
|
|
83
|
+
name: '.metamaskprodrc',
|
|
84
|
+
reason: production.reason,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (production.kind === 'file') {
|
|
88
|
+
const value = readInfuraProjectId(production.text);
|
|
89
|
+
if (value !== undefined) {
|
|
90
|
+
return isConfiguredInfuraId(value, placeholder)
|
|
91
|
+
? { kind: 'configured', source: '.metamaskprodrc', value }
|
|
92
|
+
: { kind: 'invalid-value', source: '.metamaskprodrc' };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const config = readConfigSnapshot(path.join(target, '.metamaskrc'));
|
|
97
|
+
if (config.kind === 'missing') {
|
|
98
|
+
return { kind: 'missing', name: '.metamaskrc' };
|
|
99
|
+
}
|
|
100
|
+
if (config.kind === 'invalid') {
|
|
101
|
+
return {
|
|
102
|
+
kind: 'invalid-file',
|
|
103
|
+
name: '.metamaskrc',
|
|
104
|
+
reason: config.reason,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const value = readInfuraProjectId(config.text) ?? '';
|
|
108
|
+
return isConfiguredInfuraId(value, placeholder)
|
|
109
|
+
? { kind: 'configured', source: '.metamaskrc', value }
|
|
110
|
+
: { kind: 'invalid-value', source: '.metamaskrc' };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
readInfuraProjectId,
|
|
115
|
+
resolveExtensionInfuraProjectId,
|
|
116
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type ConfigName = '.metamaskrc.dist' | '.metamaskprodrc' | '.metamaskrc';
|
|
2
|
+
type ConfigInvalidReason = 'symlink' | 'not-regular' | 'unreadable';
|
|
3
|
+
type ConfigSource = 'environment' | '.metamaskprodrc' | '.metamaskrc';
|
|
4
|
+
|
|
5
|
+
type ExtensionInfuraProjectIdResolution =
|
|
6
|
+
| { kind: 'configured'; source: ConfigSource; value: string }
|
|
7
|
+
| { kind: 'not-required' }
|
|
8
|
+
| { kind: 'invalid-file'; name: ConfigName; reason: ConfigInvalidReason }
|
|
9
|
+
| { kind: 'missing'; name: '.metamaskrc' }
|
|
10
|
+
| { kind: 'invalid-value'; source: ConfigSource };
|
|
11
|
+
|
|
12
|
+
export function readInfuraProjectId(text: string): string | undefined;
|
|
13
|
+
export function resolveExtensionInfuraProjectId(
|
|
14
|
+
target: string,
|
|
15
|
+
environment?: NodeJS.ProcessEnv,
|
|
16
|
+
): ExtensionInfuraProjectIdResolution;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# Inputs (flags / env):
|
|
12
12
|
# --target <metamask-extension> (default $PWD)
|
|
13
13
|
# --cdp-port <port> (optional; inferred from checkout context/pool when omitted)
|
|
14
|
-
# --launch-existing-dist | --start-watch | --prepare-cmd <cmd>
|
|
14
|
+
# --launch-existing-dist | --start-watch | --build-lavamoat | --prepare-cmd <cmd>
|
|
15
15
|
# (env RECIPE_HARNESS_EXTENSION_LAUNCH_CMD)
|
|
16
16
|
# --dist-dir <rel> (default dist/chrome), --chrome-user-data-dir <dir>,
|
|
17
17
|
# --remote-flag <KEY=VARIANT[,KEY=VARIANT...]> (pins manifest _flags into the
|
|
@@ -39,6 +39,7 @@ OUT=""
|
|
|
39
39
|
PREPARE_CMD="${RECIPE_HARNESS_EXTENSION_LAUNCH_CMD:-}"
|
|
40
40
|
LAUNCH_EXISTING_DIST=false
|
|
41
41
|
START_WATCH=false
|
|
42
|
+
BUILD_LAVAMOAT=false
|
|
42
43
|
DIST_DIR="dist/chrome"
|
|
43
44
|
CHROME_USER_DATA_DIR="${CHROME_USER_DATA_DIR:-}"
|
|
44
45
|
REMOTE_FLAGS=""
|
|
@@ -53,14 +54,20 @@ while [ "$#" -gt 0 ]; do
|
|
|
53
54
|
--launch-existing-dist) LAUNCH_EXISTING_DIST=true; shift ;;
|
|
54
55
|
--start-url) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; START_URL="$2"; shift 2 ;;
|
|
55
56
|
--start-watch|--start-test-watch) START_WATCH=true; LAUNCH_EXISTING_DIST=true; shift ;;
|
|
57
|
+
--build-lavamoat) BUILD_LAVAMOAT=true; LAUNCH_EXISTING_DIST=true; shift ;;
|
|
56
58
|
--dist-dir) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; DIST_DIR="$2"; shift 2 ;;
|
|
57
59
|
--chrome-user-data-dir) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; CHROME_USER_DATA_DIR="$2"; shift 2 ;;
|
|
58
60
|
--remote-flag) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; REMOTE_FLAGS="$2"; shift 2 ;;
|
|
59
|
-
-h|--help) echo "Usage: live.sh [--target <metamask-extension>] [--out <recipes-dir>] [--cdp-port <port>] [--launch-existing-dist|--start-watch|--prepare-cmd <cmd>] [--dist-dir dist/chrome] [--remote-flag KEY=VARIANT] [--artifacts-dir <dir>]"; exit 0 ;;
|
|
61
|
+
-h|--help) echo "Usage: live.sh [--target <metamask-extension>] [--out <recipes-dir>] [--cdp-port <port>] [--launch-existing-dist|--start-watch|--build-lavamoat|--prepare-cmd <cmd>] [--dist-dir dist/chrome] [--remote-flag KEY=VARIANT] [--artifacts-dir <dir>]"; exit 0 ;;
|
|
60
62
|
*) echo "Unknown arg: $1" >&2; exit 2 ;;
|
|
61
63
|
esac
|
|
62
64
|
done
|
|
63
65
|
|
|
66
|
+
if $BUILD_LAVAMOAT && $START_WATCH; then
|
|
67
|
+
echo "--build-lavamoat cannot be combined with --start-watch" >&2
|
|
68
|
+
exit 2
|
|
69
|
+
fi
|
|
70
|
+
|
|
64
71
|
case "$CHROME_USER_DATA_DIR" in
|
|
65
72
|
""|/*) ;;
|
|
66
73
|
*) CHROME_USER_DATA_DIR="$INVOCATION_DIR/$CHROME_USER_DATA_DIR" ;;
|
|
@@ -167,7 +174,9 @@ NODE
|
|
|
167
174
|
quoted_profile="$(printf '%q' "$PROFILE_ABS")"
|
|
168
175
|
quoted_seed_fixture="$(printf '%q' "$SCRIPT_DIR/seed-fixture.sh")"
|
|
169
176
|
quoted_start_watch="$(printf '%q' "$SCRIPT_DIR/start-watch.sh")"
|
|
177
|
+
quoted_build_lavamoat="$(printf '%q' "$SCRIPT_DIR/build-lavamoat.sh")"
|
|
170
178
|
quoted_snapshot_dist="$(printf '%q' "$SCRIPT_DIR/snapshot-dist.sh")"
|
|
179
|
+
quoted_configure_manifest="$(printf '%q' "$SCRIPT_DIR/configure-runtime-manifest.cjs")"
|
|
171
180
|
quoted_stamp_title="$(printf '%q' "$SCRIPT_DIR/stamp-runtime-title.cjs")"
|
|
172
181
|
quoted_chrome_launcher="$(printf '%q' "$SCRIPT_DIR/launch-browser.cjs")"
|
|
173
182
|
quoted_fixture_state="$(printf '%q' "$FIXTURE_STATE_ABS")"
|
|
@@ -245,8 +254,12 @@ NODE
|
|
|
245
254
|
if $START_WATCH; then
|
|
246
255
|
prepare_parts+=("bash ${quoted_start_watch} --runtime-dir ${quoted_runtime_dir} --runner-bin ${quoted_runner}")
|
|
247
256
|
fi
|
|
257
|
+
if $BUILD_LAVAMOAT; then
|
|
258
|
+
prepare_parts+=("bash ${quoted_build_lavamoat} --target ${quoted_target} --runtime-dir ${quoted_runtime_dir}")
|
|
259
|
+
fi
|
|
248
260
|
prepare_parts+=("node ${quoted_chrome_launcher} --stop-only 1 --reset-profile 1 --chrome-bin ${quoted_chrome} --profile ${quoted_profile} --cdp-port ${CDP_PORT} --extension-dir ${quoted_runtime_dist} --chrome-log ${quoted_chrome_log} --chrome-pid ${quoted_chrome_pid}")
|
|
249
261
|
prepare_parts+=("bash ${quoted_snapshot_dist} --dist ${quoted_dist} --runtime-dist ${quoted_runtime_dist}")
|
|
262
|
+
prepare_parts+=("node ${quoted_configure_manifest} --target ${quoted_target} --manifest ${quoted_runtime_dist}/manifest.json")
|
|
250
263
|
prepare_parts+=("node ${quoted_stamp_title} --target ${quoted_target} --runtime-dist ${quoted_runtime_dist} --runtime-dir ${quoted_runtime_dir}")
|
|
251
264
|
# Optional A/B feature-flag pinning: patch the ephemeral snapshot manifest so
|
|
252
265
|
# manifest._flags wins over the fetched ClientConfigApi value. No-op unless
|
|
@@ -275,12 +288,13 @@ NODE
|
|
|
275
288
|
fi
|
|
276
289
|
|
|
277
290
|
if [ -n "$REMOTE_FLAGS" ] && [ "$REMOTE_FLAGS_APPLIED" != "true" ]; then
|
|
278
|
-
echo "[recipe-harness] WARN: --remote-flag '$REMOTE_FLAGS' had no effect: flag pinning only applies to the snapshot launch path (--launch-existing-dist/--start-watch without a custom --prepare-cmd)." >&2
|
|
291
|
+
echo "[recipe-harness] WARN: --remote-flag '$REMOTE_FLAGS' had no effect: flag pinning only applies to the snapshot launch path (--launch-existing-dist/--start-watch/--build-lavamoat without a custom --prepare-cmd)." >&2
|
|
279
292
|
fi
|
|
280
293
|
|
|
281
294
|
echo "Extension live validation command:"
|
|
282
295
|
display_args=(mm-harness runtime-launch --adapter extension --target "$TARGET" --cdp-port "$CDP_PORT")
|
|
283
296
|
$START_WATCH && display_args+=(--start-watch)
|
|
297
|
+
$BUILD_LAVAMOAT && display_args+=(--build-lavamoat)
|
|
284
298
|
[ -n "$REMOTE_FLAGS" ] && display_args+=(--remote-flag "$REMOTE_FLAGS")
|
|
285
299
|
[ -n "$CHROME_USER_DATA_DIR" ] && display_args+=(--chrome-user-data-dir "$CHROME_USER_DATA_DIR")
|
|
286
300
|
printf ' '
|
|
@@ -309,7 +323,7 @@ else
|
|
|
309
323
|
echo "Skipping Extension live verify because launch failed; see $ARTIFACTS/launch/summary.json" >&2
|
|
310
324
|
fi
|
|
311
325
|
|
|
312
|
-
TARGET_FOR_SUMMARY="$TARGET" ARTIFACTS_FOR_SUMMARY="$ARTIFACTS" CDP_PORT_FOR_SUMMARY="$CDP_PORT" LAUNCH_STATUS="$launch_status" VERIFY_STATUS="$verify_status" LAUNCH_EXISTING_DIST="$LAUNCH_EXISTING_DIST" START_WATCH="$START_WATCH" node <<'NODE'
|
|
326
|
+
TARGET_FOR_SUMMARY="$TARGET" ARTIFACTS_FOR_SUMMARY="$ARTIFACTS" CDP_PORT_FOR_SUMMARY="$CDP_PORT" LAUNCH_STATUS="$launch_status" VERIFY_STATUS="$verify_status" LAUNCH_EXISTING_DIST="$LAUNCH_EXISTING_DIST" START_WATCH="$START_WATCH" BUILD_LAVAMOAT="$BUILD_LAVAMOAT" node <<'NODE'
|
|
313
327
|
const fs = require('fs');
|
|
314
328
|
const path = require('path');
|
|
315
329
|
const artifacts = process.env.ARTIFACTS_FOR_SUMMARY;
|
|
@@ -325,6 +339,7 @@ fs.writeFileSync(path.join(artifacts, 'summary.json'), `${JSON.stringify({
|
|
|
325
339
|
cdpPort: process.env.CDP_PORT_FOR_SUMMARY,
|
|
326
340
|
launchExistingDist: process.env.LAUNCH_EXISTING_DIST === 'true',
|
|
327
341
|
startWatch: process.env.START_WATCH === 'true',
|
|
342
|
+
buildLavaMoat: process.env.BUILD_LAVAMOAT === 'true',
|
|
328
343
|
launch: { exitCode: launchStatus, summaryPath: fs.existsSync(launchSummary) ? launchSummary : null },
|
|
329
344
|
verify: { exitCode: verifyStatus, summaryPath: fs.existsSync(verifySummary) ? verifySummary : null },
|
|
330
345
|
easyCommand: `mm-harness launch --verify --target <repo>`,
|
|
@@ -103,6 +103,7 @@ echo "[reattach] reusing loaded runtime-dist: $RUNTIME_DIST_ABS" >&2
|
|
|
103
103
|
if [ -d "$DIST_ABS" ] && [ -d "$RUNTIME_DIST_ABS" ]; then
|
|
104
104
|
echo "[reattach] refreshing loaded runtime-dist in place" >&2
|
|
105
105
|
rsync -a --exclude _metadata "$DIST_ABS/" "$RUNTIME_DIST_ABS/" >&2
|
|
106
|
+
node "$SCRIPT_DIR/configure-runtime-manifest.cjs" --target "$TARGET" --manifest "$RUNTIME_DIST_ABS/manifest.json" >&2
|
|
106
107
|
node "$SCRIPT_DIR/stamp-runtime-title.cjs" --target "$TARGET" --runtime-dist "$RUNTIME_DIST_ABS" --runtime-dir "$RUNTIME_DIR" >&2
|
|
107
108
|
fi
|
|
108
109
|
|
|
@@ -58,6 +58,23 @@ function syncRuntimeDist() {
|
|
|
58
58
|
process.stderr.write(`runtime-dist sync failed: ${(result.stderr || result.stdout || `rsync exited ${result.status}`).trim()}\n`);
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
|
+
const configure = spawnSync(
|
|
62
|
+
process.execPath,
|
|
63
|
+
[
|
|
64
|
+
path.join(__dirname, 'configure-runtime-manifest.cjs'),
|
|
65
|
+
'--target',
|
|
66
|
+
path.resolve(args.target),
|
|
67
|
+
'--manifest',
|
|
68
|
+
path.join(runtimeDist, 'manifest.json'),
|
|
69
|
+
],
|
|
70
|
+
{ encoding: 'utf8' },
|
|
71
|
+
);
|
|
72
|
+
if (configure.status !== 0) {
|
|
73
|
+
process.stderr.write(
|
|
74
|
+
`runtime-dist configuration failed: ${(configure.stderr || configure.stdout || `node exited ${configure.status}`).trim()}\n`,
|
|
75
|
+
);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
61
78
|
stampRuntimeTitles({ target: path.resolve(args.target), runtimeDist });
|
|
62
79
|
process.stdout.write(`runtime-dist updated after webpack compile (${new Date().toISOString()})\n`);
|
|
63
80
|
}
|
package/adapters/manifest.json
CHANGED
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"entry": "adapters/extension/live.sh",
|
|
120
120
|
"kind": "bash",
|
|
121
121
|
"purpose": "Sequencer: builds the prepare command from the named feature scripts, then launch + verify.",
|
|
122
|
-
"inputs": "--target --cdp-port --launch-existing-dist|--start-watch|--prepare-cmd --dist-dir --chrome-user-data-dir --out --artifacts-dir; env RECIPE_HARNESS_CHROME_BIN, RECIPE_WALLET_FIXTURE, RECIPE_HARNESS_LIVE_KEEP",
|
|
122
|
+
"inputs": "--target --cdp-port --launch-existing-dist|--start-watch|--build-lavamoat|--prepare-cmd --dist-dir --chrome-user-data-dir --out --artifacts-dir; env RECIPE_HARNESS_CHROME_BIN, RECIPE_WALLET_FIXTURE, RECIPE_HARNESS_LIVE_KEEP",
|
|
123
123
|
"outputs": "<artifacts>/summary.json + launch/ + verify/ + logs/fixture-source.json; exit 0/1/2"
|
|
124
124
|
},
|
|
125
125
|
{
|
|
@@ -138,6 +138,14 @@
|
|
|
138
138
|
"inputs": "--target --runtime-dir --runner-bin --summary",
|
|
139
139
|
"outputs": "<runtime-dir>/recipe-harness-webpack.{pid,log}; optional summary; exit 0/1/2"
|
|
140
140
|
},
|
|
141
|
+
{
|
|
142
|
+
"id": "extension/build-lavamoat",
|
|
143
|
+
"entry": "adapters/extension/build-lavamoat.sh",
|
|
144
|
+
"kind": "bash",
|
|
145
|
+
"purpose": "Build a production-like Extension runtime with LavaMoat without enabling test-build fixture semantics.",
|
|
146
|
+
"inputs": "--target --runtime-dir",
|
|
147
|
+
"outputs": "dist/chrome; exit 0/1/2"
|
|
148
|
+
},
|
|
141
149
|
{
|
|
142
150
|
"id": "extension/launch-webpack",
|
|
143
151
|
"entry": "adapters/extension/launch-webpack.cjs",
|
|
@@ -154,6 +162,14 @@
|
|
|
154
162
|
"inputs": "--watcher-pid --watch-log --target --dist --runtime-dist --pid-file",
|
|
155
163
|
"outputs": "runtime-dist updated in place after successful compile markers; exits with its watcher"
|
|
156
164
|
},
|
|
165
|
+
{
|
|
166
|
+
"id": "extension/configure-runtime-manifest",
|
|
167
|
+
"entry": "adapters/extension/configure-runtime-manifest.cjs",
|
|
168
|
+
"kind": "node",
|
|
169
|
+
"purpose": "Inject the checkout's configured Infura project id into an isolated runtime manifest so Extension test builds cannot trigger browser-native authentication prompts.",
|
|
170
|
+
"inputs": "--target --manifest",
|
|
171
|
+
"outputs": "runtime manifest _flags.testing.infuraProjectId; exit 0/1"
|
|
172
|
+
},
|
|
157
173
|
{
|
|
158
174
|
"id": "extension/stamp-runtime-title",
|
|
159
175
|
"entry": "adapters/extension/stamp-runtime-title.cjs",
|
|
@@ -402,6 +418,14 @@
|
|
|
402
418
|
"inputs": "ensure-runner-deps.sh [RUNNER_DIR]; env FARMSLOT_ROOT / METAMASK_RUNNER_PROTOCOL_ROOT optional local link target",
|
|
403
419
|
"outputs": "installed node_modules in RUNNER_DIR; exit 0 when deps ready"
|
|
404
420
|
},
|
|
421
|
+
{
|
|
422
|
+
"id": "lib/runner-deps-ready",
|
|
423
|
+
"entry": "adapters/shared/runner-deps-ready.mjs",
|
|
424
|
+
"kind": "node",
|
|
425
|
+
"purpose": "Validate installed runner dependency versions, accepting only explicit local protocol-root links as development overrides.",
|
|
426
|
+
"inputs": "<runner-dir> [local-protocol-root]",
|
|
427
|
+
"outputs": "exit 0 when dependencies satisfy package.json; exit 1 otherwise"
|
|
428
|
+
},
|
|
405
429
|
{
|
|
406
430
|
"id": "lib/install-repo-deps",
|
|
407
431
|
"entry": "adapters/shared/install-repo-deps.sh",
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
# Env: FARMSLOT_ROOT / METAMASK_RUNNER_PROTOCOL_ROOT — optional local link target
|
|
11
11
|
set -euo pipefail
|
|
12
12
|
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
14
|
RUNNER_DIR="${1:-}"
|
|
14
15
|
if [ -z "$RUNNER_DIR" ]; then
|
|
15
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
16
|
RUNNER_DIR="$(cd "$SCRIPT_DIR/../.." && pwd -P)"
|
|
17
17
|
fi
|
|
18
18
|
RUNNER_DIR="$(cd "$RUNNER_DIR" && pwd -P)"
|
|
@@ -23,59 +23,8 @@ if [ -z "$PROTOCOL_ROOT" ] && [ -f "$RUNNER_DIR/.farmslot-root" ]; then
|
|
|
23
23
|
fi
|
|
24
24
|
|
|
25
25
|
deps_ready() {
|
|
26
|
-
node
|
|
27
|
-
|
|
28
|
-
import { createRequire } from 'node:module';
|
|
29
|
-
import path from 'node:path';
|
|
30
|
-
import { pathToFileURL } from 'node:url';
|
|
31
|
-
|
|
32
|
-
const runnerDir = process.argv[2];
|
|
33
|
-
const overrideRootInput = process.argv[3];
|
|
34
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(runnerDir, 'package.json'), 'utf8'));
|
|
35
|
-
let dependencyVersionSatisfies;
|
|
36
|
-
try {
|
|
37
|
-
const requireFromRunner = createRequire(path.join(runnerDir, 'package.json'));
|
|
38
|
-
const helperPath = requireFromRunner.resolve(
|
|
39
|
-
'@farmslot/recipe-harness/runtime/deps-readiness',
|
|
40
|
-
);
|
|
41
|
-
({ dependencyVersionSatisfies } = await import(pathToFileURL(helperPath).href));
|
|
42
|
-
if (typeof dependencyVersionSatisfies !== 'function') process.exit(1);
|
|
43
|
-
} catch {
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
let overrideRoot = null;
|
|
47
|
-
if (overrideRootInput) {
|
|
48
|
-
try {
|
|
49
|
-
overrideRoot = fs.realpathSync(overrideRootInput);
|
|
50
|
-
} catch {
|
|
51
|
-
overrideRoot = null;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
function isOverrideLink(packageDir) {
|
|
55
|
-
if (!overrideRoot) return false;
|
|
56
|
-
try {
|
|
57
|
-
if (!fs.lstatSync(packageDir).isSymbolicLink()) return false;
|
|
58
|
-
const resolved = fs.realpathSync(packageDir);
|
|
59
|
-
const relative = path.relative(overrideRoot, resolved);
|
|
60
|
-
return relative === '' || (
|
|
61
|
-
relative !== '..' &&
|
|
62
|
-
!relative.startsWith(`..${path.sep}`) &&
|
|
63
|
-
!path.isAbsolute(relative)
|
|
64
|
-
);
|
|
65
|
-
} catch {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
const invalid = Object.entries(pkg.dependencies ?? {}).filter(([name, request]) => {
|
|
70
|
-
const packageDir = path.join(runnerDir, 'node_modules', ...name.split('/'));
|
|
71
|
-
const installedPath = path.join(packageDir, 'package.json');
|
|
72
|
-
if (!fs.existsSync(installedPath)) return true;
|
|
73
|
-
if (isOverrideLink(packageDir)) return false;
|
|
74
|
-
const installed = JSON.parse(fs.readFileSync(installedPath, 'utf8'));
|
|
75
|
-
return !dependencyVersionSatisfies(installed.version, request);
|
|
76
|
-
});
|
|
77
|
-
process.exit(invalid.length === 0 ? 0 : 1);
|
|
78
|
-
NODE
|
|
26
|
+
node "$SCRIPT_DIR/runner-deps-ready.mjs" \
|
|
27
|
+
"$RUNNER_DIR" "$PROTOCOL_ROOT"
|
|
79
28
|
}
|
|
80
29
|
|
|
81
30
|
# Library actions import @deeeed/metamask-harness by package name; a source checkout
|
|
@@ -127,7 +76,9 @@ fi
|
|
|
127
76
|
echo "mm-harness: installing runner dependencies via npm in $RUNNER_DIR" >&2
|
|
128
77
|
(
|
|
129
78
|
cd "$RUNNER_DIR"
|
|
130
|
-
|
|
79
|
+
umask 022
|
|
80
|
+
npm_config_cache="${MM_HARNESS_NPM_CACHE:-${TMPDIR:-/tmp}/mm-harness-npm-cache-${UID:-$(id -u)}}" \
|
|
81
|
+
npm install --ignore-scripts >&2
|
|
131
82
|
)
|
|
132
83
|
|
|
133
84
|
# npm may replace the local protocol links with registry packages. Restore the
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
|
|
6
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
7
|
+
console.log('Usage: runner-deps-ready.mjs <runner-dir> [local-protocol-root]');
|
|
8
|
+
process.exit(0);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const [runnerDir, overrideRootInput] = process.argv.slice(2);
|
|
12
|
+
if (!runnerDir) process.exit(1);
|
|
13
|
+
const pkg = JSON.parse(
|
|
14
|
+
fs.readFileSync(path.join(runnerDir, 'package.json'), 'utf8'),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
let dependencyVersionSatisfies;
|
|
18
|
+
try {
|
|
19
|
+
const requireFromRunner = createRequire(path.join(runnerDir, 'package.json'));
|
|
20
|
+
const helperPath = requireFromRunner.resolve(
|
|
21
|
+
'@farmslot/recipe-harness/runtime/deps-readiness',
|
|
22
|
+
);
|
|
23
|
+
({ dependencyVersionSatisfies } = await import(pathToFileURL(helperPath).href));
|
|
24
|
+
if (typeof dependencyVersionSatisfies !== 'function') process.exit(1);
|
|
25
|
+
} catch {
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let overrideRoot = null;
|
|
30
|
+
if (overrideRootInput) {
|
|
31
|
+
try {
|
|
32
|
+
overrideRoot = fs.realpathSync(overrideRootInput);
|
|
33
|
+
} catch {
|
|
34
|
+
overrideRoot = null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isOverrideLink(packageDir) {
|
|
39
|
+
if (!overrideRoot) return false;
|
|
40
|
+
try {
|
|
41
|
+
if (!fs.lstatSync(packageDir).isSymbolicLink()) return false;
|
|
42
|
+
const resolved = fs.realpathSync(packageDir);
|
|
43
|
+
const relative = path.relative(overrideRoot, resolved);
|
|
44
|
+
return (
|
|
45
|
+
relative === '' ||
|
|
46
|
+
(relative !== '..' &&
|
|
47
|
+
!relative.startsWith(`..${path.sep}`) &&
|
|
48
|
+
!path.isAbsolute(relative))
|
|
49
|
+
);
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const invalid = Object.entries(pkg.dependencies ?? {}).filter(
|
|
56
|
+
([name, request]) => {
|
|
57
|
+
const packageDir = path.join(
|
|
58
|
+
runnerDir,
|
|
59
|
+
'node_modules',
|
|
60
|
+
...name.split('/'),
|
|
61
|
+
);
|
|
62
|
+
const installedPath = path.join(packageDir, 'package.json');
|
|
63
|
+
if (!fs.existsSync(installedPath)) return true;
|
|
64
|
+
if (isOverrideLink(packageDir)) return false;
|
|
65
|
+
const installed = JSON.parse(fs.readFileSync(installedPath, 'utf8'));
|
|
66
|
+
return !dependencyVersionSatisfies(installed.version, request);
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
process.exit(invalid.length === 0 ? 0 : 1);
|
|
@@ -1,77 +1,40 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
2
|
+
import productConfig from "../../../adapters/extension/lib/product-config.cjs";
|
|
3
3
|
import { shellQuote } from "../../commands/parse-args.js";
|
|
4
|
-
function isConfiguredInfuraId(value, placeholder) {
|
|
5
|
-
return value.length > 0 && value !== placeholder && value !== "true" && value !== "false" && value !== "null";
|
|
6
|
-
}
|
|
7
4
|
function extensionProductConfigBlock(target) {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
const resolution = productConfig.resolveExtensionInfuraProjectId(
|
|
6
|
+
target,
|
|
7
|
+
process.env
|
|
8
|
+
);
|
|
9
|
+
if (resolution.kind === "configured" || resolution.kind === "not-required") {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
if (resolution.kind === "invalid-file") {
|
|
13
|
+
return invalidConfigBlock(target, resolution.name, resolution.reason);
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
if (resolution.kind === "missing") {
|
|
16
|
+
return {
|
|
17
|
+
message: "Extension product configuration is required before launch: create .metamaskrc and set a non-placeholder INFURA_PROJECT_ID (the value is never displayed).",
|
|
18
|
+
userAction: `cd ${shellQuote(path.resolve(target))} && cp .metamaskrc.dist .metamaskrc && \${EDITOR:-vi} .metamaskrc`
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (resolution.source === "environment") {
|
|
18
22
|
return {
|
|
19
23
|
message: "Extension product configuration is blocked by an unusable exported INFURA_PROJECT_ID (the value is never displayed).",
|
|
20
24
|
userAction: `unset INFURA_PROJECT_ID; cd ${shellQuote(path.resolve(target))} && cp -n .metamaskrc.dist .metamaskrc && \${EDITOR:-vi} .metamaskrc`
|
|
21
25
|
};
|
|
22
26
|
}
|
|
23
|
-
|
|
24
|
-
if (productionConfig.kind === "invalid") {
|
|
25
|
-
return invalidConfigBlock(target, ".metamaskprodrc", productionConfig.reason);
|
|
26
|
-
}
|
|
27
|
-
if (productionConfig.kind === "file") {
|
|
28
|
-
const value2 = readInfuraProjectId(productionConfig.text);
|
|
29
|
-
if (value2 !== void 0) {
|
|
30
|
-
if (isConfiguredInfuraId(value2, placeholder)) return null;
|
|
31
|
-
return {
|
|
32
|
-
message: "Extension product configuration is incomplete: .metamaskprodrc shadows .metamaskrc but does not contain a usable INFURA_PROJECT_ID (the value is never displayed).",
|
|
33
|
-
userAction: `cd ${shellQuote(path.resolve(target))} && \${EDITOR:-vi} .metamaskprodrc`
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
const config = path.join(target, ".metamaskrc");
|
|
38
|
-
const configSnapshot = readConfigSnapshot(config);
|
|
39
|
-
if (configSnapshot.kind === "missing") {
|
|
27
|
+
if (resolution.source === ".metamaskprodrc") {
|
|
40
28
|
return {
|
|
41
|
-
message: "Extension product configuration is
|
|
42
|
-
userAction: `cd ${shellQuote(path.resolve(target))} &&
|
|
29
|
+
message: "Extension product configuration is incomplete: .metamaskprodrc shadows .metamaskrc but does not contain a usable INFURA_PROJECT_ID (the value is never displayed).",
|
|
30
|
+
userAction: `cd ${shellQuote(path.resolve(target))} && \${EDITOR:-vi} .metamaskprodrc`
|
|
43
31
|
};
|
|
44
32
|
}
|
|
45
|
-
if (configSnapshot.kind === "invalid") {
|
|
46
|
-
return invalidConfigBlock(target, ".metamaskrc", configSnapshot.reason);
|
|
47
|
-
}
|
|
48
|
-
const value = readInfuraProjectId(configSnapshot.text) ?? "";
|
|
49
|
-
if (isConfiguredInfuraId(value, placeholder)) return null;
|
|
50
33
|
return {
|
|
51
34
|
message: "Extension product configuration is incomplete: .metamaskrc must contain a non-placeholder INFURA_PROJECT_ID (the value is never displayed).",
|
|
52
35
|
userAction: `cd ${shellQuote(path.resolve(target))} && \${EDITOR:-vi} .metamaskrc`
|
|
53
36
|
};
|
|
54
37
|
}
|
|
55
|
-
function readConfigSnapshot(file) {
|
|
56
|
-
let descriptor;
|
|
57
|
-
try {
|
|
58
|
-
descriptor = fs.openSync(
|
|
59
|
-
file,
|
|
60
|
-
fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK
|
|
61
|
-
);
|
|
62
|
-
} catch (error) {
|
|
63
|
-
const code = error.code;
|
|
64
|
-
if (code === "ENOENT") return { kind: "missing" };
|
|
65
|
-
if (code === "ELOOP") return { kind: "invalid", reason: "symlink" };
|
|
66
|
-
return { kind: "invalid", reason: "unreadable" };
|
|
67
|
-
}
|
|
68
|
-
try {
|
|
69
|
-
if (!fs.fstatSync(descriptor).isFile()) return { kind: "invalid", reason: "not-regular" };
|
|
70
|
-
return { kind: "file", text: fs.readFileSync(descriptor, "utf8") };
|
|
71
|
-
} finally {
|
|
72
|
-
fs.closeSync(descriptor);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
38
|
function invalidConfigBlock(target, name, reason) {
|
|
76
39
|
const resolved = shellQuote(path.resolve(target));
|
|
77
40
|
if (name === ".metamaskrc.dist") {
|
|
@@ -91,20 +54,6 @@ function invalidConfigBlock(target, name, reason) {
|
|
|
91
54
|
userAction: `cd ${resolved} && rm ${name} && ${name === ".metamaskrc" ? "cp .metamaskrc.dist .metamaskrc && " : ""}\${EDITOR:-vi} ${name}`
|
|
92
55
|
};
|
|
93
56
|
}
|
|
94
|
-
function readInfuraProjectId(text) {
|
|
95
|
-
const lines = text.replace(/\r\n?/gu, "\n");
|
|
96
|
-
const pattern = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gmu;
|
|
97
|
-
let result;
|
|
98
|
-
for (const match of lines.matchAll(pattern)) {
|
|
99
|
-
if (match[1] !== "INFURA_PROJECT_ID") continue;
|
|
100
|
-
let value = (match[2] ?? "").trim();
|
|
101
|
-
const quote = value[0];
|
|
102
|
-
value = value.replace(/^(['"`])([\s\S]*)\1$/u, "$2");
|
|
103
|
-
if (quote === '"') value = value.replace(/\\n/gu, "\n").replace(/\\r/gu, "\r");
|
|
104
|
-
result = value;
|
|
105
|
-
}
|
|
106
|
-
return result;
|
|
107
|
-
}
|
|
108
57
|
export {
|
|
109
58
|
extensionProductConfigBlock
|
|
110
59
|
};
|
|
@@ -169,6 +169,8 @@ function runtimeDistCheck(target) {
|
|
|
169
169
|
"home.html",
|
|
170
170
|
"--exclude",
|
|
171
171
|
"sidepanel.html",
|
|
172
|
+
"--exclude",
|
|
173
|
+
"manifest.json",
|
|
172
174
|
"--out-format=%n",
|
|
173
175
|
`${dist}${path.sep}`,
|
|
174
176
|
`${runtimeDist}${path.sep}`
|
|
@@ -182,6 +184,9 @@ function runtimeDistCheck(target) {
|
|
|
182
184
|
const loaded = path.join(runtimeDist, name);
|
|
183
185
|
if (normalizedRuntimeHtml(source) !== normalizedRuntimeHtml(loaded)) modified.push(name);
|
|
184
186
|
}
|
|
187
|
+
if (normalizedRuntimeManifest(path.join(dist, "manifest.json")) !== normalizedRuntimeManifest(path.join(runtimeDist, "manifest.json"))) {
|
|
188
|
+
modified.push("manifest.json");
|
|
189
|
+
}
|
|
185
190
|
const boundedModified = [...new Set(modified)].slice(0, 10);
|
|
186
191
|
return boundedModified.length > 0 ? { status: "stale", modified: boundedModified } : { status: "fresh" };
|
|
187
192
|
}
|
|
@@ -193,6 +198,25 @@ function normalizedRuntimeHtml(file) {
|
|
|
193
198
|
return `<title>${base}</title>`;
|
|
194
199
|
});
|
|
195
200
|
}
|
|
201
|
+
function normalizedRuntimeManifest(file) {
|
|
202
|
+
if (!fs.existsSync(file)) return null;
|
|
203
|
+
try {
|
|
204
|
+
const manifest = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
205
|
+
if (manifest?._flags?.testing) {
|
|
206
|
+
delete manifest._flags.testing.infuraProjectId;
|
|
207
|
+
if (Object.keys(manifest._flags.testing).length === 0) {
|
|
208
|
+
delete manifest._flags.testing;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (manifest?._flags) {
|
|
212
|
+
delete manifest._flags.remoteFeatureFlags;
|
|
213
|
+
if (Object.keys(manifest._flags).length === 0) delete manifest._flags;
|
|
214
|
+
}
|
|
215
|
+
return JSON.stringify(manifest);
|
|
216
|
+
} catch {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
196
220
|
function distCheck(target) {
|
|
197
221
|
const manifestPath = path.join(target, "dist/chrome/manifest.json");
|
|
198
222
|
if (!fs.existsSync(manifestPath)) return { status: "no-build" };
|
|
@@ -222,10 +222,10 @@ async function extensionRebuild(target) {
|
|
|
222
222
|
fs.mkdirSync(path.dirname(rebuildLog), { recursive: true });
|
|
223
223
|
fs.writeFileSync(rebuildLog, "");
|
|
224
224
|
const liveScript = recipeHarnessPath(target, "extension", "scripts", "live.sh");
|
|
225
|
-
const liveArgs = ["--target", target, "--
|
|
225
|
+
const liveArgs = ["--target", target, "--build-lavamoat"];
|
|
226
226
|
if (process.env.CDP_PORT) liveArgs.push("--cdp-port", process.env.CDP_PORT);
|
|
227
227
|
if (process.env.EXTENSION_START_URL) liveArgs.push("--start-url", process.env.EXTENSION_START_URL);
|
|
228
|
-
console.error(colorHumanMessage(`\u2192 extension quick relaunch \u2014
|
|
228
|
+
console.error(colorHumanMessage(`\u2192 extension quick relaunch \u2014 production-like LavaMoat build \xB7 CDP :${process.env.CDP_PORT ?? "default"} (output streams below)`));
|
|
229
229
|
const result = await spawnScriptStreaming(liveScript, liveArgs, target);
|
|
230
230
|
if (result.output) {
|
|
231
231
|
try {
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
optionString,
|
|
10
10
|
parsePort,
|
|
11
11
|
shellQuote,
|
|
12
|
-
targetPath
|
|
12
|
+
targetPath,
|
|
13
|
+
usageError
|
|
13
14
|
} from "./parse-args.js";
|
|
14
15
|
async function handleRuntimeLaunch({ options }) {
|
|
15
16
|
const adapter = adapterOption(options);
|
|
@@ -24,6 +25,11 @@ async function handleRuntimeLaunch({ options }) {
|
|
|
24
25
|
optionString(options, "artifactsDir") ?? recipeHarnessPath(target, "extension", "runtime-launch", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/gu, ""))
|
|
25
26
|
);
|
|
26
27
|
const startWatch = optionFlag(options, "startWatch");
|
|
28
|
+
const buildLavaMoat = optionFlag(options, "buildLavamoat");
|
|
29
|
+
if (startWatch && buildLavaMoat) {
|
|
30
|
+
throw usageError("runtime-launch accepts only one of --start-watch or --build-lavamoat.");
|
|
31
|
+
}
|
|
32
|
+
const mode = buildLavaMoat ? "build-lavamoat" : startWatch ? "start-watch" : "existing-dist";
|
|
27
33
|
const liveScript = recipeHarnessPath(target, "extension", "scripts", "live.sh");
|
|
28
34
|
const command = [
|
|
29
35
|
"bash",
|
|
@@ -32,7 +38,7 @@ async function handleRuntimeLaunch({ options }) {
|
|
|
32
38
|
target,
|
|
33
39
|
"--cdp-port",
|
|
34
40
|
String(cdpPort),
|
|
35
|
-
|
|
41
|
+
`--${mode}`,
|
|
36
42
|
"--artifacts-dir",
|
|
37
43
|
artifactsDir
|
|
38
44
|
];
|
|
@@ -46,7 +52,7 @@ async function handleRuntimeLaunch({ options }) {
|
|
|
46
52
|
cdpPort,
|
|
47
53
|
artifactsDir,
|
|
48
54
|
reason: "harness_live_script_missing",
|
|
49
|
-
fix: `Run mm-harness install --adapter extension --target ${shellQuote(target)}, then rerun: ${runtimeLaunchCommand(target, cdpPort, chromeUserDataDir)}`,
|
|
55
|
+
fix: `Run mm-harness install --adapter extension --target ${shellQuote(target)}, then rerun: ${runtimeLaunchCommand(target, cdpPort, chromeUserDataDir, mode)}`,
|
|
50
56
|
command
|
|
51
57
|
});
|
|
52
58
|
printRuntimeLaunchReport(report2, optionFlag(options, "json"));
|
|
@@ -55,14 +61,15 @@ async function handleRuntimeLaunch({ options }) {
|
|
|
55
61
|
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
56
62
|
const jsonMode = optionFlag(options, "json");
|
|
57
63
|
if (!jsonMode) {
|
|
58
|
-
|
|
64
|
+
const detail = mode === "build-lavamoat" ? "production-like LavaMoat build" : mode === "start-watch" ? "test webpack watcher" : "existing dist";
|
|
65
|
+
console.error(`recipe: runtime-launch starting (cdp=${cdpPort}, ${detail})`);
|
|
59
66
|
} else {
|
|
60
67
|
console.error(JSON.stringify({
|
|
61
68
|
schemaVersion: 1,
|
|
62
69
|
type: "progress",
|
|
63
70
|
command: "rebuild",
|
|
64
71
|
phase: "runtime-launch",
|
|
65
|
-
message:
|
|
72
|
+
message: mode === "build-lavamoat" ? "production-like LavaMoat build starting" : mode === "start-watch" ? "test webpack watcher starting" : "launching existing dist",
|
|
66
73
|
cdpPort
|
|
67
74
|
}));
|
|
68
75
|
}
|
|
@@ -102,16 +109,16 @@ async function handleRuntimeLaunch({ options }) {
|
|
|
102
109
|
launchLogPath: fs.existsSync(launchLogPath) ? launchLogPath : void 0,
|
|
103
110
|
verifySummaryPath: fs.existsSync(verifySummaryPath) ? verifySummaryPath : void 0,
|
|
104
111
|
reason,
|
|
105
|
-
fix: `Read ${failurePath}, fix the first error, then rerun: ${runtimeLaunchCommand(target, cdpPort, chromeUserDataDir,
|
|
112
|
+
fix: `Read ${failurePath}, fix the first error, then rerun: ${runtimeLaunchCommand(target, cdpPort, chromeUserDataDir, mode)}`,
|
|
106
113
|
command,
|
|
107
114
|
exitCode: result.status ?? 1
|
|
108
115
|
});
|
|
109
116
|
printRuntimeLaunchReport(report, optionFlag(options, "json"));
|
|
110
117
|
return 1;
|
|
111
118
|
}
|
|
112
|
-
function runtimeLaunchCommand(target, cdpPort, chromeUserDataDir,
|
|
119
|
+
function runtimeLaunchCommand(target, cdpPort, chromeUserDataDir, mode = "existing-dist") {
|
|
113
120
|
const base = `mm-harness runtime-launch --adapter extension --target ${shellQuote(target)} --cdp-port ${cdpPort}`;
|
|
114
|
-
const withMode =
|
|
121
|
+
const withMode = mode === "existing-dist" ? base : `${base} --${mode}`;
|
|
115
122
|
return chromeUserDataDir ? `${withMode} --chrome-user-data-dir ${shellQuote(chromeUserDataDir)}` : withMode;
|
|
116
123
|
}
|
|
117
124
|
function runtimeLaunchReport(status, fields) {
|
|
@@ -709,10 +709,8 @@ async function readPerpsRuntimeState(page) {
|
|
|
709
709
|
const request = hooks.submitRequestToBackground;
|
|
710
710
|
const reduxState = hooks.store?.getState?.() || {};
|
|
711
711
|
const cleanState = (await hooks.getCleanAppState?.()) || hooks.getState?.() || {};
|
|
712
|
-
const persisted = await globalThis.chrome?.storage?.local?.get?.('data');
|
|
713
|
-
// Extension flattens PerpsController state into state.metamask via ComposableObservableStore.getFlatState().
|
|
714
712
|
const metamask = reduxState.metamask || cleanState.metamask || {};
|
|
715
|
-
const nestedPerps =
|
|
713
|
+
const nestedPerps = metamask.PerpsController || cleanState.PerpsController || cleanState.perps || {};
|
|
716
714
|
return {
|
|
717
715
|
available: typeof request === 'function',
|
|
718
716
|
activeProvider: metamask.activeProvider || nestedPerps.activeProvider || 'hyperliquid',
|
|
@@ -27,20 +27,75 @@ function nonNegativeInteger(value, name, fallback) {
|
|
|
27
27
|
return parsed;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
const PROPERTY_MATCHERS = new Set(['$exists', '$type', '$gt']);
|
|
31
|
+
|
|
32
|
+
function validatePropertyExpectation(value, key) {
|
|
33
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return;
|
|
34
|
+
const keys = Object.keys(value);
|
|
35
|
+
const matcherKeys = keys.filter((entry) => entry.startsWith('$'));
|
|
36
|
+
if (matcherKeys.length === 0) return;
|
|
37
|
+
if (matcherKeys.length !== keys.length) {
|
|
38
|
+
throw new Error(`Property matcher for "${key}" cannot mix matcher and literal keys.`);
|
|
39
|
+
}
|
|
40
|
+
const unsupported = matcherKeys.filter((entry) => !PROPERTY_MATCHERS.has(entry));
|
|
41
|
+
if (unsupported.length > 0) {
|
|
42
|
+
throw new Error(`Unsupported property matcher for "${key}": ${unsupported.join(', ')}.`);
|
|
43
|
+
}
|
|
44
|
+
if ('$exists' in value && typeof value.$exists !== 'boolean') {
|
|
45
|
+
throw new Error(`$exists matcher for "${key}" must be boolean.`);
|
|
46
|
+
}
|
|
47
|
+
if (
|
|
48
|
+
'$type' in value &&
|
|
49
|
+
!['string', 'number', 'boolean', 'object', 'array'].includes(value.$type)
|
|
50
|
+
) {
|
|
51
|
+
throw new Error(`$type matcher for "${key}" is invalid.`);
|
|
52
|
+
}
|
|
53
|
+
if ('$gt' in value && !isNumericValue(value.$gt)) {
|
|
54
|
+
throw new Error(`$gt matcher for "${key}" must be numeric.`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isNumericValue(value) {
|
|
59
|
+
return (
|
|
60
|
+
(typeof value === 'number' && Number.isFinite(value)) ||
|
|
61
|
+
(typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value)))
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function matchesValue(got, want) {
|
|
66
|
+
if (want !== null && typeof want === 'object' && !Array.isArray(want)) {
|
|
67
|
+
const matcherKeys = Object.keys(want).filter((entry) => entry.startsWith('$'));
|
|
68
|
+
if (matcherKeys.length > 0) {
|
|
69
|
+
if ('$exists' in want && (got !== undefined) !== want.$exists) return false;
|
|
70
|
+
if ('$type' in want) {
|
|
71
|
+
const actualType = got === null ? 'null' : Array.isArray(got) ? 'array' : typeof got;
|
|
72
|
+
if (actualType !== want.$type) return false;
|
|
73
|
+
}
|
|
74
|
+
if ('$gt' in want) {
|
|
75
|
+
if (!isNumericValue(got) || Number(got) <= Number(want.$gt)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (want !== null && typeof want === 'object') {
|
|
83
|
+
return JSON.stringify(got) === JSON.stringify(want);
|
|
84
|
+
}
|
|
85
|
+
// Segment serialises numbers inconsistently across clients.
|
|
86
|
+
if (typeof want === 'number' && typeof got === 'string' && got.trim() !== '') {
|
|
87
|
+
return Number(got) === want;
|
|
88
|
+
}
|
|
89
|
+
if (typeof got === 'number' && typeof want === 'string' && want.trim() !== '') {
|
|
90
|
+
return got === Number(want);
|
|
91
|
+
}
|
|
92
|
+
return Object.is(got, want);
|
|
93
|
+
}
|
|
94
|
+
|
|
30
95
|
function matchesProperties(actual, expected) {
|
|
31
96
|
return Object.entries(expected).every(([key, want]) => {
|
|
32
97
|
const got = actual?.[key];
|
|
33
|
-
|
|
34
|
-
return JSON.stringify(got) === JSON.stringify(want);
|
|
35
|
-
}
|
|
36
|
-
// Segment serialises numbers inconsistently across clients.
|
|
37
|
-
if (typeof want === 'number' && typeof got === 'string' && got.trim() !== '') {
|
|
38
|
-
return Number(got) === want;
|
|
39
|
-
}
|
|
40
|
-
if (typeof got === 'number' && typeof want === 'string' && want.trim() !== '') {
|
|
41
|
-
return got === Number(want);
|
|
42
|
-
}
|
|
43
|
-
return Object.is(got, want);
|
|
98
|
+
return matchesValue(got, want);
|
|
44
99
|
});
|
|
45
100
|
}
|
|
46
101
|
|
|
@@ -66,6 +121,9 @@ function expectationsFrom(input) {
|
|
|
66
121
|
if (hasCount && hasBounds) {
|
|
67
122
|
throw new Error(`expect entry for "${entry.event}" sets both count and min/max; pick one.`);
|
|
68
123
|
}
|
|
124
|
+
for (const [key, value] of Object.entries(entry.properties ?? {})) {
|
|
125
|
+
validatePropertyExpectation(value, key);
|
|
126
|
+
}
|
|
69
127
|
const expectation = {
|
|
70
128
|
event: entry.event,
|
|
71
129
|
properties: entry.properties ?? null,
|
|
@@ -2875,7 +2875,7 @@
|
|
|
2875
2875
|
},
|
|
2876
2876
|
"properties": {
|
|
2877
2877
|
"type": "object",
|
|
2878
|
-
"description": "Subset match on event properties;
|
|
2878
|
+
"description": "Subset match on event properties; values may be exact or use $exists, $type, and $gt predicates."
|
|
2879
2879
|
}
|
|
2880
2880
|
},
|
|
2881
2881
|
"required": [
|
|
@@ -2997,7 +2997,7 @@
|
|
|
2997
2997
|
},
|
|
2998
2998
|
"properties": {
|
|
2999
2999
|
"type": "object",
|
|
3000
|
-
"description": "Subset match on event properties;
|
|
3000
|
+
"description": "Subset match on event properties; values may be exact or use $exists, $type, and $gt predicates."
|
|
3001
3001
|
}
|
|
3002
3002
|
},
|
|
3003
3003
|
"required": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deeeed/metamask-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mm-harness": "bin/mm-harness"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"@farmslot/agent-runtime": "^0.4.0",
|
|
23
23
|
"@farmslot/handoff": "^0.3.1",
|
|
24
24
|
"@farmslot/protocol": "^0.14.0",
|
|
25
|
-
"@farmslot/recipe-harness": "^0.10.
|
|
25
|
+
"@farmslot/recipe-harness": "^0.10.4",
|
|
26
26
|
"commander": "^12.0.0",
|
|
27
27
|
"es-module-lexer": "2.3.1",
|
|
28
28
|
"esbuild": "0.28.1",
|