@deeeed/metamask-harness 0.36.0 → 0.38.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.
Files changed (140) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/adapters/extension/artifact-runtime-state.cjs +138 -0
  3. package/adapters/extension/inject.mjs +2 -0
  4. package/adapters/extension/launch-browser.cjs +24 -0
  5. package/adapters/extension/live.sh +82 -12
  6. package/adapters/extension/readiness.mjs +77 -36
  7. package/adapters/extension/snapshot-dist.sh +88 -3
  8. package/adapters/extension/verify.sh +6 -2
  9. package/adapters/manifest.json +17 -1
  10. package/adapters/shared/log-tui.mjs +1 -1
  11. package/dist/adapters/extension/artifact-integrity.js +38 -0
  12. package/dist/adapters/extension/extension-id.js +23 -4
  13. package/dist/adapters/extension/release-artifact.js +386 -0
  14. package/dist/adapters/extension/runtime-decision.js +164 -20
  15. package/dist/adapters/extension/runtime.js +169 -3
  16. package/dist/adapters/mobile/provision.js +38 -3
  17. package/dist/adapters/mobile/release-artifact-state.js +124 -0
  18. package/dist/adapters/mobile/release-artifact.js +295 -0
  19. package/dist/adapters.js +78 -6
  20. package/dist/app-lifecycle.js +4 -3
  21. package/dist/command-contract.js +16 -0
  22. package/dist/commands/call.js +6 -2
  23. package/dist/commands/launch/extension.js +74 -9
  24. package/dist/commands/launch/index.js +3 -0
  25. package/dist/commands/launch/mobile.js +2 -0
  26. package/dist/commands/provision.js +2 -0
  27. package/dist/commands/run-engine.js +109 -14
  28. package/dist/commands/run.js +4 -2
  29. package/dist/commands/runtime-launch.js +178 -10
  30. package/dist/heal-bounds.js +1 -1
  31. package/dist/live-adapter-contract.js +3 -1
  32. package/dist/metamask-action-validation.js +47 -1
  33. package/dist/mm-harness-cli.js +31 -1
  34. package/dist/recipe-security.js +40 -0
  35. package/dist/run-diagnostics.js +1 -1
  36. package/dist/runner.js +3 -0
  37. package/docs/RELEASE-QA-CAPABILITY-MAP.md +125 -0
  38. package/library/actions/extension/assets/finish_send.mjs +128 -0
  39. package/library/actions/extension/assets/import_custom_token.mjs +227 -0
  40. package/library/actions/extension/assets/prepare_send.mjs +53 -0
  41. package/library/actions/extension/assets/read_details.mjs +57 -0
  42. package/library/actions/extension/assets/read_visible_state.mjs +50 -0
  43. package/library/actions/extension/assets/review_send.mjs +23 -0
  44. package/library/actions/extension/assets/select_network_scope.mjs +30 -0
  45. package/library/actions/extension/assets/send.mjs +104 -0
  46. package/library/actions/extension/assets/set_custom_gas.mjs +56 -0
  47. package/library/actions/extension/assets/set_token_visibility.mjs +129 -0
  48. package/library/actions/extension/assets/verify_sorting.mjs +89 -0
  49. package/library/actions/extension/networks/add_chainlist.mjs +94 -0
  50. package/library/actions/extension/networks/add_custom.mjs +123 -0
  51. package/library/actions/extension/networks/custom_network.mjs +103 -0
  52. package/library/actions/extension/networks/read_visible_state.mjs +50 -0
  53. package/library/actions/extension/networks/remove_custom.mjs +68 -0
  54. package/library/actions/extension/perps/close_orders.mjs +1 -0
  55. package/library/actions/extension/perps/close_visible_position.mjs +133 -0
  56. package/library/actions/extension/perps/compare_provider_market.mjs +109 -0
  57. package/library/actions/extension/perps/edit_margin.mjs +82 -0
  58. package/library/actions/extension/perps/open_balance_action.mjs +58 -0
  59. package/library/actions/extension/perps/open_position_action.mjs +80 -0
  60. package/library/actions/extension/perps/perps.mjs +196 -2
  61. package/library/actions/extension/perps/place_order.mjs +5 -2
  62. package/library/actions/extension/perps/read_eligibility.mjs +34 -0
  63. package/library/actions/extension/perps/read_funds_confirmation.mjs +69 -0
  64. package/library/actions/extension/perps/read_snapshot.mjs +470 -0
  65. package/library/actions/extension/perps/read_visible_state.mjs +98 -0
  66. package/library/actions/extension/perps/select_activity_filter.mjs +44 -0
  67. package/library/actions/extension/perps/select_market_filter.mjs +71 -0
  68. package/library/actions/extension/perps/set_market_favorite.mjs +47 -0
  69. package/library/actions/extension/perps/update_position_tpsl.mjs +106 -0
  70. package/library/actions/extension/platform/cdp.mjs +109 -3
  71. package/library/actions/extension/settings/set_basic_functionality.mjs +58 -0
  72. package/library/actions/extension/swap_bridge/read_visible_state.mjs +71 -0
  73. package/library/actions/extension/swap_bridge/select_assets.mjs +103 -0
  74. package/library/actions/extension/swap_bridge/set_amount.mjs +36 -0
  75. package/library/actions/extension/swap_bridge/set_max_amount.mjs +58 -0
  76. package/library/actions/extension/swap_bridge/set_slippage.mjs +121 -0
  77. package/library/actions/extension/swap_bridge/submit_transaction.mjs +363 -0
  78. package/library/actions/extension/ui/activate_and_observe.mjs +96 -0
  79. package/library/actions/extension/ui/navigate.mjs +264 -8
  80. package/library/actions/extension/wallet/ensure_unlocked.mjs +2 -6
  81. package/library/actions/extension/wallet/import.mjs +13 -46
  82. package/library/actions/extension/wallet/lock.mjs +35 -0
  83. package/library/actions/extension/wallet/secret-input.mjs +98 -0
  84. package/library/actions/extension/wallet/select_account.mjs +71 -7
  85. package/library/actions/mobile/assets/read_visible_state.mjs +37 -0
  86. package/library/actions/mobile/perps/read_visible_state.mjs +31 -0
  87. package/library/actions/mobile/platform/native-session-name.mjs +5 -0
  88. package/library/actions/mobile/platform/observe-ui.mjs +86 -2
  89. package/library/actions/mobile/swap_bridge/native-session.mjs +45 -0
  90. package/library/actions/mobile/swap_bridge/read_visible_state.mjs +31 -0
  91. package/library/actions/mobile/swap_bridge/select_assets.mjs +94 -0
  92. package/library/actions/mobile/swap_bridge/set_amount.mjs +73 -0
  93. package/library/actions/mobile/ui/native-navigation.mjs +265 -0
  94. package/library/actions/mobile/ui/navigate.mjs +10 -1
  95. package/library/actions/mobile/wallet/ensure_unlocked.mjs +17 -0
  96. package/library/actions/mobile/wallet/import.mjs +71 -2
  97. package/library/actions/mobile/wallet/native-ui.mjs +512 -0
  98. package/library/actions/mobile/wallet/read_state.mjs +16 -0
  99. package/library/actions/mobile/wallet/reset.mjs +17 -4
  100. package/library/actions/shared/assets/visible-state.mjs +176 -0
  101. package/library/actions/shared/networks/visible-state.mjs +70 -0
  102. package/library/actions/shared/perps/visible-state.mjs +354 -0
  103. package/library/actions/shared/swap-bridge/transaction.mjs +54 -0
  104. package/library/actions/shared/swap-bridge/visible-state.mjs +97 -0
  105. package/library/actions/shared/ui/locators.mjs +7 -0
  106. package/library/manifests/extension.action-manifest.json +1366 -184
  107. package/library/manifests/mobile.action-manifest.json +235 -1
  108. package/library/recipes/assets/release-custom-gas-send.recipe.json +88 -0
  109. package/library/recipes/assets/release-custom-network-token-persistence.recipe.json +106 -0
  110. package/library/recipes/assets/release-native-max-cancel.recipe.json +70 -0
  111. package/library/recipes/assets/release-native-max-send.recipe.json +78 -0
  112. package/library/recipes/assets/release-own-account-send.recipe.json +80 -0
  113. package/library/recipes/assets/release-token-address-import.recipe.json +47 -0
  114. package/library/recipes/assets/release-token-details.recipe.json +48 -0
  115. package/library/recipes/assets/release-token-hide-readd.recipe.json +55 -0
  116. package/library/recipes/assets/release-token-list.recipe.json +75 -0
  117. package/library/recipes/assets/release-token-persistence.recipe.json +73 -0
  118. package/library/recipes/assets/release-token-search-import.recipe.json +40 -0
  119. package/library/recipes/assets/release-token-sorting.recipe.json +29 -0
  120. package/library/recipes/extension/networks/release-balance-stability.recipe.json +51 -0
  121. package/library/recipes/extension/perps/release-live-limit-order.recipe.json +104 -0
  122. package/library/recipes/extension/perps/release-order-entry.recipe.json +155 -0
  123. package/library/recipes/extension/perps/release-order-validation.recipe.json +220 -0
  124. package/library/recipes/extension/runner/action-validation.recipe.json +12 -1
  125. package/library/recipes/perps/release-activity-filters.recipe.json +51 -0
  126. package/library/recipes/perps/release-funds-flows.recipe.json +76 -0
  127. package/library/recipes/perps/release-live-market-order.recipe.json +122 -0
  128. package/library/recipes/perps/release-market-details.recipe.json +83 -0
  129. package/library/recipes/perps/release-market-search.recipe.json +79 -0
  130. package/library/recipes/perps/release-watchlist.recipe.json +99 -0
  131. package/library/recipes/swap-bridge/release-custom-slippage.recipe.json +90 -0
  132. package/library/recipes/swap-bridge/release-live-max-swap.recipe.json +99 -0
  133. package/library/recipes/swap-bridge/release-live-swap.recipe.json +101 -0
  134. package/library/recipes/swap-bridge/release-max-quote.recipe.json +72 -0
  135. package/library/recipes/swap-bridge/release-quote.recipe.json +72 -0
  136. package/library/recipes/swap-bridge/release-surface.recipe.json +43 -0
  137. package/library/recipes/wallet/import.recipe.json +20 -1
  138. package/library/recipes/wallet/reset-import.recipe.json +20 -1
  139. package/package.json +6 -5
  140. package/scripts/validate-release-report.mjs +319 -0
@@ -9,9 +9,12 @@
9
9
  #
10
10
  # Inputs (flags):
11
11
  # --dist <dir> source dist (required, e.g. <repo>/dist/chrome)
12
+ # --target <dir> absolute checkout containment root
13
+ # --runtime-root <dir> absolute directory that owns the snapshot
12
14
  # --runtime-dist <dir> snapshot destination (required, recreated)
13
15
  # --wait-iterations <n> manifest wait loop length, 2s each (default 180)
14
16
  # --summary <file> optional standard summary.json
17
+ # --exact preserve every source entry (release artifacts)
15
18
  #
16
19
  # Outputs:
17
20
  # <runtime-dist>/ snapshot (excludes _metadata); optional --summary file
@@ -24,24 +27,32 @@
24
27
  set -euo pipefail
25
28
 
26
29
  DIST=""
30
+ TARGET_ROOT=""
31
+ RUNTIME_ROOT=""
27
32
  RUNTIME_DIST=""
28
33
  WAIT_ITERATIONS=180
29
34
  SUMMARY=""
35
+ EXACT=false
30
36
  require_value() { [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; }
31
37
  while [ "$#" -gt 0 ]; do
32
38
  case "$1" in
33
39
  --dist) require_value "$@"; DIST="$2"; shift 2 ;;
40
+ --target) require_value "$@"; TARGET_ROOT="$2"; shift 2 ;;
41
+ --runtime-root) require_value "$@"; RUNTIME_ROOT="$2"; shift 2 ;;
34
42
  --runtime-dist) require_value "$@"; RUNTIME_DIST="$2"; shift 2 ;;
35
43
  --wait-iterations) require_value "$@"; WAIT_ITERATIONS="$2"; shift 2 ;;
36
44
  --summary) require_value "$@"; SUMMARY="$2"; shift 2 ;;
45
+ --exact) EXACT=true; shift ;;
37
46
  -h|--help)
38
- echo "Usage: snapshot-dist.sh --dist <dir> --runtime-dist <dir> [--wait-iterations <n>] [--summary <file>]"
47
+ echo "Usage: snapshot-dist.sh --dist <dir> --target <dir> --runtime-root <dir> --runtime-dist <dir> [--wait-iterations <n>] [--summary <file>]"
39
48
  exit 0
40
49
  ;;
41
50
  *) echo "Unknown arg: $1" >&2; exit 2 ;;
42
51
  esac
43
52
  done
44
53
  [ -n "$DIST" ] || { echo "Missing --dist" >&2; exit 2; }
54
+ [ -n "$TARGET_ROOT" ] || { echo "Missing --target" >&2; exit 2; }
55
+ [ -n "$RUNTIME_ROOT" ] || { echo "Missing --runtime-root" >&2; exit 2; }
45
56
  [ -n "$RUNTIME_DIST" ] || { echo "Missing --runtime-dist" >&2; exit 2; }
46
57
  case "$WAIT_ITERATIONS" in ''|*[!0-9]*) echo "Invalid --wait-iterations (must be numeric): $WAIT_ITERATIONS" >&2; exit 2 ;; esac
47
58
 
@@ -73,8 +84,82 @@ done
73
84
  test -f "$DIST/manifest.json" || { echo "snapshot-dist: no manifest at $DIST/manifest.json" >&2; exit 1; }
74
85
 
75
86
  echo "[recipe-harness] snapshotting dist -> runtime-dist: $RUNTIME_DIST" >&2
76
- rm -rf "$RUNTIME_DIST" && mkdir -p "$RUNTIME_DIST" \
77
- && rsync -a --delete --exclude _metadata "$DIST/" "$RUNTIME_DIST/" || exit 1
87
+ node - "$DIST" "$TARGET_ROOT" "$RUNTIME_ROOT" "$RUNTIME_DIST" <<'NODE'
88
+ const fs = require('node:fs');
89
+ const path = require('node:path');
90
+
91
+ const [sourceInput, targetInput, rootInput, destinationInput] = process.argv.slice(2);
92
+ const source = path.resolve(sourceInput);
93
+ const target = path.resolve(targetInput);
94
+ const root = path.resolve(rootInput);
95
+ const destination = path.resolve(destinationInput);
96
+
97
+ function refuse(message) {
98
+ throw new Error(`snapshot-dist: ${message}`);
99
+ }
100
+
101
+ if (![targetInput, rootInput, destinationInput].every(path.isAbsolute)) {
102
+ refuse('--target, --runtime-root, and --runtime-dist must be absolute paths.');
103
+ }
104
+ const targetStat = fs.lstatSync(target, { throwIfNoEntry: false });
105
+ if (!targetStat?.isDirectory() || targetStat.isSymbolicLink()) {
106
+ refuse(`target is not a regular directory: ${target}.`);
107
+ }
108
+ const rootRelative = path.relative(target, root);
109
+ if (!rootRelative || rootRelative === '..' || rootRelative.startsWith(`..${path.sep}`) || path.isAbsolute(rootRelative)) {
110
+ refuse(`runtime root must be a child of ${target}.`);
111
+ }
112
+ const relative = path.relative(root, destination);
113
+ if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
114
+ refuse(`runtime destination must be a child of ${root}.`);
115
+ }
116
+ if (
117
+ source === destination ||
118
+ source.startsWith(`${destination}${path.sep}`) ||
119
+ destination.startsWith(`${source}${path.sep}`)
120
+ ) {
121
+ refuse('source and runtime destination must be separate directory trees.');
122
+ }
123
+
124
+ function ensureDirectoryTree(base, relativePath) {
125
+ let current = base;
126
+ const components = relativePath.split(path.sep).filter(Boolean);
127
+ for (const component of components) {
128
+ current = path.join(current, component);
129
+ const stat = fs.lstatSync(current, { throwIfNoEntry: false });
130
+ if (!stat) {
131
+ fs.mkdirSync(current, { mode: 0o700 });
132
+ continue;
133
+ }
134
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
135
+ refuse(`runtime path contains an unsafe entry: ${current}.`);
136
+ }
137
+ }
138
+ }
139
+
140
+ ensureDirectoryTree(target, rootRelative);
141
+ ensureDirectoryTree(root, path.relative(root, path.dirname(destination)));
142
+ const realTarget = fs.realpathSync(target);
143
+ const realRoot = fs.realpathSync(root);
144
+ const realParent = fs.realpathSync(path.dirname(destination));
145
+ if (
146
+ !realRoot.startsWith(`${realTarget}${path.sep}`) ||
147
+ (realParent !== realRoot && !realParent.startsWith(`${realRoot}${path.sep}`))
148
+ ) {
149
+ refuse(`runtime destination escapes ${root}.`);
150
+ }
151
+ const destinationStat = fs.lstatSync(destination, { throwIfNoEntry: false });
152
+ if (destinationStat && (!destinationStat.isDirectory() || destinationStat.isSymbolicLink())) {
153
+ refuse(`runtime destination is not a regular directory: ${destination}.`);
154
+ }
155
+ if (destinationStat) fs.rmSync(destination, { recursive: true });
156
+ fs.mkdirSync(destination, { mode: 0o700 });
157
+ NODE
158
+ if $EXACT; then
159
+ rsync -a --delete "$DIST/" "$RUNTIME_DIST/" || exit 1
160
+ else
161
+ rsync -a --delete --exclude _metadata "$DIST/" "$RUNTIME_DIST/" || exit 1
162
+ fi
78
163
  echo "[recipe-harness] runtime-dist snapshot complete" >&2
79
164
 
80
165
  # Freshness guard: the loaded runtime-dist must match dist/chrome's git id. A
@@ -344,14 +344,18 @@ let r = {};
344
344
  try { r = JSON.parse(fs.readFileSync(dir + "/runtime-decision.json", "utf8")); } catch {}
345
345
  const c = r.checks || {};
346
346
  const dist = c.dist || { status: "unknown" };
347
- const distMsg = dist.status === "fresh" ? "dist id matches HEAD; no uncommitted source."
347
+ const distMsg = dist.source === "release-artifact"
348
+ ? ("release artifact " + (dist.manifestVersion || "?") + " sha256=" + (dist.artifactSha256 || "?") + " matches the loaded runtime snapshot.")
349
+ : dist.status === "fresh" ? "dist id matches HEAD; no uncommitted source."
348
350
  : dist.status === "stale" ? (dist.reason === "uncommitted-source"
349
351
  ? ((dist.modified ? dist.modified.length : "some") + " uncommitted source file(s); rebuild or commit.")
350
352
  : ("dist id " + (dist.distGitId || "?") + " != HEAD " + (dist.head || "?") + "; rebuild."))
351
353
  : dist.status === "no-build" ? "no dist/chrome build."
352
354
  : "no git id in dist or not a git checkout; cannot prove parity.";
353
355
  fs.writeFileSync(dir + "/dist-freshness.json", JSON.stringify({ ...dist, message: distMsg }));
354
- const bl = c.buildLog || { status: "unknown" };
356
+ const bl = c.releaseArtifact?.status === "valid"
357
+ ? { status: "no-watch", source: "release-artifact" }
358
+ : (c.buildLog || { status: "unknown" });
355
359
  const blMsg = bl.status === "ok" ? "webpack compiled."
356
360
  : bl.status === "no-watch" ? "no webpack watch log; build-health n/a (e.g. one-shot build)."
357
361
  : bl.status === "building" ? "webpack has not reported a successful compile yet."
@@ -186,6 +186,14 @@
186
186
  "inputs": "--target --runtime-dist; optional --runtime-dir",
187
187
  "outputs": "home.html and sidepanel.html titles updated in the isolated runtime only; exit 0/1"
188
188
  },
189
+ {
190
+ "id": "extension/artifact-runtime-state",
191
+ "entry": "adapters/extension/artifact-runtime-state.cjs",
192
+ "kind": "node",
193
+ "purpose": "Record or clear the acquired release artifact identity bound to the loaded runtime snapshot.",
194
+ "inputs": "set|clear --target --runtime-dir; set also requires --source-dir --runtime-dist --provenance",
195
+ "outputs": "<runtime-dir>/extension-release-artifact.json or removal; exit 0/1/2"
196
+ },
189
197
  {
190
198
  "id": "extension/stop-viewers",
191
199
  "entry": "adapters/extension/stop-viewers.sh",
@@ -207,7 +215,7 @@
207
215
  "entry": "adapters/extension/snapshot-dist.sh",
208
216
  "kind": "bash",
209
217
  "purpose": "Runtime-dist snapshot (rsync, excludes _metadata) with from-git-id freshness guard.",
210
- "inputs": "--dist --runtime-dist --wait-iterations --summary",
218
+ "inputs": "--dist --target --runtime-root --runtime-dist --wait-iterations --summary",
211
219
  "outputs": "<runtime-dist>/ snapshot; optional summary; exit 0/1/2"
212
220
  },
213
221
  {
@@ -226,6 +234,14 @@
226
234
  "inputs": "--cdp-port --chrome-bin --profile --extension-dir --chrome-log --chrome-pid",
227
235
  "outputs": "pid file + append-log; exit 0 / non-zero"
228
236
  },
237
+ {
238
+ "id": "extension/validation-process-ownership",
239
+ "entry": "adapters/extension/lib/validation-process-ownership.cjs",
240
+ "kind": "lib",
241
+ "purpose": "Resolve browser processes owned by one isolated Extension validation profile.",
242
+ "inputs": "profile path",
243
+ "outputs": "owned process identifiers"
244
+ },
229
245
  {
230
246
  "id": "extension/refresh-build",
231
247
  "entry": "adapters/extension/refresh-build.sh",
@@ -236,7 +236,7 @@ async function watchLog(options) {
236
236
  renderCompact({ label, logPath, events, quiet: mode === 'quiet' });
237
237
  if (mode !== 'quiet') {
238
238
  const elapsed = Math.round((Date.now() - started) / 1000);
239
- process.stderr.write(`${color('label', 'mm-harness', { stream: process.stderr })} ${color('dim', '|', { stream: process.stderr })} ${color('ok', `build complete (${elapsed}s)`, { stream: process.stderr })}\n`);
239
+ process.stderr.write(`${color('label', 'mm-harness', { stream: process.stderr })} ${color('dim', '|', { stream: process.stderr })} ${color('ok', `${label} complete (${elapsed}s)`, { stream: process.stderr })}\n`);
240
240
  }
241
241
  return 0;
242
242
  }
@@ -0,0 +1,38 @@
1
+ import { createHash } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ function extensionTreeSha256(root) {
5
+ const resolvedRoot = path.resolve(root);
6
+ const rootStat = fs.lstatSync(resolvedRoot);
7
+ if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
8
+ throw new Error(`Extension artifact root must be a regular directory: ${resolvedRoot}`);
9
+ }
10
+ const digest = createHash("sha256");
11
+ walk(resolvedRoot, "");
12
+ return digest.digest("hex");
13
+ function walk(directory, relativeDirectory) {
14
+ const entries = fs.readdirSync(directory, { withFileTypes: true }).sort((left, right) => Buffer.compare(Buffer.from(left.name), Buffer.from(right.name)));
15
+ for (const entry of entries) {
16
+ const absolute = path.join(directory, entry.name);
17
+ const relative = relativeDirectory ? `${relativeDirectory}/${entry.name}` : entry.name;
18
+ const stat = fs.lstatSync(absolute);
19
+ if (stat.isSymbolicLink()) {
20
+ throw new Error(`Extension artifact tree contains a symbolic link: ${relative}`);
21
+ }
22
+ if (stat.isDirectory()) {
23
+ digest.update(`directory\0${relative}\0`);
24
+ walk(absolute, relative);
25
+ continue;
26
+ }
27
+ if (!stat.isFile()) {
28
+ throw new Error(`Extension artifact tree contains an unsupported entry: ${relative}`);
29
+ }
30
+ digest.update(`file\0${relative}\0${stat.size}\0`);
31
+ digest.update(fs.readFileSync(absolute));
32
+ digest.update("\0");
33
+ }
34
+ }
35
+ }
36
+ export {
37
+ extensionTreeSha256
38
+ };
@@ -1,6 +1,8 @@
1
1
  import crypto from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
+ import { recipeRuntimeDir } from "../../paths.js";
5
+ import { releaseArtifactState, runtimeDistCheck } from "./runtime-decision.js";
4
6
  const DIST_MANIFEST = "dist/chrome/manifest.json";
5
7
  function extensionIdFromKey(keyBase64) {
6
8
  const der = Buffer.from(keyBase64, "base64");
@@ -12,8 +14,8 @@ function extensionIdFromKey(keyBase64) {
12
14
  }
13
15
  return id;
14
16
  }
15
- function idFromDistManifest(target) {
16
- const manifestPath = path.join(target, DIST_MANIFEST);
17
+ function idFromManifest(manifestPath) {
18
+ if (!manifestPath) return null;
17
19
  if (!fs.existsSync(manifestPath)) return null;
18
20
  try {
19
21
  const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
@@ -39,7 +41,24 @@ async function cdpExtensionIds(cdpPort) {
39
41
  }
40
42
  async function resolveExtensionId(target, options = {}) {
41
43
  const resolved = path.resolve(target);
42
- const fromKey = idFromDistManifest(resolved);
44
+ const runtimeSnapshot = runtimeDistCheck(resolved, releaseArtifactState(resolved));
45
+ const releaseArtifact = runtimeSnapshot.source === "release-artifact";
46
+ if (releaseArtifact && runtimeSnapshot.status !== "fresh") {
47
+ return {
48
+ adapter: "extension",
49
+ target: resolved,
50
+ extensionId: null,
51
+ source: "none",
52
+ verified: false
53
+ };
54
+ }
55
+ const manifestPath = releaseArtifact ? runtimeSnapshot.status === "fresh" ? path.join(
56
+ resolved,
57
+ recipeRuntimeDir(),
58
+ process.env.RECIPE_RUNTIME_DIST_DIR || "runtime-dist",
59
+ "manifest.json"
60
+ ) : null : path.join(resolved, DIST_MANIFEST);
61
+ const fromKey = idFromManifest(manifestPath);
43
62
  let cdpIds = null;
44
63
  if (options.cdpPort) cdpIds = await cdpExtensionIds(options.cdpPort);
45
64
  if (fromKey) {
@@ -47,7 +66,7 @@ async function resolveExtensionId(target, options = {}) {
47
66
  adapter: "extension",
48
67
  target: resolved,
49
68
  extensionId: fromKey,
50
- source: "manifest-key",
69
+ source: releaseArtifact ? "release-manifest-key" : "manifest-key",
51
70
  verified: cdpIds ? cdpIds.includes(fromKey) : null
52
71
  };
53
72
  }