Skip to content

Commit d816abe

Browse files
committed
Initial scaffold: @devicecloud.dev/eas-workflow
Thin npm wrapper for running Maestro tests on devicecloud.dev from EAS Workflows. Reads EAS context from env vars (build URL, commit SHA, branch, PR), shells out to @devicecloud.dev/dcd cloud, parses status, and emits set-output lines so downstream EAS jobs can consume the result via needs:/after:. Mirrors the device-cloud-for-maestro repo layout: ncc-bundled dist/index.js committed, build-source.yaml CI auto-bundles on push to main.
0 parents  commit d816abe

14 files changed

Lines changed: 633 additions & 0 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Build Source
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- main
8+
paths:
9+
- 'src/**'
10+
- 'package.json'
11+
- 'pnpm-lock.yaml'
12+
- 'tsconfig.json'
13+
14+
jobs:
15+
build:
16+
name: Build
17+
runs-on: ubuntu-latest
18+
permissions:
19+
contents: write
20+
steps:
21+
- name: Checkout Repository
22+
uses: actions/checkout@v4
23+
with:
24+
fetch-depth: 0
25+
26+
- name: Setup Node.js
27+
uses: actions/setup-node@v4
28+
with:
29+
node-version: '24.x'
30+
31+
- name: Setup pnpm
32+
uses: pnpm/action-setup@v4
33+
34+
- name: Install Dependencies
35+
run: pnpm install
36+
37+
- name: Compile files
38+
run: pnpm build
39+
40+
- name: Setup Git Credentials
41+
run: |
42+
git config user.name github-actions
43+
git config user.email github-actions@github.com
44+
45+
- name: Commit Files
46+
continue-on-error: true
47+
run: |
48+
git add dist/index.js -f
49+
git commit -m "[CI] Add changes"
50+
51+
- name: Push changes
52+
uses: ad-m/github-push-action@v0.8.0
53+
with:
54+
github_token: ${{ secrets.GITHUB_TOKEN }}
55+
branch: main

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.DS_Store
2+
node_modules/
3+
yarn-error.log
4+
.claude

.prettierrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"singleQuote": true,
3+
"bracketSameLine": true,
4+
"endOfLine": "auto"
5+
}

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) devicecloud.dev
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE

README.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Device Cloud for EAS Workflows
2+
3+
Run [Maestro](https://maestro.mobile.dev) flows on [devicecloud.dev](https://devicecloud.dev) from [EAS Workflows](https://docs.expo.dev/eas/workflows/get-started/). A drop-in for Expo's `maestro-cloud` job that targets Device Cloud instead.
4+
5+
## Quick start
6+
7+
```yaml
8+
jobs:
9+
build_android:
10+
type: build
11+
params:
12+
platform: android
13+
profile: preview
14+
15+
e2e:
16+
after: [build_android]
17+
runs_on: linux-medium
18+
outputs:
19+
console_url: ${{ steps.dcd.outputs.console_url }}
20+
status: ${{ steps.dcd.outputs.upload_status }}
21+
steps:
22+
- uses: eas/checkout
23+
- id: dcd
24+
run: npx --yes @devicecloud.dev/eas-workflow@v0 --flows ./.maestro --android-device pixel-7
25+
env:
26+
DEVICE_CLOUD_API_KEY: ${{ secrets.DEVICE_CLOUD_API_KEY }}
27+
EAS_BUILD_URL: ${{ after.build_android.outputs.build_url }}
28+
EAS_GH_SHA: ${{ github.sha }}
29+
```
30+
31+
Full Android and iOS examples are in [`examples/`](./examples/).
32+
33+
## Inputs
34+
35+
The wrapper takes its config from two places: **environment variables** (for context that's the same across runs) and **CLI flags** (for per-flow options). Anything you pass on the command line is forwarded as-is to `dcd cloud`.
36+
37+
### Environment variables
38+
39+
| Var | Required | Source in YAML | Purpose |
40+
|---|---|---|---|
41+
| `DEVICE_CLOUD_API_KEY` | ✅ | `${{ secrets.DEVICE_CLOUD_API_KEY }}` | Device Cloud API key |
42+
| `EAS_BUILD_URL` | when no `--app-binary-id`/`--app-file` | `${{ after.<job>.outputs.build_url }}` | Signed URL to the EAS build artifact |
43+
| `EAS_BUILD_ID` | optional | `${{ after.<job>.outputs.build_id }}` | Tagged as `eas_build_id` metadata |
44+
| `EAS_BUILD_PLATFORM` | optional | `${{ after.<job>.outputs.platform }}` | Tagged as `eas_platform` metadata |
45+
| `EAS_BUILD_PROFILE` | optional | (you set this) | Tagged as `eas_profile` metadata |
46+
| `EAS_APP_VERSION` | optional | `${{ after.<job>.outputs.app_version }}` | Tagged as `eas_app_version` metadata |
47+
| `EAS_GH_SHA` | optional | `${{ github.sha }}` | Tagged as `gh_sha` metadata |
48+
| `EAS_GH_BRANCH` | optional | `${{ github.ref_name }}` | Tagged as `gh_branch` metadata |
49+
| `EAS_GH_PR_NUMBER` | optional | `${{ github.event.pull_request.number }}` | Tagged as `gh_pr_number` metadata |
50+
| `EAS_GH_PR_URL` | optional | `${{ github.event.pull_request.html_url }}` | Tagged as `gh_pr_url` metadata |
51+
| `EAS_GH_REPO` | optional | `${{ github.repository }}` | Tagged as `gh_repo` metadata |
52+
| `EAS_GH_RUN_ID` | optional | `${{ github.run_id }}` | Tagged as `gh_run_id` metadata |
53+
| `DEVICE_CLOUD_API_URL` | optional | — | Override API URL (staging only) |
54+
| `DCD_USE_BETA` | optional | — | Set to `true` to use the beta CLI |
55+
56+
### CLI flags
57+
58+
Everything you pass after `npx @devicecloud.dev/eas-workflow@v0 ...` is forwarded verbatim to `dcd cloud`. Common ones:
59+
60+
- `--flows <path>` — directory of Maestro flows (e.g. `./.maestro`)
61+
- `--android-device <model>` — `pixel-6`, `pixel-7`, `pixel-7-pro`, …
62+
- `--android-api-level <n>` — `29`–`36`
63+
- `--ios-device <model>` — `iphone-16`, `iphone-16-pro`, …
64+
- `--ios-version <n>` — `16`, `17`, `18`, `26`
65+
- `--maestro-version <semver>`
66+
- `--include-tags`, `--exclude-tags`
67+
- `--retry <n>`
68+
- `--async`
69+
- `--google-play`
70+
71+
See [docs.devicecloud.dev/cli](https://docs.devicecloud.dev/cli) for the full list.
72+
73+
## Outputs
74+
75+
Emitted as `set-output <name> <value>` lines on stdout and surfaced as EAS step outputs:
76+
77+
| Output | Type | Description |
78+
|---|---|---|
79+
| `console_url` | string | Direct link to the test run in the Device Cloud console |
80+
| `upload_status` | `PASSED` \| `FAILED` \| `CANCELLED` \| `PENDING` \| `RUNNING` | Final status of the run |
81+
| `app_binary_id` | string | ID of the uploaded app binary (reuse with `--app-binary-id` for faster reruns) |
82+
| `flow_results` | JSON string | Array of `{name, status}` per flow |
83+
84+
Reference downstream like `${{ steps.dcd.outputs.console_url }}` (same job) or `${{ needs.e2e.outputs.console_url }}` (different job, with `needs:` / `after:`).
85+
86+
## Exit codes
87+
88+
- `0` — all flows passed, or the wrapper ran in async mode
89+
- `1` — at least one flow failed, the run was cancelled, or the wrapper hit an internal error
90+
91+
## Choosing a runner
92+
93+
`linux-medium` is fine for the wrapper itself — it just shells out to the Device Cloud platform, which runs your devices remotely. macOS runners are not required for iOS flows (the iOS simulators live on Device Cloud's Mac fleet).
94+
95+
## Migrating from `maestro-cloud`
96+
97+
EAS's built-in `maestro-cloud` job is hardcoded to Maestro Cloud. Swap the whole job for a `runs_on` custom job:
98+
99+
```yaml
100+
# Before
101+
e2e:
102+
type: maestro-cloud
103+
params:
104+
build_id: ${{ after.build.outputs.build_id }}
105+
maestro_project_id: proj_xxx
106+
flows: ./.maestro
107+
maestro_api_key: ${{ secrets.MAESTRO_CLOUD_API_KEY }}
108+
109+
# After
110+
e2e:
111+
after: [build]
112+
runs_on: linux-medium
113+
steps:
114+
- id: dcd
115+
run: npx --yes @devicecloud.dev/eas-workflow@v0 --flows ./.maestro
116+
env:
117+
DEVICE_CLOUD_API_KEY: ${{ secrets.DEVICE_CLOUD_API_KEY }}
118+
EAS_BUILD_URL: ${{ after.build.outputs.build_url }}
119+
```
120+
121+
## Releases
122+
123+
The wrapper auto-bundles `dist/index.js` on push to `main`. Pin to a major (`@v0`) for stability or a specific version (`@0.1.0`) for reproducibility.

dist/index.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/android.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: PR — Android E2E on Device Cloud
2+
3+
on:
4+
pull_request: {}
5+
6+
jobs:
7+
build_android:
8+
type: build
9+
params:
10+
platform: android
11+
profile: preview
12+
13+
e2e_android:
14+
after: [build_android]
15+
runs_on: linux-medium
16+
outputs:
17+
console_url: ${{ steps.dcd.outputs.console_url }}
18+
status: ${{ steps.dcd.outputs.upload_status }}
19+
steps:
20+
- uses: eas/checkout
21+
- id: dcd
22+
run: |
23+
npx --yes @devicecloud.dev/eas-workflow@v0 \
24+
--flows ./.maestro \
25+
--android-device pixel-7 \
26+
--android-api-level 34
27+
env:
28+
DEVICE_CLOUD_API_KEY: ${{ secrets.DEVICE_CLOUD_API_KEY }}
29+
EAS_BUILD_URL: ${{ after.build_android.outputs.build_url }}
30+
EAS_BUILD_ID: ${{ after.build_android.outputs.build_id }}
31+
EAS_BUILD_PLATFORM: ${{ after.build_android.outputs.platform }}
32+
EAS_APP_VERSION: ${{ after.build_android.outputs.app_version }}
33+
EAS_GH_SHA: ${{ github.sha }}
34+
EAS_GH_BRANCH: ${{ github.ref_name }}
35+
EAS_GH_PR_NUMBER: ${{ github.event.pull_request.number }}
36+
EAS_GH_REPO: ${{ github.repository }}

examples/ios.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: PR — iOS E2E on Device Cloud
2+
3+
on:
4+
pull_request: {}
5+
6+
jobs:
7+
build_ios:
8+
type: build
9+
params:
10+
platform: ios
11+
profile: preview
12+
13+
e2e_ios:
14+
after: [build_ios]
15+
runs_on: linux-medium
16+
outputs:
17+
console_url: ${{ steps.dcd.outputs.console_url }}
18+
status: ${{ steps.dcd.outputs.upload_status }}
19+
steps:
20+
- uses: eas/checkout
21+
- id: dcd
22+
run: |
23+
npx --yes @devicecloud.dev/eas-workflow@v0 \
24+
--flows ./.maestro \
25+
--ios-device iphone-16 \
26+
--ios-version 18
27+
env:
28+
DEVICE_CLOUD_API_KEY: ${{ secrets.DEVICE_CLOUD_API_KEY }}
29+
EAS_BUILD_URL: ${{ after.build_ios.outputs.build_url }}
30+
EAS_BUILD_ID: ${{ after.build_ios.outputs.build_id }}
31+
EAS_BUILD_PLATFORM: ${{ after.build_ios.outputs.platform }}
32+
EAS_APP_VERSION: ${{ after.build_ios.outputs.app_version }}
33+
EAS_GH_SHA: ${{ github.sha }}
34+
EAS_GH_BRANCH: ${{ github.ref_name }}
35+
EAS_GH_PR_NUMBER: ${{ github.event.pull_request.number }}
36+
EAS_GH_REPO: ${{ github.repository }}

package.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "@devicecloud.dev/eas-workflow",
3+
"description": "Run Maestro tests on devicecloud.dev from EAS Workflows",
4+
"author": "devicecloud.dev",
5+
"version": "0.1.0",
6+
"main": "dist/index.js",
7+
"bin": {
8+
"eas-workflow": "dist/index.js"
9+
},
10+
"files": [
11+
"dist"
12+
],
13+
"license": "MIT",
14+
"engines": {
15+
"node": ">=20.0.0"
16+
},
17+
"scripts": {
18+
"build": "ncc build src/index.ts -o dist --minify"
19+
},
20+
"devDependencies": {
21+
"@types/node": "^25.4.0",
22+
"@vercel/ncc": "^0.38.4",
23+
"typescript": "^5.9.3"
24+
},
25+
"pnpm": {
26+
"overrides": {
27+
"undici": ">=7.24.4"
28+
}
29+
},
30+
"packageManager": "pnpm@10.29.3"
31+
}

pnpm-lock.yaml

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)