mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
fix: restore helper subpath exports (#402)
* fix: restore helper subpath exports * fix: polish helper subpath typings
This commit is contained in:
committed by
GitHub
parent
b0f52a9905
commit
f4f143ced7
@@ -12,6 +12,10 @@
|
||||
"import": "./dist/src/index.js",
|
||||
"types": "./dist/src/index.d.ts"
|
||||
},
|
||||
"./artifacts": {
|
||||
"import": "./dist/src/artifacts.js",
|
||||
"types": "./dist/src/artifacts.d.ts"
|
||||
},
|
||||
"./metro": {
|
||||
"import": "./dist/src/metro.js",
|
||||
"types": "./dist/src/metro.d.ts"
|
||||
@@ -20,9 +24,21 @@
|
||||
"import": "./dist/src/remote-config.js",
|
||||
"types": "./dist/src/remote-config.d.ts"
|
||||
},
|
||||
"./install-source": {
|
||||
"import": "./dist/src/install-source.js",
|
||||
"types": "./dist/src/install-source.d.ts"
|
||||
},
|
||||
"./contracts": {
|
||||
"import": "./dist/src/contracts.js",
|
||||
"types": "./dist/src/contracts.d.ts"
|
||||
},
|
||||
"./selectors": {
|
||||
"import": "./dist/src/selectors.js",
|
||||
"types": "./dist/src/selectors.d.ts"
|
||||
},
|
||||
"./finders": {
|
||||
"import": "./dist/src/finders.js",
|
||||
"types": "./dist/src/finders.d.ts"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -17,9 +17,13 @@ export default defineConfig({
|
||||
source: {
|
||||
entry: {
|
||||
index: 'src/index.ts',
|
||||
artifacts: 'src/artifacts.ts',
|
||||
metro: 'src/metro.ts',
|
||||
'remote-config': 'src/remote-config.ts',
|
||||
'install-source': 'src/install-source.ts',
|
||||
contracts: 'src/contracts.ts',
|
||||
selectors: 'src/selectors.ts',
|
||||
finders: 'src/finders.ts',
|
||||
},
|
||||
tsconfigPath: 'tsconfig.lib.json',
|
||||
},
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { test } from 'vitest';
|
||||
|
||||
import { resolveAndroidArchivePackageName } from '../artifacts.ts';
|
||||
|
||||
const resolver: (archivePath: string) => Promise<string | undefined> =
|
||||
resolveAndroidArchivePackageName;
|
||||
|
||||
test('package subpath exports android archive package resolver', () => {
|
||||
assert.equal(typeof resolver, 'function');
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { test } from 'vitest';
|
||||
import {
|
||||
findBestMatchesByLocator,
|
||||
normalizeRole,
|
||||
normalizeText,
|
||||
parseFindArgs,
|
||||
} from '../finders.ts';
|
||||
import type { SnapshotNode } from '../utils/snapshot.ts';
|
||||
|
||||
function makeNode(ref: string, label?: string): SnapshotNode {
|
||||
return {
|
||||
index: Number(ref.replace('e', '')) || 0,
|
||||
ref,
|
||||
type: 'XCUIElementTypeButton',
|
||||
label,
|
||||
};
|
||||
}
|
||||
|
||||
test('public finders entrypoint re-exports pure helpers', () => {
|
||||
const nodes: SnapshotNode[] = [makeNode('e1', 'Continue')];
|
||||
|
||||
const parsed = parseFindArgs(['label', 'Continue', 'click']);
|
||||
const best = findBestMatchesByLocator(nodes, 'label', 'Continue');
|
||||
const requireRectLegacy = findBestMatchesByLocator(nodes, 'label', 'Continue', true);
|
||||
const requireRectOptions = findBestMatchesByLocator(nodes, 'label', 'Continue', {
|
||||
requireRect: true,
|
||||
});
|
||||
|
||||
assert.equal(normalizeText(' Continue\nNow '), 'continue now');
|
||||
assert.equal(normalizeRole('XCUIElementTypeApplication.XCUIElementTypeButton'), 'button');
|
||||
assert.equal(parsed.action, 'click');
|
||||
assert.equal(best.matches.length, 1);
|
||||
assert.equal(requireRectLegacy.matches.length, 0);
|
||||
assert.equal(requireRectOptions.matches.length, 0);
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { test } from 'vitest';
|
||||
|
||||
import {
|
||||
ARCHIVE_EXTENSIONS,
|
||||
isTrustedInstallSourceUrl,
|
||||
materializeInstallablePath,
|
||||
validateDownloadSourceUrl,
|
||||
} from '../install-source.ts';
|
||||
|
||||
test('public install-source entrypoint re-exports pure helpers', () => {
|
||||
assert.deepEqual(ARCHIVE_EXTENSIONS, ['.zip', '.tar', '.tar.gz', '.tgz']);
|
||||
assert.equal(
|
||||
isTrustedInstallSourceUrl('https://api.github.com/repos/acme/app/actions/artifacts/1/zip'),
|
||||
true,
|
||||
);
|
||||
assert.equal(typeof materializeInstallablePath, 'function');
|
||||
assert.equal(typeof validateDownloadSourceUrl, 'function');
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { test } from 'vitest';
|
||||
import {
|
||||
findSelectorChainMatch,
|
||||
formatSelectorFailure,
|
||||
isNodeEditable,
|
||||
isNodeVisible,
|
||||
isSelectorToken,
|
||||
parseSelectorChain,
|
||||
resolveSelectorChain,
|
||||
tryParseSelectorChain,
|
||||
type Selector,
|
||||
type SelectorChain,
|
||||
type SelectorDiagnostics,
|
||||
type SelectorResolution,
|
||||
} from '../selectors.ts';
|
||||
import type { SnapshotNode } from '../utils/snapshot.ts';
|
||||
|
||||
const nodes: SnapshotNode[] = [
|
||||
{
|
||||
ref: 'e1',
|
||||
index: 0,
|
||||
type: 'android.widget.Button',
|
||||
label: 'Continue',
|
||||
rect: { x: 0, y: 0, width: 120, height: 48 },
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
ref: 'e2',
|
||||
index: 1,
|
||||
type: 'android.widget.EditText',
|
||||
label: 'Email',
|
||||
rect: { x: 0, y: 64, width: 200, height: 48 },
|
||||
enabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
test('public selector subpath exposes platform-aware matching helpers', () => {
|
||||
const chain: SelectorChain = parseSelectorChain('role=button label="Continue" visible=true');
|
||||
const firstSelector: Selector = chain.selectors[0];
|
||||
assert.equal(firstSelector.raw, 'role=button label="Continue" visible=true');
|
||||
assert.equal(tryParseSelectorChain(chain.raw)?.raw, chain.raw);
|
||||
assert.equal(isSelectorToken('visible=true'), true);
|
||||
|
||||
const match = findSelectorChainMatch(nodes, chain, {
|
||||
platform: 'android',
|
||||
requireRect: true,
|
||||
});
|
||||
assert.ok(match);
|
||||
assert.equal(match.matches, 1);
|
||||
|
||||
const resolved: SelectorResolution | null = resolveSelectorChain(nodes, chain, {
|
||||
platform: 'android',
|
||||
requireRect: true,
|
||||
});
|
||||
assert.equal(resolved?.node.ref, 'e1');
|
||||
|
||||
assert.equal(isNodeVisible(nodes[0]), true);
|
||||
assert.equal(isNodeEditable(nodes[1], 'android'), true);
|
||||
});
|
||||
|
||||
test('public selector diagnostics format failures', () => {
|
||||
const chain = parseSelectorChain('label=Missing');
|
||||
const diagnostics: SelectorDiagnostics[] = [{ selector: 'label=Missing', matches: 0 }];
|
||||
|
||||
assert.equal(
|
||||
formatSelectorFailure(chain, diagnostics, { unique: false }),
|
||||
'Selector did not match (label=Missing -> 0)',
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export { resolveAndroidArchivePackageName } from './platforms/android/manifest.ts';
|
||||
@@ -0,0 +1,23 @@
|
||||
export type { FindLocator } from './utils/finders.ts';
|
||||
export type { SnapshotNode } from './utils/snapshot.ts';
|
||||
export { normalizeRole, normalizeText, parseFindArgs } from './utils/finders.ts';
|
||||
|
||||
import {
|
||||
findBestMatchesByLocator as findBestMatchesByLocatorInternal,
|
||||
type FindLocator,
|
||||
} from './utils/finders.ts';
|
||||
import type { SnapshotNode } from './utils/snapshot.ts';
|
||||
|
||||
export type FindMatchOptions = {
|
||||
requireRect?: boolean;
|
||||
};
|
||||
|
||||
export function findBestMatchesByLocator(
|
||||
nodes: SnapshotNode[],
|
||||
locator: FindLocator,
|
||||
query: string,
|
||||
options?: boolean | FindMatchOptions,
|
||||
) {
|
||||
const matchOptions = typeof options === 'boolean' ? { requireRect: options } : options;
|
||||
return findBestMatchesByLocatorInternal(nodes, locator, query, matchOptions);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export {
|
||||
ARCHIVE_EXTENSIONS,
|
||||
isBlockedIpAddress,
|
||||
isBlockedSourceHostname,
|
||||
isTrustedInstallSourceUrl,
|
||||
materializeInstallablePath,
|
||||
validateDownloadSourceUrl,
|
||||
} from './platforms/install-source.ts';
|
||||
|
||||
export type {
|
||||
MaterializeInstallSource,
|
||||
MaterializedInstallable,
|
||||
} from './platforms/install-source.ts';
|
||||
@@ -0,0 +1,18 @@
|
||||
export type {
|
||||
Selector,
|
||||
SelectorChain,
|
||||
SelectorDiagnostics,
|
||||
SelectorResolution,
|
||||
} from './daemon/selectors.ts';
|
||||
export type { SnapshotNode } from './utils/snapshot.ts';
|
||||
|
||||
export {
|
||||
findSelectorChainMatch,
|
||||
formatSelectorFailure,
|
||||
isNodeEditable,
|
||||
isNodeVisible,
|
||||
isSelectorToken,
|
||||
parseSelectorChain,
|
||||
resolveSelectorChain,
|
||||
tryParseSelectorChain,
|
||||
} from './daemon/selectors.ts';
|
||||
@@ -23,6 +23,32 @@ Public subpath API exposed for Node consumers:
|
||||
- types: `RemoteConfigProfile`, `RemoteConfigProfileOptions`, `ResolvedRemoteConfigProfile`
|
||||
- `agent-device/contracts`
|
||||
- types: `SessionRuntimeHints`, `DaemonInstallSource`, `DaemonLockPolicy`, `DaemonRequestMeta`, `DaemonRequest`, `DaemonArtifact`, `DaemonResponseData`, `DaemonError`, `DaemonResponse`
|
||||
- `agent-device/selectors`
|
||||
- `parseSelectorChain(expression)`
|
||||
- `tryParseSelectorChain(expression)`
|
||||
- `resolveSelectorChain(nodes, chain, options)`
|
||||
- `findSelectorChainMatch(nodes, chain, options)`
|
||||
- `formatSelectorFailure(chain, diagnostics, options)`
|
||||
- `isNodeVisible(node)`
|
||||
- `isSelectorToken(token)`
|
||||
- `isNodeEditable(node, platform)`
|
||||
- types: `Selector`, `SelectorChain`, `SelectorDiagnostics`, `SelectorResolution`, `SnapshotNode`
|
||||
- `agent-device/finders`
|
||||
- `findBestMatchesByLocator(nodes, locator, query, requireRectOrOptions)`
|
||||
- `normalizeRole(value)`
|
||||
- `normalizeText(value)`
|
||||
- `parseFindArgs(args)`
|
||||
- types: `FindLocator`, `FindMatchOptions`, `SnapshotNode`
|
||||
- `agent-device/install-source`
|
||||
- `ARCHIVE_EXTENSIONS`
|
||||
- `isBlockedIpAddress(address)`
|
||||
- `isBlockedSourceHostname(hostname)`
|
||||
- `isTrustedInstallSourceUrl(sourceUrl)`
|
||||
- `materializeInstallablePath(options)`
|
||||
- `validateDownloadSourceUrl(url)`
|
||||
- types: `MaterializeInstallSource`, `MaterializedInstallable`
|
||||
- `agent-device/artifacts`
|
||||
- `resolveAndroidArchivePackageName(archivePath)`
|
||||
|
||||
## Basic usage
|
||||
|
||||
@@ -189,3 +215,22 @@ await stopMetroTunnel({
|
||||
```
|
||||
|
||||
Use `agent-device/remote-config` for profile loading and path resolution, `agent-device/metro` for Metro preparation and tunnel lifecycle, and `agent-device/contracts` when a server consumer needs daemon request or runtime contract types.
|
||||
|
||||
## Selector helpers
|
||||
|
||||
Use `agent-device/selectors` when a remote daemon or bridge needs to parse and match selector expressions without deep-importing daemon internals. Matching is platform-aware because role normalization and editability checks differ by backend.
|
||||
|
||||
```ts
|
||||
import { findSelectorChainMatch, parseSelectorChain } from 'agent-device/selectors';
|
||||
|
||||
const chain = parseSelectorChain('role=button label="Continue" visible=true');
|
||||
|
||||
const match = findSelectorChainMatch(snapshot.nodes, chain, {
|
||||
platform: 'android',
|
||||
requireRect: true,
|
||||
});
|
||||
|
||||
if (!match) {
|
||||
// Build a daemon-shaped error with formatSelectorFailure(...) if needed.
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user