Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/layer3-branch-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ jobs:
android-flow:
name: Android — YAML flow
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' || (inputs.platform == 'android' && inputs.goal == '')
# Fork pull requests do not receive repository secrets. Keep the real
# device/LLM check for same-repository PRs and manual runs only.
if: >-
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event_name == 'workflow_dispatch' &&
inputs.platform == 'android' && inputs.goal == '')

steps:
- uses: actions/checkout@v4
Expand Down
13 changes: 10 additions & 3 deletions .github/workflows/runner-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ on:
jobs:
dom:
name: DOM route
# A bare "labeled" event only concerns the vision route — don't re-run dom.
if: github.event.action != 'labeled'
# A bare "labeled" event only concerns the vision route. Fork PRs cannot
# receive LLM_API_KEY, so do not invoke the secret-required workflow there.
if: >-
github.event.action != 'labeled' &&
(github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository)
concurrency:
group: runner-e2e-dom-${{ github.ref }}
cancel-in-progress: true
Expand All @@ -44,7 +48,10 @@ jobs:

vision:
name: Vision route (AI label)
if: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'AI')
if: >-
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository &&
contains(github.event.pull_request.labels.*.name, 'AI')
concurrency:
group: runner-e2e-vision-${{ github.ref }}
cancel-in-progress: true
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/device/ios-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* For real devices, provides guidance on WDA signing requirements.
*/

import { createInterface } from 'node:readline';
import type { MCPClient } from '../mcp/types.js';
import { extractText } from '../mcp/tools.js';
import * as ui from '../ui/terminal.js';
Expand Down Expand Up @@ -117,8 +118,7 @@ export async function checkRealDeviceWDA(): Promise<void> {

// Ask user to confirm
return new Promise((resolve) => {
const readline = require('node:readline');
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const rl = createInterface({ input: process.stdin, output: process.stdout });

rl.question(' Continue (WDA already installed)? [Y/n] ', (answer: string) => {
rl.close();
Expand Down
59 changes: 59 additions & 0 deletions tests/sdk/ios-setup.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { afterEach, describe, expect, test, vi } from 'vitest';

const readlineMocks = vi.hoisted(() => ({
close: vi.fn(),
createInterface: vi.fn(),
question: vi.fn(),
}));

vi.mock('node:readline', () => ({
createInterface: readlineMocks.createInterface,
}));

vi.mock('@appclaw/core/ui/terminal', () => ({
printInfo: vi.fn(),
printWarning: vi.fn(),
}));

const { checkRealDeviceWDA } = await import('@appclaw/core/device/ios-setup');

function setInteractiveTerminal(): () => void {
Object.defineProperty(process.stdin, 'isTTY', { configurable: true, value: true });
Object.defineProperty(process.stdout, 'isTTY', { configurable: true, value: true });

return () => {
delete (process.stdin as NodeJS.ReadStream & { isTTY?: boolean }).isTTY;
delete (process.stdout as NodeJS.WriteStream & { isTTY?: boolean }).isTTY;
};
}

let restoreTerminal: (() => void) | undefined;

afterEach(() => {
restoreTerminal?.();
restoreTerminal = undefined;
vi.clearAllMocks();
});

describe('checkRealDeviceWDA', () => {
test('prompts through the ESM readline import in interactive terminals', async () => {
restoreTerminal = setInteractiveTerminal();
readlineMocks.createInterface.mockReturnValue({
close: readlineMocks.close,
question: readlineMocks.question,
});
readlineMocks.question.mockImplementation((_prompt, callback) => callback('yes'));

await expect(checkRealDeviceWDA()).resolves.toBeUndefined();

expect(readlineMocks.createInterface).toHaveBeenCalledWith({
input: process.stdin,
output: process.stdout,
});
expect(readlineMocks.question).toHaveBeenCalledWith(
' Continue (WDA already installed)? [Y/n] ',
expect.any(Function)
);
expect(readlineMocks.close).toHaveBeenCalledOnce();
});
});
Loading