@deeeed/metamask-harness 0.35.0 → 0.37.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 +39 -0
- package/README.md +9 -1
- package/adapters/extension/artifact-runtime-state.cjs +128 -0
- package/adapters/extension/check-infura-readiness.cjs +102 -0
- package/adapters/extension/inject.mjs +2 -0
- package/adapters/extension/launch-browser.cjs +22 -0
- package/adapters/extension/live.sh +103 -18
- package/adapters/extension/readiness.mjs +77 -36
- package/adapters/extension/snapshot-dist.sh +88 -3
- package/adapters/extension/start-watch.sh +15 -0
- package/adapters/extension/verify.sh +6 -2
- package/adapters/extension/wallet-fixture-state.cjs +3 -1
- package/adapters/manifest.json +25 -1
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +73 -42
- package/adapters/mobile/reset-app-data.sh +154 -0
- package/adapters/shared/log-tui.mjs +1 -1
- package/dist/adapters/extension/artifact-integrity.js +38 -0
- package/dist/adapters/extension/extension-id.js +23 -4
- package/dist/adapters/extension/product-config.js +29 -1
- package/dist/adapters/extension/release-artifact.js +386 -0
- package/dist/adapters/extension/runtime-decision.js +161 -20
- package/dist/adapters/extension/runtime.js +127 -0
- package/dist/adapters/mobile/release-artifact-state.js +124 -0
- package/dist/adapters/mobile/release-artifact.js +295 -0
- package/dist/adapters.js +29 -4
- package/dist/cli-commands.js +1 -1
- package/dist/cli.js +2 -2
- package/dist/command-contract.js +17 -1
- package/dist/commands/call.js +2 -1
- package/dist/commands/device-target.js +5 -0
- package/dist/commands/fixtures.js +106 -31
- package/dist/commands/launch/extension.js +92 -7
- package/dist/commands/launch/index.js +13 -0
- package/dist/commands/launch/mobile.js +2 -0
- package/dist/commands/provision.js +2 -0
- package/dist/commands/run-engine.js +89 -5
- package/dist/commands/run.js +3 -1
- package/dist/commands/runtime-launch.js +178 -10
- package/dist/heal-bounds.js +1 -1
- package/dist/live-adapter-contract.js +3 -1
- package/dist/metamask-action-validation.js +47 -1
- package/dist/mm-harness-cli.js +37 -4
- package/dist/recipe-security.js +3 -0
- package/dist/run-diagnostics.js +1 -1
- package/docs/RECIPES.md +29 -0
- package/docs/RELEASE-QA-CAPABILITY-MAP.md +150 -0
- package/library/actions/extension/perps/perps.mjs +2 -0
- package/library/actions/extension/perps/read_snapshot.mjs +470 -0
- package/library/actions/extension/platform/cdp.mjs +6 -3
- package/library/actions/extension/wallet/import.mjs +201 -0
- package/library/actions/extension/wallet/reset.mjs +98 -0
- package/library/actions/extension/wallet/secret-input.mjs +98 -0
- package/library/actions/extension/wallet/state.mjs +1 -0
- package/library/actions/mobile/analytics/consent-settings.mjs +112 -0
- package/library/actions/mobile/analytics/set_consent.mjs +4 -112
- package/library/actions/mobile/platform/bridge.mjs +8 -0
- package/library/actions/mobile/platform/observe-ui.mjs +84 -2
- package/library/actions/mobile/ui/native-navigation.mjs +225 -0
- package/library/actions/mobile/ui/navigate.mjs +7 -0
- package/library/actions/mobile/wallet/import.mjs +328 -0
- package/library/actions/mobile/wallet/native-ui.mjs +493 -0
- package/library/actions/mobile/wallet/read_state.mjs +16 -0
- package/library/actions/mobile/wallet/reset-helper.mjs +99 -0
- package/library/actions/mobile/wallet/reset.mjs +20 -0
- package/library/actions/shared/ui/locators.mjs +7 -0
- package/library/actions/shared/wallet/import-source.mjs +101 -0
- package/library/manifests/extension.action-manifest.json +228 -0
- package/library/manifests/mobile.action-manifest.json +124 -0
- package/library/recipes/extension/runner/action-validation.recipe.json +12 -1
- package/library/recipes/wallet/import.recipe.json +102 -0
- package/library/recipes/wallet/reset-import.recipe.json +107 -0
- package/package.json +1 -1
- package/scripts/completions.sh +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.37.0 - 2026-08-13
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Add archive and extracted-tree integrity checks, caching, provenance, and isolated launch for downloaded Extension release artifacts without using the checkout build or product configuration.
|
|
10
|
+
- Add verified local Android APK install and launch with expected version name, version code, package ID, trusted digest, explicit device selection, terminal provenance, and native-accessibility recipes that do not require a development bridge or HUD.
|
|
11
|
+
- Add an Extension Perps snapshot action for bounded, redacted account, market, position, open-order, history, and watchlist observations when the background test hook is available.
|
|
12
|
+
- Add a release QA capability map covering the current Perps, Assets, Swap and Bridge, and Networks checklists, with a prioritized parity roadmap.
|
|
13
|
+
- Add native Android release navigation and accessibility observation for wallet home, Perps home, and parameterized Perps markets without Metro, a development bridge, a HUD, or fixed screen coordinates.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Probe Ethereum through the Extension UI provider when production builds omit test state hooks, while preserving explicit unreachable failures.
|
|
18
|
+
- Import recovery phrases through production LavaMoat onboarding without browser clipboard permissions, and remove the temporary browser bridge after use.
|
|
19
|
+
- Keep release-artifact recipe failures on the acquired binary and fail closed instead of recovering through a checkout build.
|
|
20
|
+
- Relaunch a stopped verified Extension artifact from its integrity-checked cache without switching builds or deleting the imported wallet profile.
|
|
21
|
+
- Recover a newly opened Extension home tab from Chromium's transient blocked-page shell by navigating the selected extension target once before readiness fails.
|
|
22
|
+
- Prefer the visible side panel after official-build onboarding opens it, instead of leaving recipes attached to the completed onboarding tab.
|
|
23
|
+
- Reject unattended biometric setup explicitly, handle default Mobile notification and wallet-tour choices on bridge-backed onboarding, and keep Extension-only Perps snapshot limits out of Core validation.
|
|
24
|
+
- Confine Extension runtime snapshots and artifact identity state to non-symlinked checkout runtime directories before replacing prior state.
|
|
25
|
+
- Complete opaque Mobile onboarding without Metro or a development bridge by cancelling biometric storage for password-only automation, handling notification and wallet-tour prompts with configurable defaults, and proving the imported address through native UI.
|
|
26
|
+
- Close each physical-device accessibility session only after its wallet proof completes, so composed reset/import/state recipes hand the device to the next node cleanly.
|
|
27
|
+
- Label artifact preparation as preparation instead of reporting it as a client build.
|
|
28
|
+
|
|
29
|
+
## 0.36.0 - 2026-08-13
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- Add a composable Mobile and Extension wallet import recipe that either verifies an existing fixture-backed profile or imports the primary mnemonic through visible onboarding UI, with secret-safe inputs, privacy-preserving defaults, and configurable MetaMetrics and Mobile interests.
|
|
34
|
+
- Add `fixtures reset` plus a shared Mobile/Extension reset-import recipe. Extension resets its owned browser profile; Mobile clears only the selected app's data while preserving its installed build. The visible recipe separately proves the real user reset and import flow.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Restart a harness-owned Extension watcher when its effective Infura credential differs from the current product configuration, and preflight compiled Ethereum and Linea credentials before Chrome starts, preventing a forced-red validation from poisoning later launches or opening a native HTTP authentication dialog.
|
|
39
|
+
- Make normal Extension launch and runtime recovery use the product's `yarn start` development build, while keeping production-like LavaMoat builds behind explicit `launch --build`.
|
|
40
|
+
- Require wallet profile reuse to match the requested account, wait for Mobile onboarding to reach a terminal route, and resolve the exact Mobile reset device and prewarm its bundle before relaunching after app-data reset.
|
|
41
|
+
- Refuse ambiguous installed Mobile app variants during reset, prefer complete environment wallet credentials without parsing a stale fixture, and reject bad Extension RPC configuration before replacing the loaded runtime snapshot.
|
|
42
|
+
- Validate recovery credentials, fixture structure, and platform-specific onboarding choices before destructive resets; prove Mobile reset by the visible import control; honor persisted MetaMetrics consent through Security settings; and keep file-backed wallet secrets out of bridge errors.
|
|
43
|
+
|
|
5
44
|
## 0.35.0 - 2026-08-12
|
|
6
45
|
|
|
7
46
|
### Fixed
|
package/README.md
CHANGED
|
@@ -88,10 +88,14 @@ mm-harness stop
|
|
|
88
88
|
mm-harness logs
|
|
89
89
|
mm-harness debug
|
|
90
90
|
mm-harness fixtures set
|
|
91
|
+
|
|
92
|
+
# Delete the current wallet, then reapply the canonical fixture from clean state.
|
|
93
|
+
mm-harness fixtures reset
|
|
91
94
|
```
|
|
92
95
|
|
|
93
96
|
Extension `launch` keeps its incremental watcher running. Refresh the active
|
|
94
|
-
page after a successful rebuild; use `launch --build` only for
|
|
97
|
+
page after a successful rebuild; use `launch --build` only for an explicit
|
|
98
|
+
production-like LavaMoat rebuild.
|
|
95
99
|
|
|
96
100
|
Log sources stay separate:
|
|
97
101
|
|
|
@@ -115,6 +119,10 @@ mm-harness call metamask.wallet.ensure_unlocked
|
|
|
115
119
|
mm-harness run --list
|
|
116
120
|
mm-harness run perps.clean-market-testnet --describe
|
|
117
121
|
mm-harness run wallet.smoke --describe
|
|
122
|
+
mm-harness run wallet.import method=auto
|
|
123
|
+
|
|
124
|
+
# Use visible client UI to delete and import the wallet again.
|
|
125
|
+
mm-harness run wallet.reset-import
|
|
118
126
|
mm-harness run path/to/recipe.json market=ETH --plan
|
|
119
127
|
mm-harness run path/to/recipe.json
|
|
120
128
|
```
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
8
|
+
console.log('Usage: artifact-runtime-state.cjs <set|clear> --target <absolute-dir> --runtime-dir <relative-dir> [--source-dir <absolute-dir> --runtime-dist <absolute-dir> --provenance <absolute-file>]');
|
|
9
|
+
process.exit(0);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const [action, ...argv] = process.argv.slice(2);
|
|
13
|
+
const args = parseArgs(argv);
|
|
14
|
+
const target = requiredPath(args, 'target');
|
|
15
|
+
const runtimeDir = requiredRelative(args, 'runtime-dir');
|
|
16
|
+
|
|
17
|
+
if (action === 'clear') {
|
|
18
|
+
const directory = safeRuntimeDirectory(target, runtimeDir, false);
|
|
19
|
+
if (!directory) process.exit(0);
|
|
20
|
+
const statePath = path.join(directory, 'extension-release-artifact.json');
|
|
21
|
+
const existing = fs.lstatSync(statePath, { throwIfNoEntry: false });
|
|
22
|
+
if (!existing) process.exit(0);
|
|
23
|
+
if (!existing.isFile() || existing.isSymbolicLink()) throw invalidRuntimeState(statePath);
|
|
24
|
+
fs.unlinkSync(statePath);
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
if (action !== 'set') throw new Error('Usage: artifact-runtime-state.cjs <set|clear> --target <dir> --runtime-dir <relative> [set flags]');
|
|
28
|
+
|
|
29
|
+
const sourceDir = requiredPath(args, 'source-dir');
|
|
30
|
+
const runtimeDist = requiredPath(args, 'runtime-dist');
|
|
31
|
+
const provenancePath = requiredPath(args, 'provenance');
|
|
32
|
+
const provenance = readRegularJson(provenancePath);
|
|
33
|
+
const relativeExtensionDir = provenance.extensionDir;
|
|
34
|
+
if (
|
|
35
|
+
typeof relativeExtensionDir !== 'string' ||
|
|
36
|
+
!relativeExtensionDir ||
|
|
37
|
+
path.isAbsolute(relativeExtensionDir) ||
|
|
38
|
+
relativeExtensionDir.split(/[\\/]+/).includes('..')
|
|
39
|
+
) {
|
|
40
|
+
throw new Error('Release artifact provenance has an invalid extensionDir.');
|
|
41
|
+
}
|
|
42
|
+
const provenanceRoot = path.dirname(provenancePath);
|
|
43
|
+
const boundSource = path.resolve(provenanceRoot, relativeExtensionDir);
|
|
44
|
+
if (boundSource !== sourceDir) throw new Error('Release artifact provenance does not bind the selected extension directory.');
|
|
45
|
+
if (!/^[a-f0-9]{64}$/.test(provenance.sha256 || '')) throw new Error('Release artifact provenance has an invalid SHA-256.');
|
|
46
|
+
if (!/^[a-f0-9]{64}$/.test(provenance.treeSha256 || '')) throw new Error('Release artifact provenance has an invalid tree SHA-256.');
|
|
47
|
+
if (!fs.existsSync(path.join(sourceDir, 'manifest.json'))) throw new Error('Release artifact source manifest is missing.');
|
|
48
|
+
if (!fs.existsSync(path.join(runtimeDist, 'manifest.json'))) throw new Error('Release artifact runtime manifest is missing.');
|
|
49
|
+
|
|
50
|
+
const state = {
|
|
51
|
+
schemaVersion: 1,
|
|
52
|
+
sourceDir,
|
|
53
|
+
runtimeDist,
|
|
54
|
+
provenancePath,
|
|
55
|
+
sha256: provenance.sha256,
|
|
56
|
+
treeSha256: provenance.treeSha256,
|
|
57
|
+
expectedVersion: provenance.expectedVersion,
|
|
58
|
+
manifestVersion: provenance.manifestVersion,
|
|
59
|
+
recordedAt: new Date().toISOString(),
|
|
60
|
+
};
|
|
61
|
+
const directory = safeRuntimeDirectory(target, runtimeDir, true);
|
|
62
|
+
const statePath = path.join(directory, 'extension-release-artifact.json');
|
|
63
|
+
const existing = fs.lstatSync(statePath, { throwIfNoEntry: false });
|
|
64
|
+
if (existing && (!existing.isFile() || existing.isSymbolicLink())) throw invalidRuntimeState(statePath);
|
|
65
|
+
const temporary = `${statePath}.${process.pid}.tmp`;
|
|
66
|
+
fs.writeFileSync(temporary, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600, flag: 'wx' });
|
|
67
|
+
fs.renameSync(temporary, statePath);
|
|
68
|
+
|
|
69
|
+
function parseArgs(values) {
|
|
70
|
+
const parsed = {};
|
|
71
|
+
for (let index = 0; index < values.length; index += 2) {
|
|
72
|
+
const flag = values[index];
|
|
73
|
+
const value = values[index + 1];
|
|
74
|
+
if (!flag?.startsWith('--') || value === undefined) throw new Error(`Invalid argument: ${flag || ''}`);
|
|
75
|
+
parsed[flag.slice(2)] = value;
|
|
76
|
+
}
|
|
77
|
+
return parsed;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function requiredPath(values, key) {
|
|
81
|
+
if (!values[key] || !path.isAbsolute(values[key])) throw new Error(`--${key} must be an absolute path.`);
|
|
82
|
+
return path.resolve(values[key]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function requiredRelative(values, key) {
|
|
86
|
+
const value = values[key];
|
|
87
|
+
const parts = value?.split(/[\\/]+/) ?? [];
|
|
88
|
+
if (!value || path.isAbsolute(value) || parts.some((part) => !part || part === '.' || part === '..')) {
|
|
89
|
+
throw new Error(`--${key} must be a safe relative path.`);
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function safeRuntimeDirectory(target, runtimeDir, create) {
|
|
95
|
+
const targetStat = fs.lstatSync(target, { throwIfNoEntry: false });
|
|
96
|
+
if (!targetStat?.isDirectory() || targetStat.isSymbolicLink()) {
|
|
97
|
+
throw invalidRuntimeState(path.join(target, runtimeDir));
|
|
98
|
+
}
|
|
99
|
+
let current = target;
|
|
100
|
+
for (const component of runtimeDir.split(/[\\/]+/)) {
|
|
101
|
+
current = path.join(current, component);
|
|
102
|
+
const stat = fs.lstatSync(current, { throwIfNoEntry: false });
|
|
103
|
+
if (!stat) {
|
|
104
|
+
if (!create) return null;
|
|
105
|
+
fs.mkdirSync(current, { mode: 0o700 });
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) throw invalidRuntimeState(current);
|
|
109
|
+
}
|
|
110
|
+
const realTarget = fs.realpathSync(target);
|
|
111
|
+
const realRuntime = fs.realpathSync(current);
|
|
112
|
+
if (realRuntime !== realTarget && !realRuntime.startsWith(`${realTarget}${path.sep}`)) {
|
|
113
|
+
throw invalidRuntimeState(current);
|
|
114
|
+
}
|
|
115
|
+
return current;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function invalidRuntimeState(file) {
|
|
119
|
+
return new Error(`Release artifact runtime state path is unsafe: ${file}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function readRegularJson(file) {
|
|
123
|
+
const stat = fs.lstatSync(file);
|
|
124
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size <= 0 || stat.size > 64 * 1024) {
|
|
125
|
+
throw new Error(`Release artifact provenance must be a regular JSON file: ${file}`);
|
|
126
|
+
}
|
|
127
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
128
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const https = require('node:https');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const args = {};
|
|
9
|
+
for (let index = 2; index < process.argv.length; index += 1) {
|
|
10
|
+
const key = process.argv[index];
|
|
11
|
+
if (key === '--help' || key === '-h') {
|
|
12
|
+
process.stdout.write('Usage: check-infura-readiness.cjs --target <metamask-extension> --runtime-dist <dist>\n');
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
if (!key.startsWith('--') || index + 1 >= process.argv.length) {
|
|
16
|
+
throw new Error(`invalid argument: ${key}`);
|
|
17
|
+
}
|
|
18
|
+
args[key.slice(2)] = process.argv[++index];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (const key of ['target', 'runtime-dist']) {
|
|
22
|
+
if (!args[key]) throw new Error(`--${key} is required`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const target = path.resolve(args.target);
|
|
26
|
+
const runtimeDist = path.resolve(args['runtime-dist']);
|
|
27
|
+
const config = require('./lib/product-config.cjs');
|
|
28
|
+
|
|
29
|
+
async function main() {
|
|
30
|
+
const resolution = config.resolveExtensionInfuraProjectId(target, process.env);
|
|
31
|
+
if (resolution.kind !== 'configured') {
|
|
32
|
+
fail('the effective Extension product configuration has no usable Infura project ID');
|
|
33
|
+
}
|
|
34
|
+
if (!compiledScriptsContain(runtimeDist, resolution.value)) {
|
|
35
|
+
fail('the compiled Extension scripts do not match the effective Infura project ID');
|
|
36
|
+
}
|
|
37
|
+
for (const host of ['mainnet.infura.io', 'linea-mainnet.infura.io']) {
|
|
38
|
+
const response = await rpcProbe(host, resolution.value);
|
|
39
|
+
if (response.statusCode < 200 || response.statusCode >= 300 || response.jsonRpcResult !== true) {
|
|
40
|
+
fail(`${host} rejected the effective Infura project ID (HTTP ${response.statusCode})`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
process.stdout.write('Infura readiness preflight passed for Ethereum and Linea.\n');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function compiledScriptsContain(root, value) {
|
|
47
|
+
const pending = [root];
|
|
48
|
+
while (pending.length > 0) {
|
|
49
|
+
const current = pending.pop();
|
|
50
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
51
|
+
const file = path.join(current, entry.name);
|
|
52
|
+
if (entry.isDirectory()) pending.push(file);
|
|
53
|
+
else if (entry.isFile() && entry.name.endsWith('.js') && fs.readFileSync(file, 'utf8').includes(value)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function rpcProbe(host, projectId) {
|
|
62
|
+
const body = JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_chainId', params: [] });
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const request = https.request({
|
|
65
|
+
hostname: host,
|
|
66
|
+
path: `/v3/${encodeURIComponent(projectId)}`,
|
|
67
|
+
method: 'POST',
|
|
68
|
+
timeout: 10_000,
|
|
69
|
+
headers: {
|
|
70
|
+
'content-type': 'application/json',
|
|
71
|
+
'content-length': Buffer.byteLength(body),
|
|
72
|
+
},
|
|
73
|
+
}, (response) => {
|
|
74
|
+
let text = '';
|
|
75
|
+
response.setEncoding('utf8');
|
|
76
|
+
response.on('data', (chunk) => { text += chunk; });
|
|
77
|
+
response.on('end', () => {
|
|
78
|
+
let jsonRpcResult = false;
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(text);
|
|
81
|
+
jsonRpcResult = typeof parsed.result === 'string' && parsed.result.startsWith('0x');
|
|
82
|
+
} catch {
|
|
83
|
+
jsonRpcResult = false;
|
|
84
|
+
}
|
|
85
|
+
resolve({ statusCode: response.statusCode || 0, jsonRpcResult });
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
request.on('timeout', () => request.destroy(new Error(`${host} timed out`)));
|
|
89
|
+
request.on('error', reject);
|
|
90
|
+
request.end(body);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function fail(detail) {
|
|
95
|
+
process.stderr.write(
|
|
96
|
+
`EVM_RPC_UNREACHABLE: Infura readiness preflight failed before browser launch: ${detail}.\n` +
|
|
97
|
+
`Next: stop the stale watcher, confirm ${path.join(target, '.metamaskrc')} has a real INFURA_PROJECT_ID, then rerun mm-harness launch --verify --target ${JSON.stringify(target)}.\n`,
|
|
98
|
+
);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
main().catch((error) => fail(error instanceof Error ? error.message : String(error)));
|
|
@@ -102,7 +102,9 @@ copyFile(path.join(runnerDir, 'adapters/extension/build-lavamoat.sh'), path.join
|
|
|
102
102
|
copyFile(path.join(runnerDir, 'adapters/extension/launch-webpack.cjs'), path.join(harnessDir, 'scripts/launch-webpack.cjs'));
|
|
103
103
|
copyFile(path.join(runnerDir, 'adapters/extension/sync-webpack-dist.cjs'), path.join(harnessDir, 'scripts/sync-webpack-dist.cjs'));
|
|
104
104
|
copyFile(path.join(runnerDir, 'adapters/extension/configure-runtime-manifest.cjs'), path.join(harnessDir, 'scripts/configure-runtime-manifest.cjs'));
|
|
105
|
+
copyFile(path.join(runnerDir, 'adapters/extension/check-infura-readiness.cjs'), path.join(harnessDir, 'scripts/check-infura-readiness.cjs'));
|
|
105
106
|
copyFile(path.join(runnerDir, 'adapters/extension/stamp-runtime-title.cjs'), path.join(harnessDir, 'scripts/stamp-runtime-title.cjs'));
|
|
107
|
+
copyFile(path.join(runnerDir, 'adapters/extension/artifact-runtime-state.cjs'), path.join(harnessDir, 'scripts/artifact-runtime-state.cjs'));
|
|
106
108
|
copyFile(path.join(runnerDir, 'adapters/extension/stop-viewers.sh'), path.join(harnessDir, 'scripts/stop-viewers.sh'));
|
|
107
109
|
copyFile(path.join(runnerDir, 'adapters/extension/snapshot-dist.sh'), path.join(harnessDir, 'scripts/snapshot-dist.sh'));
|
|
108
110
|
copyFile(path.join(runnerDir, 'adapters/extension/pin-remote-flags.cjs'), path.join(harnessDir, 'scripts/pin-remote-flags.cjs'));
|
|
@@ -72,6 +72,7 @@ if (args['stop-only'] === undefined) {
|
|
|
72
72
|
// injects its own already-isolated profile via runtime-context, so this only trips
|
|
73
73
|
// on a genuinely personal/shared path — protecting the user's browser data.
|
|
74
74
|
assertIsolatedProfile(args.profile);
|
|
75
|
+
if (args['reset-profile'] !== undefined) assertResettableProfile(args.profile, runtimeDir);
|
|
75
76
|
|
|
76
77
|
fs.mkdirSync(args.profile, { recursive: true });
|
|
77
78
|
fs.mkdirSync(path.dirname(args['chrome-log']), { recursive: true });
|
|
@@ -301,6 +302,27 @@ function assertIsolatedProfile(profile) {
|
|
|
301
302
|
}
|
|
302
303
|
}
|
|
303
304
|
|
|
305
|
+
function assertResettableProfile(profile, expectedRuntimeDir) {
|
|
306
|
+
const resolved = path.resolve(profile);
|
|
307
|
+
const runtime = path.resolve(expectedRuntimeDir);
|
|
308
|
+
if (!resolved.startsWith(`${runtime}${path.sep}`)) {
|
|
309
|
+
throw new Error(`Refusing to reset --profile ${resolved}: it must be a dedicated child of ${runtime}.`);
|
|
310
|
+
}
|
|
311
|
+
const runtimeStat = fs.lstatSync(runtime);
|
|
312
|
+
if (!runtimeStat.isDirectory() || runtimeStat.isSymbolicLink()) {
|
|
313
|
+
throw new Error(`Refusing to reset --profile: runtime directory is not a regular directory: ${runtime}.`);
|
|
314
|
+
}
|
|
315
|
+
let current = runtime;
|
|
316
|
+
for (const segment of path.relative(runtime, resolved).split(path.sep)) {
|
|
317
|
+
current = path.join(current, segment);
|
|
318
|
+
if (!fs.existsSync(current)) break;
|
|
319
|
+
const stat = fs.lstatSync(current);
|
|
320
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
321
|
+
throw new Error(`Refusing to reset --profile: path contains an unsafe entry: ${current}.`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
304
326
|
// Proof of ownership: a process is ours iff its command line loads the exact
|
|
305
327
|
// --user-data-dir we launch with. If the process can't be inspected we treat it
|
|
306
328
|
// as NOT ours (fail safe — never assume ownership of an opaque process).
|
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
# Purpose:
|
|
7
7
|
# Builds the per-run prepare command from the named feature scripts
|
|
8
8
|
# (watch, dist snapshot, fixture seed, detached chrome, CDP poll), then
|
|
9
|
-
# runs launch.sh and verify.sh under a
|
|
9
|
+
# runs launch.sh and, unless --launch-only is set, verify.sh under a
|
|
10
|
+
# timestamped artifact dir.
|
|
10
11
|
#
|
|
11
12
|
# Inputs (flags / env):
|
|
12
13
|
# --target <metamask-extension> (default $PWD)
|
|
13
14
|
# --cdp-port <port> (optional; inferred from checkout context/pool when omitted)
|
|
14
15
|
# --launch-existing-dist | --start-watch | --build-lavamoat | --prepare-cmd <cmd>
|
|
15
16
|
# (env RECIPE_HARNESS_EXTENSION_LAUNCH_CMD)
|
|
17
|
+
# --launch-only (skip recipe verification; used by fixture application while
|
|
18
|
+
# its checkout lock is held)
|
|
16
19
|
# --dist-dir <rel> (default dist/chrome), --chrome-user-data-dir <dir>,
|
|
17
20
|
# --remote-flag <KEY=VARIANT[,KEY=VARIANT...]> (pins manifest _flags into the
|
|
18
21
|
# ephemeral runtime-dist snapshot before launch; omit for default behavior)
|
|
@@ -40,10 +43,14 @@ PREPARE_CMD="${RECIPE_HARNESS_EXTENSION_LAUNCH_CMD:-}"
|
|
|
40
43
|
LAUNCH_EXISTING_DIST=false
|
|
41
44
|
START_WATCH=false
|
|
42
45
|
BUILD_LAVAMOAT=false
|
|
46
|
+
EXTERNAL_ARTIFACT=false
|
|
47
|
+
ARTIFACT_PROVENANCE=""
|
|
48
|
+
RESET_PROFILE=true
|
|
43
49
|
DIST_DIR="dist/chrome"
|
|
44
50
|
CHROME_USER_DATA_DIR="${CHROME_USER_DATA_DIR:-}"
|
|
45
51
|
REMOTE_FLAGS=""
|
|
46
52
|
REMOTE_FLAGS_APPLIED=false
|
|
53
|
+
RUN_VERIFY=true
|
|
47
54
|
while [ "$#" -gt 0 ]; do
|
|
48
55
|
case "$1" in
|
|
49
56
|
--target) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; TARGET="$2"; shift 2 ;;
|
|
@@ -52,13 +59,17 @@ while [ "$#" -gt 0 ]; do
|
|
|
52
59
|
--artifacts-dir) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; ARTIFACTS="$2"; shift 2 ;;
|
|
53
60
|
--prepare-cmd) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; PREPARE_CMD="$2"; shift 2 ;;
|
|
54
61
|
--launch-existing-dist) LAUNCH_EXISTING_DIST=true; shift ;;
|
|
62
|
+
--external-artifact) EXTERNAL_ARTIFACT=true; LAUNCH_EXISTING_DIST=true; shift ;;
|
|
63
|
+
--artifact-provenance) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; ARTIFACT_PROVENANCE="$2"; shift 2 ;;
|
|
64
|
+
--preserve-profile) RESET_PROFILE=false; shift ;;
|
|
55
65
|
--start-url) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; START_URL="$2"; shift 2 ;;
|
|
56
66
|
--start-watch|--start-test-watch) START_WATCH=true; LAUNCH_EXISTING_DIST=true; shift ;;
|
|
57
67
|
--build-lavamoat) BUILD_LAVAMOAT=true; LAUNCH_EXISTING_DIST=true; shift ;;
|
|
68
|
+
--launch-only) RUN_VERIFY=false; shift ;;
|
|
58
69
|
--dist-dir) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; DIST_DIR="$2"; shift 2 ;;
|
|
59
70
|
--chrome-user-data-dir) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; CHROME_USER_DATA_DIR="$2"; shift 2 ;;
|
|
60
71
|
--remote-flag) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; REMOTE_FLAGS="$2"; shift 2 ;;
|
|
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 ;;
|
|
72
|
+
-h|--help) echo "Usage: live.sh [--target <metamask-extension>] [--out <recipes-dir>] [--cdp-port <port>] [--launch-existing-dist|--start-watch|--build-lavamoat|--external-artifact --dist-dir <absolute-dir> --artifact-provenance <absolute-file> [--preserve-profile]|--prepare-cmd <cmd>] [--launch-only] [--dist-dir dist/chrome] [--remote-flag KEY=VARIANT] [--artifacts-dir <dir>]"; exit 0 ;;
|
|
62
73
|
*) echo "Unknown arg: $1" >&2; exit 2 ;;
|
|
63
74
|
esac
|
|
64
75
|
done
|
|
@@ -67,6 +78,24 @@ if $BUILD_LAVAMOAT && $START_WATCH; then
|
|
|
67
78
|
echo "--build-lavamoat cannot be combined with --start-watch" >&2
|
|
68
79
|
exit 2
|
|
69
80
|
fi
|
|
81
|
+
if $EXTERNAL_ARTIFACT && { $BUILD_LAVAMOAT || $START_WATCH || [ -n "$REMOTE_FLAGS" ]; }; then
|
|
82
|
+
echo "--external-artifact cannot be combined with build, watch, or remote-flag options" >&2
|
|
83
|
+
exit 2
|
|
84
|
+
fi
|
|
85
|
+
if $EXTERNAL_ARTIFACT && [ ! -f "$ARTIFACT_PROVENANCE" ]; then
|
|
86
|
+
echo "--external-artifact requires --artifact-provenance <file>" >&2
|
|
87
|
+
exit 2
|
|
88
|
+
fi
|
|
89
|
+
if ! $RESET_PROFILE && ! $EXTERNAL_ARTIFACT; then
|
|
90
|
+
echo "--preserve-profile is only valid for a verified external artifact" >&2
|
|
91
|
+
exit 2
|
|
92
|
+
fi
|
|
93
|
+
if $EXTERNAL_ARTIFACT; then
|
|
94
|
+
case "$ARTIFACT_PROVENANCE" in
|
|
95
|
+
/*) ;;
|
|
96
|
+
*) echo "--artifact-provenance must be an absolute file path" >&2; exit 2 ;;
|
|
97
|
+
esac
|
|
98
|
+
fi
|
|
70
99
|
|
|
71
100
|
case "$CHROME_USER_DATA_DIR" in
|
|
72
101
|
""|/*) ;;
|
|
@@ -115,6 +144,12 @@ if [ -z "$VERIFY_SH" ]; then
|
|
|
115
144
|
fi
|
|
116
145
|
RUNTIME_DIR="$(recipe_runtime_dir)"
|
|
117
146
|
RUNTIME_DIST_DIR="${RECIPE_RUNTIME_DIST_DIR:-runtime-dist}"
|
|
147
|
+
case "$RUNTIME_DIST_DIR" in
|
|
148
|
+
""|.|..|*/*|*[!A-Za-z0-9._-]*)
|
|
149
|
+
echo "RECIPE_RUNTIME_DIST_DIR must be one safe directory name: '$RUNTIME_DIST_DIR'" >&2
|
|
150
|
+
exit 2
|
|
151
|
+
;;
|
|
152
|
+
esac
|
|
118
153
|
CHROME_PROFILE_NAME="${RECIPE_CHROME_PROFILE_NAME:-chrome-profile}"
|
|
119
154
|
# Runner bin (installed wrapper → source runner). Used by the watch prepare path
|
|
120
155
|
# to defer the cache-clear DECISION to `runtime-decision` (single source) and to
|
|
@@ -140,21 +175,36 @@ if [ -d "$LIVE_ROOT" ]; then
|
|
|
140
175
|
fi
|
|
141
176
|
|
|
142
177
|
if $LAUNCH_EXISTING_DIST && [ -z "$PREPARE_CMD" ]; then
|
|
143
|
-
|
|
178
|
+
if $EXTERNAL_ARTIFACT; then
|
|
179
|
+
case "$DIST_DIR" in
|
|
180
|
+
/*) DIST_ABS="$DIST_DIR" ;;
|
|
181
|
+
*) echo "--external-artifact requires an absolute --dist-dir" >&2; exit 2 ;;
|
|
182
|
+
esac
|
|
183
|
+
else
|
|
184
|
+
DIST_ABS="$TARGET/$DIST_DIR"
|
|
185
|
+
fi
|
|
144
186
|
# Stable beside the profile: the reused profile registers the unpacked
|
|
145
187
|
# extension by PATH; a per-run snapshot path gets pruned and the next launch
|
|
146
188
|
# loads nothing ("No EIP-6963 Provider Detected").
|
|
147
|
-
|
|
189
|
+
RUNTIME_ROOT_ABS="$TARGET/$RUNTIME_DIR"
|
|
190
|
+
RUNTIME_DIST_ABS="$RUNTIME_ROOT_ABS/$RUNTIME_DIST_DIR"
|
|
148
191
|
# Stable per-checkout profile: successive runs recognize their own Chrome
|
|
149
192
|
# (ownership guard matches on --user-data-dir), take it over cleanly, and the
|
|
150
193
|
# seeded wallet persists across relaunches. A per-run profile made every
|
|
151
194
|
# relaunch "foreign" and let live/ pruning delete profiles still in use.
|
|
152
195
|
PROFILE_ABS="${CHROME_USER_DATA_DIR:-$TARGET/$RUNTIME_DIR/$CHROME_PROFILE_NAME}"
|
|
196
|
+
case "$PROFILE_ABS" in
|
|
197
|
+
"$TARGET/$RUNTIME_DIR/"*) ;;
|
|
198
|
+
*) echo "--chrome-user-data-dir must be a dedicated child of $TARGET/$RUNTIME_DIR" >&2; exit 2 ;;
|
|
199
|
+
esac
|
|
153
200
|
FIXTURE_STATE_ABS="$ARTIFACTS/fixture-state.json"
|
|
154
201
|
FIXTURE_VALIDATION_ABS="$ARTIFACTS/logs/fixture-account-parity.json"
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
202
|
+
WALLET_FIXTURE_ABS=""
|
|
203
|
+
if ! $EXTERNAL_ARTIFACT; then
|
|
204
|
+
# Wallet fixture resolution chain + provenance live in seed-fixture.sh.
|
|
205
|
+
WALLET_FIXTURE_ABS="$(bash "$SCRIPT_DIR/seed-fixture.sh" resolve --target "$TARGET" --cdp-port "$CDP_PORT" --source-out "$ARTIFACTS/logs/fixture-source.json")"
|
|
206
|
+
fi
|
|
207
|
+
if ! $EXTERNAL_ARTIFACT && [ ! -f "$ARTIFACTS/logs/fixture-source.json" ]; then
|
|
158
208
|
# resolve failed before writing provenance (invalid RECIPE_WALLET_FIXTURE);
|
|
159
209
|
# keep the documented missing-provenance artifact.
|
|
160
210
|
node - "$ARTIFACTS/logs/fixture-source.json" "" <<'NODE'
|
|
@@ -170,6 +220,7 @@ NODE
|
|
|
170
220
|
fi
|
|
171
221
|
mkdir -p "$PROFILE_ABS"
|
|
172
222
|
quoted_dist="$(printf '%q' "$DIST_ABS")"
|
|
223
|
+
quoted_runtime_root="$(printf '%q' "$RUNTIME_ROOT_ABS")"
|
|
173
224
|
quoted_runtime_dist="$(printf '%q' "$RUNTIME_DIST_ABS")"
|
|
174
225
|
quoted_profile="$(printf '%q' "$PROFILE_ABS")"
|
|
175
226
|
quoted_seed_fixture="$(printf '%q' "$SCRIPT_DIR/seed-fixture.sh")"
|
|
@@ -177,8 +228,10 @@ NODE
|
|
|
177
228
|
quoted_build_lavamoat="$(printf '%q' "$SCRIPT_DIR/build-lavamoat.sh")"
|
|
178
229
|
quoted_snapshot_dist="$(printf '%q' "$SCRIPT_DIR/snapshot-dist.sh")"
|
|
179
230
|
quoted_configure_manifest="$(printf '%q' "$SCRIPT_DIR/configure-runtime-manifest.cjs")"
|
|
231
|
+
quoted_check_infura="$(printf '%q' "$SCRIPT_DIR/check-infura-readiness.cjs")"
|
|
180
232
|
quoted_stamp_title="$(printf '%q' "$SCRIPT_DIR/stamp-runtime-title.cjs")"
|
|
181
233
|
quoted_chrome_launcher="$(printf '%q' "$SCRIPT_DIR/launch-browser.cjs")"
|
|
234
|
+
quoted_artifact_state="$(printf '%q' "$SCRIPT_DIR/artifact-runtime-state.cjs")"
|
|
182
235
|
quoted_fixture_state="$(printf '%q' "$FIXTURE_STATE_ABS")"
|
|
183
236
|
quoted_fixture_validation="$(printf '%q' "$FIXTURE_VALIDATION_ABS")"
|
|
184
237
|
quoted_extension_id_file="$(printf '%q' "$TARGET/$RUNTIME_DIR/extension.id")"
|
|
@@ -251,16 +304,37 @@ NODE
|
|
|
251
304
|
quoted_chrome_log="$(printf '%q' "$ARTIFACTS/logs/chrome.log")"
|
|
252
305
|
quoted_chrome_pid="$(printf '%q' "$ARTIFACTS/logs/chrome.pid")"
|
|
253
306
|
prepare_parts=()
|
|
307
|
+
if $EXTERNAL_ARTIFACT; then
|
|
308
|
+
quoted_artifact_provenance="$(printf '%q' "$ARTIFACT_PROVENANCE")"
|
|
309
|
+
else
|
|
310
|
+
node "$SCRIPT_DIR/artifact-runtime-state.cjs" clear --target "$TARGET" --runtime-dir "$RUNTIME_DIR"
|
|
311
|
+
fi
|
|
254
312
|
if $START_WATCH; then
|
|
255
313
|
prepare_parts+=("bash ${quoted_start_watch} --runtime-dir ${quoted_runtime_dir} --runner-bin ${quoted_runner}")
|
|
256
314
|
fi
|
|
257
315
|
if $BUILD_LAVAMOAT; then
|
|
258
316
|
prepare_parts+=("bash ${quoted_build_lavamoat} --target ${quoted_target} --runtime-dir ${quoted_runtime_dir}")
|
|
259
317
|
fi
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
318
|
+
if ! $EXTERNAL_ARTIFACT; then
|
|
319
|
+
prepare_parts+=("node ${quoted_check_infura} --target ${quoted_target} --runtime-dist ${quoted_dist}")
|
|
320
|
+
fi
|
|
321
|
+
if $EXTERNAL_ARTIFACT; then
|
|
322
|
+
prepare_parts+=("bash ${quoted_snapshot_dist} --dist ${quoted_dist} --target ${quoted_target} --runtime-root ${quoted_runtime_root} --runtime-dist ${quoted_runtime_dist} --exact")
|
|
323
|
+
else
|
|
324
|
+
prepare_parts+=("bash ${quoted_snapshot_dist} --dist ${quoted_dist} --target ${quoted_target} --runtime-root ${quoted_runtime_root} --runtime-dist ${quoted_runtime_dist}")
|
|
325
|
+
fi
|
|
326
|
+
if $EXTERNAL_ARTIFACT; then
|
|
327
|
+
prepare_parts+=("node ${quoted_artifact_state} set --target ${quoted_target} --runtime-dir ${quoted_runtime_dir} --source-dir ${quoted_dist} --runtime-dist ${quoted_runtime_dist} --provenance ${quoted_artifact_provenance}")
|
|
328
|
+
fi
|
|
329
|
+
if ! $EXTERNAL_ARTIFACT; then
|
|
330
|
+
prepare_parts+=("node ${quoted_configure_manifest} --target ${quoted_target} --manifest ${quoted_runtime_dist}/manifest.json")
|
|
331
|
+
fi
|
|
332
|
+
reset_profile_arg=""
|
|
333
|
+
$RESET_PROFILE && reset_profile_arg=" --reset-profile 1"
|
|
334
|
+
prepare_parts+=("node ${quoted_chrome_launcher} --stop-only 1${reset_profile_arg} --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}")
|
|
335
|
+
if ! $EXTERNAL_ARTIFACT; then
|
|
336
|
+
prepare_parts+=("node ${quoted_stamp_title} --target ${quoted_target} --runtime-dist ${quoted_runtime_dist} --runtime-dir ${quoted_runtime_dir}")
|
|
337
|
+
fi
|
|
264
338
|
# Optional A/B feature-flag pinning: patch the ephemeral snapshot manifest so
|
|
265
339
|
# manifest._flags wins over the fetched ClientConfigApi value. No-op unless
|
|
266
340
|
# --remote-flag was passed; the snapshot is re-created each run, so no revert.
|
|
@@ -295,13 +369,15 @@ echo "Extension live validation command:"
|
|
|
295
369
|
display_args=(mm-harness runtime-launch --adapter extension --target "$TARGET" --cdp-port "$CDP_PORT")
|
|
296
370
|
$START_WATCH && display_args+=(--start-watch)
|
|
297
371
|
$BUILD_LAVAMOAT && display_args+=(--build-lavamoat)
|
|
372
|
+
$EXTERNAL_ARTIFACT && display_args+=(--external-artifact --dist-dir "$DIST_DIR" --artifact-provenance "$ARTIFACT_PROVENANCE")
|
|
373
|
+
$RESET_PROFILE || display_args+=(--preserve-profile)
|
|
298
374
|
[ -n "$REMOTE_FLAGS" ] && display_args+=(--remote-flag "$REMOTE_FLAGS")
|
|
299
375
|
[ -n "$CHROME_USER_DATA_DIR" ] && display_args+=(--chrome-user-data-dir "$CHROME_USER_DATA_DIR")
|
|
300
376
|
printf ' '
|
|
301
377
|
printf '%q ' "${display_args[@]}"
|
|
302
378
|
printf '\n'
|
|
303
379
|
echo "Launch artifacts: $ARTIFACTS/launch"
|
|
304
|
-
echo "Verify artifacts: $ARTIFACTS/verify"
|
|
380
|
+
$RUN_VERIFY && echo "Verify artifacts: $ARTIFACTS/verify"
|
|
305
381
|
|
|
306
382
|
launch_args=(--target "$TARGET" --cdp-port "$CDP_PORT" --artifacts-dir "$ARTIFACTS/launch")
|
|
307
383
|
[ -n "$PREPARE_CMD" ] && launch_args+=(--prepare-cmd "$PREPARE_CMD")
|
|
@@ -312,18 +388,21 @@ set +e
|
|
|
312
388
|
launch_status=$?
|
|
313
389
|
set -e
|
|
314
390
|
|
|
315
|
-
verify_status=
|
|
316
|
-
if [ "$launch_status" -eq 0 ]; then
|
|
391
|
+
verify_status=0
|
|
392
|
+
if [ "$launch_status" -eq 0 ] && $RUN_VERIFY; then
|
|
317
393
|
echo "[recipe-harness] phase 2/2: live verify (readiness + smoke recipe)" >&2
|
|
318
394
|
set +e
|
|
319
395
|
"$VERIFY_SH" --target "$TARGET" --out "$OUT" --cdp-port "$CDP_PORT" --artifacts-dir "$ARTIFACTS/verify"
|
|
320
396
|
verify_status=$?
|
|
321
397
|
set -e
|
|
322
|
-
|
|
398
|
+
elif [ "$launch_status" -ne 0 ]; then
|
|
399
|
+
verify_status=1
|
|
323
400
|
echo "Skipping Extension live verify because launch failed; see $ARTIFACTS/launch/summary.json" >&2
|
|
401
|
+
else
|
|
402
|
+
echo "Skipping Extension live verify for launch-only fixture application." >&2
|
|
324
403
|
fi
|
|
325
404
|
|
|
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'
|
|
405
|
+
TARGET_FOR_SUMMARY="$TARGET" ARTIFACTS_FOR_SUMMARY="$ARTIFACTS" CDP_PORT_FOR_SUMMARY="$CDP_PORT" LAUNCH_STATUS="$launch_status" VERIFY_STATUS="$verify_status" RUN_VERIFY="$RUN_VERIFY" LAUNCH_EXISTING_DIST="$LAUNCH_EXISTING_DIST" START_WATCH="$START_WATCH" BUILD_LAVAMOAT="$BUILD_LAVAMOAT" EXTERNAL_ARTIFACT="$EXTERNAL_ARTIFACT" node <<'NODE'
|
|
327
406
|
const fs = require('fs');
|
|
328
407
|
const path = require('path');
|
|
329
408
|
const artifacts = process.env.ARTIFACTS_FOR_SUMMARY;
|
|
@@ -331,6 +410,7 @@ const launchSummary = path.join(artifacts, 'launch', 'summary.json');
|
|
|
331
410
|
const verifySummary = path.join(artifacts, 'verify', 'summary.json');
|
|
332
411
|
const launchStatus = Number(process.env.LAUNCH_STATUS);
|
|
333
412
|
const verifyStatus = Number(process.env.VERIFY_STATUS);
|
|
413
|
+
const runVerify = process.env.RUN_VERIFY === 'true';
|
|
334
414
|
fs.writeFileSync(path.join(artifacts, 'summary.json'), `${JSON.stringify({
|
|
335
415
|
adapter: 'extension',
|
|
336
416
|
action: 'live',
|
|
@@ -340,10 +420,15 @@ fs.writeFileSync(path.join(artifacts, 'summary.json'), `${JSON.stringify({
|
|
|
340
420
|
launchExistingDist: process.env.LAUNCH_EXISTING_DIST === 'true',
|
|
341
421
|
startWatch: process.env.START_WATCH === 'true',
|
|
342
422
|
buildLavaMoat: process.env.BUILD_LAVAMOAT === 'true',
|
|
423
|
+
externalArtifact: process.env.EXTERNAL_ARTIFACT === 'true',
|
|
343
424
|
launch: { exitCode: launchStatus, summaryPath: fs.existsSync(launchSummary) ? launchSummary : null },
|
|
344
|
-
verify:
|
|
425
|
+
verify: runVerify
|
|
426
|
+
? { status: verifyStatus === 0 ? 'pass' : 'fail', exitCode: verifyStatus, summaryPath: fs.existsSync(verifySummary) ? verifySummary : null }
|
|
427
|
+
: { status: 'skipped', exitCode: null, summaryPath: null },
|
|
345
428
|
easyCommand: `mm-harness launch --verify --target <repo>`,
|
|
346
|
-
note:
|
|
429
|
+
note: runVerify
|
|
430
|
+
? 'Runs launch then live verify so a developer can validate browser startup, CDP readiness, recipe bridge, screenshots/fallback classification, and sample recipes from one runner-owned command.'
|
|
431
|
+
: 'Runs launch validation only for fixture application while the checkout lock is held.',
|
|
347
432
|
generatedAt: new Date().toISOString(),
|
|
348
433
|
}, null, 2)}\n`);
|
|
349
434
|
NODE
|