Skip to content

Commit 6148f7d

Browse files
fix: GH workflow release creation
1 parent 8710909 commit 6148f7d

10 files changed

Lines changed: 145 additions & 32 deletions

File tree

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ jobs:
103103
name: Publish release
104104
permissions:
105105
contents: write
106+
id-token: write
106107
uses: ./.github/workflows/publish-release.yml
107108
secrets:
108109
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

.github/workflows/publish-release.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
workflow_call:
55
secrets:
66
NPM_TOKEN:
7-
required: true
7+
required: false
88
SLACK_WEBHOOK_URL:
99
required: true
1010

@@ -35,6 +35,8 @@ jobs:
3535
3636
publish-npm-dry-run:
3737
needs: publish-release
38+
permissions:
39+
id-token: write
3840
runs-on: ubuntu-latest
3941
steps:
4042
- name: Checkout and setup environment
@@ -47,7 +49,7 @@ jobs:
4749
with:
4850
name: publish-release-artifacts-${{ github.sha }}
4951
- name: Dry Run Publish
50-
uses: MetaMask/action-npm-publish@v5
52+
uses: MetaMask/action-npm-publish@v6
5153
with:
5254
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
5355
subteam: S042S7RE4AE
@@ -56,6 +58,8 @@ jobs:
5658

5759
publish-npm:
5860
needs: publish-npm-dry-run
61+
permissions:
62+
id-token: write
5963
runs-on: ubuntu-latest
6064
environment: npm-publish
6165
steps:
@@ -69,7 +73,7 @@ jobs:
6973
with:
7074
name: publish-release-artifacts-${{ github.sha }}
7175
- name: Publish
72-
uses: MetaMask/action-npm-publish@v5
76+
uses: MetaMask/action-npm-publish@v6
7377
with:
7478
npm-token: ${{ secrets.NPM_TOKEN }}
7579
env:

.yarnrc.yml

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
approvedGitRepositories:
2+
- "**"
3+
14
compressionLevel: mixed
25

36
enableGlobalCache: false
@@ -12,14 +15,14 @@ logFilters:
1215

1316
nodeLinker: node-modules
1417

15-
plugins:
16-
- path: .yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs
17-
spec: 'https://raw.githubusercontent.com/LavaMoat/LavaMoat/main/packages/yarn-plugin-allow-scripts/bundles/@yarnpkg/plugin-allow-scripts.js'
18-
1918
npmMinimalAgeGate: 4320
2019

2120
npmPreapprovedPackages:
22-
- '@metamask/*'
23-
- '@metamask-previews/*'
24-
- '@lavamoat/*'
25-
- '@modelcontextprotocol/sdk'
21+
- "@metamask/*"
22+
- "@metamask-previews/*"
23+
- "@lavamoat/*"
24+
- "@modelcontextprotocol/sdk"
25+
26+
plugins:
27+
- path: .yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs
28+
spec: "https://raw.githubusercontent.com/LavaMoat/LavaMoat/main/packages/yarn-plugin-allow-scripts/bundles/@yarnpkg/plugin-allow-scripts.js"

CHANGELOG.md

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- Three backends: iOS (IDB), Android (ADB), Appium/BrowserStack (W3C WebDriver)
1717
- `.device-session` file for attaching to existing Appium sessions or creating new ones
1818
- 16 MCP tools: `device_snapshot`, `device_screenshot`, `device_info`, `device_tap_element`, `device_tap_coordinates`, `device_type`, `device_swipe`, `device_long_press`, `device_wait_for`, `device_app_state`, `device_open_app`, `device_close_app`, `device_press_button`, `device_dismiss_keyboard`, `device_dismiss_alert`, `device_logs`
19-
- Fuzzy element matching (case-insensitive, partial text)
20-
- Android tree-structured UI hierarchy parser (nested, not flat)
21-
- iOS `snapshotMaxDepth` and `mobile: source` fallback for deep hierarchies
22-
- Auto-detection of platform from `.device-session`, `DEVICE_ID`, or booted simulator/emulator
23-
- Runtime health check with actionable error messages for missing IDB/ADB
24-
- SKILL.md agent reference with core loop, common patterns, and platform differences
25-
- MetaMask module template compliance (ts-bridge, dual CJS/ESM, yarn constraints, CI workflows)
26-
27-
### Fixed
28-
29-
- fix: GH workflow release creation
30-
- chore: add additional tools
31-
- chore: add MetaMask Security Code Scanner workflow
3219

3320
[Unreleased]: https://github.com/MetaMask/device-mcp/compare/v0.1.0...HEAD
3421
[0.1.0]: https://github.com/MetaMask/device-mcp/releases/tag/v0.1.0

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
"vite": "^6.4.1",
8787
"vitest": "^3.2.1"
8888
},
89-
"packageManager": "yarn@4.11.0",
89+
"packageManager": "yarn@4.16.0",
9090
"engines": {
9191
"node": "^20 || ^22 || >=24"
9292
},

src/backends/adb-backend.test.ts

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
1-
import { describe, it, expect } from 'vitest';
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
22

3-
import { parseAndroidHierarchy, parseNodeAttributes } from './adb-backend.js';
3+
import {
4+
parseAndroidHierarchy,
5+
parseNodeAttributes,
6+
AdbBackend,
7+
} from './adb-backend.js';
48
import { findElement } from '../utils/element.js';
9+
import * as execModule from '../utils/exec.js';
10+
11+
vi.mock('../utils/exec.js', () => ({
12+
exec: vi.fn(),
13+
execStrict: vi.fn(),
14+
isCommandAvailable: vi.fn().mockResolvedValue(true),
15+
}));
16+
17+
const mockExecStrict = vi.mocked(execModule.execStrict);
518

619
const SAMPLE_UIAUTOMATOR_XML = `<?xml version="1.0" encoding="UTF-8"?>
720
<hierarchy rotation="0">
@@ -96,3 +109,64 @@ describe('parseNodeAttributes', () => {
96109
expect(result!.identifier).toBeUndefined();
97110
});
98111
});
112+
113+
describe('AdbBackend.getElementText', () => {
114+
let backend: AdbBackend;
115+
116+
beforeEach(() => {
117+
vi.clearAllMocks();
118+
mockExecStrict.mockResolvedValue('device');
119+
backend = new AdbBackend('emulator-5554');
120+
});
121+
122+
const xmlWithElement = `<?xml version="1.0" encoding="UTF-8"?>
123+
<hierarchy>
124+
<node text="$42.00" resource-id="io.metamask:id/balance" class="android.widget.TextView" content-desc="Account balance" enabled="true" bounds="[100,200][500,260]" />
125+
</hierarchy>`;
126+
127+
it('returns label (content-desc) from matching element', async () => {
128+
mockExecStrict.mockImplementation(async (_cmd, args) => {
129+
if (args?.includes('get-state')) {
130+
return 'device';
131+
}
132+
return xmlWithElement;
133+
});
134+
135+
const text = await backend.getElementText({
136+
identifier: 'io.metamask:id/balance',
137+
});
138+
139+
expect(text).toBe('Account balance');
140+
});
141+
142+
it('returns value (text attr) when no label', async () => {
143+
const xmlNoDesc = `<?xml version="1.0" encoding="UTF-8"?>
144+
<hierarchy>
145+
<node text="hello" resource-id="field" class="EditText" content-desc="" enabled="true" bounds="[0,0][100,50]" />
146+
</hierarchy>`;
147+
mockExecStrict.mockImplementation(async (_cmd, args) => {
148+
if (args?.includes('get-state')) {
149+
return 'device';
150+
}
151+
return xmlNoDesc;
152+
});
153+
154+
const text = await backend.getElementText({ identifier: 'field' });
155+
156+
expect(text).toBe('hello');
157+
});
158+
159+
it('throws when element not found', async () => {
160+
const emptyXml = `<?xml version="1.0" encoding="UTF-8"?><hierarchy></hierarchy>`;
161+
mockExecStrict.mockImplementation(async (_cmd, args) => {
162+
if (args?.includes('get-state')) {
163+
return 'device';
164+
}
165+
return emptyXml;
166+
});
167+
168+
await expect(
169+
backend.getElementText({ identifier: 'missing' }),
170+
).rejects.toThrow('Element not found');
171+
});
172+
});

src/backends/idb-backend.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,4 +288,47 @@ describe('IdbBackend simctl fallback', () => {
288288
});
289289
});
290290
});
291+
292+
describe('getElementText', () => {
293+
it('returns label from matching element', async () => {
294+
const hierarchy = [
295+
{
296+
type: 'StaticText',
297+
AXLabel: 'Balance',
298+
AXValue: '$100.00',
299+
frame: { x: 0, y: 0, width: 200, height: 30 },
300+
enabled: true,
301+
},
302+
];
303+
mockExecStrict.mockResolvedValue(JSON.stringify(hierarchy));
304+
305+
const text = await backend.getElementText({ label: 'Balance' });
306+
307+
expect(text).toBe('Balance');
308+
});
309+
310+
it('returns value when label is missing', async () => {
311+
const hierarchy = [
312+
{
313+
type: 'TextField',
314+
AXValue: 'typed text',
315+
frame: { x: 0, y: 0, width: 200, height: 44 },
316+
enabled: true,
317+
},
318+
];
319+
mockExecStrict.mockResolvedValue(JSON.stringify(hierarchy));
320+
321+
const text = await backend.getElementText({ type: 'TextField' });
322+
323+
expect(text).toBe('typed text');
324+
});
325+
326+
it('throws when element is not found', async () => {
327+
mockExecStrict.mockResolvedValue(JSON.stringify([]));
328+
329+
await expect(
330+
backend.getElementText({ identifier: 'nonexistent' }),
331+
).rejects.toThrow('Element not found');
332+
});
333+
});
291334
});

src/backends/lazy-backend.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ describe('createLazyBackend', () => {
381381
await lazy.setClipboard('copied');
382382
await lazy.startScreenRecording('/tmp/rec.mp4');
383383
await lazy.stopScreenRecording();
384+
await lazy.getElementText({ label: 'Balance' });
384385

385386
expect(mockDetectPlatform).toHaveBeenCalledTimes(1);
386387
});

vitest.config.mts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ export default defineConfig({
1212
exclude: ['src/**/*.test.ts', 'src/**/*.test-d.ts'],
1313
thresholds: {
1414
autoUpdate: !process.env.CI,
15-
branches: 39.47,
16-
functions: 42.59,
17-
lines: 36.3,
18-
statements: 36.48,
15+
branches: 40.31,
16+
functions: 44.56,
17+
lines: 38.75,
18+
statements: 38.88,
1919
},
2020
},
2121

yarn.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Manual changes might be lost - proceed with caution!
33

44
__metadata:
5-
version: 8
5+
version: 10
66
cacheKey: 10
77

88
"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.28.6, @babel/code-frame@npm:^7.29.0":

0 commit comments

Comments
 (0)