From 75ef923dd53d97322d8481be533b6083520f8b73 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 28 Jan 2026 11:22:11 -0600 Subject: [PATCH 1/8] correction in docs --- wiki/devbox.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wiki/devbox.md b/wiki/devbox.md index bc4cd06d..8d427603 100644 --- a/wiki/devbox.md +++ b/wiki/devbox.md @@ -32,7 +32,7 @@ By default, Devbox uses the flake-pinned SDK (`path:./nix#android-sdk`). It sets ### Updating Android min/latest versions -- Bump pinned SDK versions in `nix/platform-versions.json` (platformVersions/buildToolsVersions/cmdLineToolsVersion). Rebuild devbox (`devbox shell --rebuild`) so everyone gets the new SDK. +- Bump pinned SDK versions in `nix/platform-versions.json` (platformVersions/buildToolsVersions/cmdLineToolsVersion). Refresh your devshell by running the `refresh` command while inside a devbox shell. - Update AVD defaults/names if you change API levels: - `devbox.json` (`start-android-*` scripts) for default AVD names. - `examples/E2E/.detoxrc.js` for the default `DETOX_AVD`. @@ -80,5 +80,5 @@ iOS uses the host Xcode toolchain. There is no Nix-provisioned iOS SDK. Run `dev After updating `nix/platform-versions.json`: -- Run `devbox install` or `devbox shell --rebuild` to refresh the SDK. +- Run `refresh` while in a devbox shell to refresh the SDK. - If you change iOS min/max, re-run the iOS E2E workflow to confirm the runtime/device exists on the runner. From 966859d1da5ebb105518545a304459d92eac746b Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 28 Jan 2026 11:25:50 -0600 Subject: [PATCH 2/8] cleanup workflow file --- .github/workflows/ci-e2e-optional.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci-e2e-optional.yml b/.github/workflows/ci-e2e-optional.yml index 5e04ae5c..30fec45c 100644 --- a/.github/workflows/ci-e2e-optional.yml +++ b/.github/workflows/ci-e2e-optional.yml @@ -2,8 +2,6 @@ name: E2E (On-Demand) on: workflow_dispatch: - push: - branches: [ci2] concurrency: group: e2e-optional-${{ github.ref }} From 6f1c3a6075398d4d9120682bc329ff0afefafa49 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 28 Jan 2026 11:40:20 -0600 Subject: [PATCH 3/8] update devbox with slimmed subshells for ci --- .github/workflows/ci-e2e-nightly.yml | 4 ++-- .github/workflows/ci-e2e-optional.yml | 4 ++-- .github/workflows/ci-fast.yml | 2 +- shells/devbox-android.json | 27 +++++++++++++++++++++++++++ shells/devbox-fast.json | 20 ++++++++++++++++++++ shells/devbox-ios.json | 25 +++++++++++++++++++++++++ wiki/devbox.md | 18 +++++++++++++++++- 7 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 shells/devbox-android.json create mode 100644 shells/devbox-fast.json create mode 100644 shells/devbox-ios.json diff --git a/.github/workflows/ci-e2e-nightly.yml b/.github/workflows/ci-e2e-nightly.yml index 5385b88b..2fb1f729 100644 --- a/.github/workflows/ci-e2e-nightly.yml +++ b/.github/workflows/ci-e2e-nightly.yml @@ -54,7 +54,7 @@ jobs: echo "IOS_RUNTIME=${PLATFORM_IOS_MAX_RUNTIME}" >> "$GITHUB_ENV" fi - name: iOS E2E Tests - run: devbox shell --omit-nix-env -- devbox run test-ios + run: devbox shell --omit-nix-env -- devbox run --config=shells/devbox-ios.json test-ios run-e2e-android: runs-on: ubuntu-24.04-arm @@ -102,4 +102,4 @@ jobs: avd_name="${device}_API${api}_arm64_v8a" echo "DETOX_AVD=${avd_name}" >> "$GITHUB_ENV" - name: Android E2E Tests - run: devbox run test-android + run: devbox run --config=shells/devbox-android.json test-android diff --git a/.github/workflows/ci-e2e-optional.yml b/.github/workflows/ci-e2e-optional.yml index 30fec45c..40864dec 100644 --- a/.github/workflows/ci-e2e-optional.yml +++ b/.github/workflows/ci-e2e-optional.yml @@ -42,7 +42,7 @@ jobs: echo "DETOX_IOS_DEVICE=${PLATFORM_IOS_MAX_DEVICE}" >> "$GITHUB_ENV" echo "IOS_RUNTIME=${PLATFORM_IOS_MAX_RUNTIME}" >> "$GITHUB_ENV" - name: iOS E2E Tests (latest) - run: devbox shell --omit-nix-env -- devbox run test-ios + run: devbox shell --omit-nix-env -- devbox run --config=shells/devbox-ios.json test-ios run-e2e-android: runs-on: ubuntu-24.04-arm @@ -78,4 +78,4 @@ jobs: avd_name="${device}_API${api}_arm64_v8a" echo "DETOX_AVD=${avd_name}" >> "$GITHUB_ENV" - name: Android E2E Tests (latest) - run: devbox run test-android + run: devbox run --config=shells/devbox-android.json test-android diff --git a/.github/workflows/ci-fast.yml b/.github/workflows/ci-fast.yml index fc37487a..0b517efb 100644 --- a/.github/workflows/ci-fast.yml +++ b/.github/workflows/ci-fast.yml @@ -38,4 +38,4 @@ jobs: with: enable-cache: 'false' - name: build - run: devbox run build + run: devbox run --config=shells/devbox-fast.json build diff --git a/shells/devbox-android.json b/shells/devbox-android.json new file mode 100644 index 00000000..26b290d7 --- /dev/null +++ b/shells/devbox-android.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.14.2/.schema/devbox.schema.json", + "packages": { + "yarn-berry": "latest", + "jdk17": "latest", + "gradle": "latest", + "jq": "latest", + "path:../nix#android-sdk": "" + }, + "shell": { + "init_hook": [ + "echo 'Android SDK env configured (details: wiki/devbox.md#devbox-android).'", + ". $DEVBOX_PROJECT_ROOT/scripts/android-env.sh" + ], + "scripts": { + "setup-android": ["bash $DEVBOX_PROJECT_ROOT/scripts/android-setup.sh"], + "test-android": [ + "devbox run setup-android", + "yarn install", + "yarn e2e install", + "yarn build", + "yarn e2e build:android", + "yarn e2e test:android" + ] + } + } +} diff --git a/shells/devbox-fast.json b/shells/devbox-fast.json new file mode 100644 index 00000000..4563fdd8 --- /dev/null +++ b/shells/devbox-fast.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.14.2/.schema/devbox.schema.json", + "packages": { + "yarn-berry": "latest", + "jq": "latest", + "treefmt": "latest", + "nixfmt": "latest", + "shfmt": "latest" + }, + "shell": { + "scripts": { + "build": [ + "yarn install --immutable", + "yarn build", + "yarn lint", + "yarn test --coverage" + ] + } + } +} diff --git a/shells/devbox-ios.json b/shells/devbox-ios.json new file mode 100644 index 00000000..6bc67f2a --- /dev/null +++ b/shells/devbox-ios.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.14.2/.schema/devbox.schema.json", + "packages": { + "cocoapods": { + "version": "latest", + "platforms": ["x86_64-darwin", "aarch64-darwin"] + }, + "yarn-berry": "latest", + "jq": "latest" + }, + "shell": { + "scripts": { + "setup-ios": ["bash $DEVBOX_PROJECT_ROOT/scripts/ios-setup.sh"], + "test-ios": [ + "devbox run setup-ios", + "yarn install", + "yarn e2e install", + "yarn e2e pods", + "yarn build", + "yarn e2e build:ios", + "yarn e2e test:ios" + ] + } + } +} diff --git a/wiki/devbox.md b/wiki/devbox.md index 8d427603..def859af 100644 --- a/wiki/devbox.md +++ b/wiki/devbox.md @@ -80,5 +80,21 @@ iOS uses the host Xcode toolchain. There is no Nix-provisioned iOS SDK. Run `dev After updating `nix/platform-versions.json`: -- Run `refresh` while in a devbox shell to refresh the SDK. +- Run `refresh` inside a devbox shell to refresh the SDK. - If you change iOS min/max, re-run the iOS E2E workflow to confirm the runtime/device exists on the runner. + +### CI devbox shells + +CI uses slim Devbox configs under `shells/` to avoid pulling unnecessary SDKs: + +- `shells/devbox-fast.json`: build/lint/tests only. +- `shells/devbox-android.json`: Android SDK + JDK/Gradle for Android E2E. +- `shells/devbox-ios.json`: CocoaPods + Yarn for iOS E2E (Xcode still required on macOS). + +Run them locally with: + +```sh +devbox run --config=shells/devbox-fast.json build +devbox run --config=shells/devbox-android.json test-android +devbox shell --omit-nix-env -- devbox run --config=shells/devbox-ios.json test-ios +``` From b96ecaf73566af94e5302d871f5a996f187c4ab4 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 28 Jan 2026 13:04:00 -0600 Subject: [PATCH 4/8] fix omit-nix-env --- .envrc | 5 +- .github/workflows/ci-e2e-nightly.yml | 2 +- .github/workflows/ci-e2e-optional.yml | 2 +- devbox.json | 46 ++-- devbox.lock | 120 +++++++++++ scripts/act-ci.sh | 18 +- scripts/{android-env.sh => android/env.sh} | 5 +- .../manager.sh} | 4 +- .../{android-setup.sh => android/setup.sh} | 5 +- scripts/android/test.sh | 11 + scripts/build.sh | 7 + scripts/ios/env.sh | 55 +++++ scripts/{ios-manager.sh => ios/manager.sh} | 7 +- scripts/{ios-setup.sh => ios/setup.sh} | 5 +- scripts/ios/test.sh | 24 +++ shells/devbox-android.json | 13 +- shells/devbox-fast.json | 7 +- shells/devbox-ios.json | 15 +- shells/devbox.lock | 201 ++++++++++++++++++ shells/gradle.properties | 1 + wiki/devbox.md | 13 +- 21 files changed, 482 insertions(+), 84 deletions(-) rename scripts/{android-env.sh => android/env.sh} (96%) rename scripts/{android-manager.sh => android/manager.sh} (93%) rename scripts/{android-setup.sh => android/setup.sh} (97%) create mode 100755 scripts/android/test.sh create mode 100755 scripts/build.sh create mode 100755 scripts/ios/env.sh rename scripts/{ios-manager.sh => ios/manager.sh} (89%) rename scripts/{ios-setup.sh => ios/setup.sh} (98%) create mode 100755 scripts/ios/test.sh create mode 100644 shells/devbox.lock create mode 100644 shells/gradle.properties diff --git a/.envrc b/.envrc index 2f05af98..f57ec204 100644 --- a/.envrc +++ b/.envrc @@ -1,9 +1,10 @@ #!/bin/bash # Automatically sets up your devbox environment whenever you cd into this -# directory via our direnv integration: +# directory via our direnv integration. +# Uses omit-nix-env as a temporary workaround: https://github.com/jetify-com/devbox/issues/1509 -eval "$(devbox generate direnv --print-envrc)" +eval "$(devbox shellenv --init-hook --install --no-refresh-alias --omit-nix-env=true)" # check out https://www.jetpack.io/devbox/docs/ide_configuration/direnv/ # for more details diff --git a/.github/workflows/ci-e2e-nightly.yml b/.github/workflows/ci-e2e-nightly.yml index 2fb1f729..c0a50596 100644 --- a/.github/workflows/ci-e2e-nightly.yml +++ b/.github/workflows/ci-e2e-nightly.yml @@ -54,7 +54,7 @@ jobs: echo "IOS_RUNTIME=${PLATFORM_IOS_MAX_RUNTIME}" >> "$GITHUB_ENV" fi - name: iOS E2E Tests - run: devbox shell --omit-nix-env -- devbox run --config=shells/devbox-ios.json test-ios + run: devbox run --config=shells/devbox-ios.json test-ios run-e2e-android: runs-on: ubuntu-24.04-arm diff --git a/.github/workflows/ci-e2e-optional.yml b/.github/workflows/ci-e2e-optional.yml index 40864dec..cf9406ec 100644 --- a/.github/workflows/ci-e2e-optional.yml +++ b/.github/workflows/ci-e2e-optional.yml @@ -42,7 +42,7 @@ jobs: echo "DETOX_IOS_DEVICE=${PLATFORM_IOS_MAX_DEVICE}" >> "$GITHUB_ENV" echo "IOS_RUNTIME=${PLATFORM_IOS_MAX_RUNTIME}" >> "$GITHUB_ENV" - name: iOS E2E Tests (latest) - run: devbox shell --omit-nix-env -- devbox run --config=shells/devbox-ios.json test-ios + run: devbox run --config=shells/devbox-ios.json test-ios run-e2e-android: runs-on: ubuntu-24.04-arm diff --git a/devbox.json b/devbox.json index 9ec933d2..3035f1f3 100644 --- a/devbox.json +++ b/devbox.json @@ -12,12 +12,14 @@ "jdk17": "latest", "gradle": "latest", "jq": "latest", + "netcat": "latest", "path:./nix#android-sdk": "" }, "shell": { "init_hook": [ "echo 'Welcome to analytics-react-native devbox!' > /dev/null", - ". $DEVBOX_PROJECT_ROOT/scripts/android-env.sh", + "if [ \"$(uname -s)\" = \"Darwin\" ]; then . $DEVBOX_PROJECT_ROOT/scripts/ios/env.sh; fi", + ". $DEVBOX_PROJECT_ROOT/scripts/android/env.sh", "echo 'Android SDK env configured (details: wiki/devbox.md#devbox-android).'" ], "scripts": { @@ -28,46 +30,26 @@ "yarn cache clean", "find $DEVBOX_PROJECT_DIR -type d -name node_modules -exec rmdir {} \\;" ], - "build": [ - "yarn install --immutable", - "yarn build", - "yarn lint", - "yarn test --coverage" - ], - "test-android": [ - "devbox run setup-android", - "yarn install", - "yarn e2e install", - "yarn build", - "yarn e2e build:android", - "yarn e2e test:android" - ], - "test-ios": [ - "devbox run setup-ios", - "yarn install", - "yarn e2e install", - "yarn e2e pods", - "yarn build", - "yarn e2e build:ios", - "yarn e2e test:ios" - ], + "build": ["bash $DEVBOX_PROJECT_ROOT/scripts/build.sh"], + "test-android": ["bash $DEVBOX_PROJECT_ROOT/scripts/android/test.sh"], + "test-ios": ["bash $DEVBOX_PROJECT_ROOT/scripts/ios/test.sh"], "act-ci": [ - "bash $DEVBOX_PROJECT_ROOT/scripts/act-ci.sh --platform ubuntu-latest=node:20-bullseye" + "bash $DEVBOX_PROJECT_ROOT/scripts/act-ci.sh --platform ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-24.04" ], - "setup-android": ["bash $DEVBOX_PROJECT_ROOT/scripts/android-setup.sh"], - "setup-ios": ["bash $DEVBOX_PROJECT_ROOT/scripts/ios-setup.sh"], + "setup-android": ["bash $DEVBOX_PROJECT_ROOT/scripts/android/setup.sh"], + "setup-ios": ["bash $DEVBOX_PROJECT_ROOT/scripts/ios/setup.sh"], "start-emulator": [ - "bash $DEVBOX_PROJECT_ROOT/scripts/android-manager.sh start" + "bash $DEVBOX_PROJECT_ROOT/scripts/android/manager.sh start" ], - "start-ios": ["bash $DEVBOX_PROJECT_ROOT/scripts/ios-manager.sh start"], + "start-ios": ["bash $DEVBOX_PROJECT_ROOT/scripts/ios/manager.sh start"], "start-android-minsdk": [ - "bash $DEVBOX_PROJECT_ROOT/scripts/android-manager.sh start" + "bash $DEVBOX_PROJECT_ROOT/scripts/android/manager.sh start" ], "start-android-latest": [ - "AVD_FLAVOR=latest bash $DEVBOX_PROJECT_ROOT/scripts/android-manager.sh start" + "AVD_FLAVOR=latest bash $DEVBOX_PROJECT_ROOT/scripts/android/manager.sh start" ], "start-android": [ - "bash $DEVBOX_PROJECT_ROOT/scripts/android-manager.sh start" + "bash $DEVBOX_PROJECT_ROOT/scripts/android/manager.sh start" ], "release": [ "npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}", diff --git a/devbox.lock b/devbox.lock index 7dda352e..81129d34 100644 --- a/devbox.lock +++ b/devbox.lock @@ -226,6 +226,126 @@ } } }, + "netcat@latest": { + "last_modified": "2026-01-20T02:11:35Z", + "resolved": "github:NixOS/nixpkgs/ed142ab1b3a092c4d149245d0c4126a5d7ea00b0#netcat", + "source": "devbox-search", + "version": "4.2.1", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "nc", + "path": "/nix/store/y2i4f3bwmgpxw4m6dl99dz9d7zp5axz2-libressl-4.2.1-nc", + "default": true + }, + { + "name": "bin", + "path": "/nix/store/ycvwxl29jb6ajjzgkq2jgy1nqpahq5k4-libressl-4.2.1-bin", + "default": true + }, + { + "name": "man", + "path": "/nix/store/2b61bckfnaiw3n5ppjg70avgd9rn60bp-libressl-4.2.1-man", + "default": true + }, + { + "name": "out", + "path": "/nix/store/fm6zdqw6856i2snd4fikcsi9d1qagj5j-libressl-4.2.1" + }, + { + "name": "dev", + "path": "/nix/store/bz5h2baa7bkpz8sgjc9ld8fr7cg5wapg-libressl-4.2.1-dev" + } + ], + "store_path": "/nix/store/y2i4f3bwmgpxw4m6dl99dz9d7zp5axz2-libressl-4.2.1-nc" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "nc", + "path": "/nix/store/0dzxkwilv9lgd7j0429s2rmshy7p8gw7-libressl-4.2.1-nc", + "default": true + }, + { + "name": "bin", + "path": "/nix/store/5ama6wp3yi03hbixdcm5jy2ya9ikvzjz-libressl-4.2.1-bin", + "default": true + }, + { + "name": "man", + "path": "/nix/store/vr4wlcvj5ba6wdmhj2aidalzqk33lrph-libressl-4.2.1-man", + "default": true + }, + { + "name": "dev", + "path": "/nix/store/hs5c0yvhf7n60xijr7bmpi592n45pb6i-libressl-4.2.1-dev" + }, + { + "name": "out", + "path": "/nix/store/fiy9987ph2kqr5drlr136w7hvm3v6rvg-libressl-4.2.1" + } + ], + "store_path": "/nix/store/0dzxkwilv9lgd7j0429s2rmshy7p8gw7-libressl-4.2.1-nc" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "nc", + "path": "/nix/store/vmrm6nr9hfhw7x8ln3ms98gszq709bfa-libressl-4.2.1-nc", + "default": true + }, + { + "name": "bin", + "path": "/nix/store/7kafgvpwc6s8pnzar90bd8017wc3cnvx-libressl-4.2.1-bin", + "default": true + }, + { + "name": "man", + "path": "/nix/store/jwdgv2x1a2a84v2jkj964xvm2s0kymps-libressl-4.2.1-man", + "default": true + }, + { + "name": "dev", + "path": "/nix/store/sjiz0iphbc7dvddlrbrk8k7kkqi3swz3-libressl-4.2.1-dev" + }, + { + "name": "out", + "path": "/nix/store/rn10w2jbdkz3f7p1q1fl6aml9b0352ki-libressl-4.2.1" + } + ], + "store_path": "/nix/store/vmrm6nr9hfhw7x8ln3ms98gszq709bfa-libressl-4.2.1-nc" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "nc", + "path": "/nix/store/54hijwy3gpc728s3468rv3sdw78ksakh-libressl-4.2.1-nc", + "default": true + }, + { + "name": "bin", + "path": "/nix/store/l5vvbs0vl734mshifahals0054pimlx4-libressl-4.2.1-bin", + "default": true + }, + { + "name": "man", + "path": "/nix/store/j26qyc85gk95bk06dq0fi0c9q8y7livx-libressl-4.2.1-man", + "default": true + }, + { + "name": "out", + "path": "/nix/store/4f21prki98shrvp29r88pnxhzw2y4qr2-libressl-4.2.1" + }, + { + "name": "dev", + "path": "/nix/store/0dndzgjzx25y7v1942c2rys20narddp8-libressl-4.2.1-dev" + } + ], + "store_path": "/nix/store/54hijwy3gpc728s3468rv3sdw78ksakh-libressl-4.2.1-nc" + } + } + }, "nixfmt@latest": { "last_modified": "2026-01-09T13:41:53Z", "resolved": "github:NixOS/nixpkgs/5f02c91314c8ba4afe83b256b023756412218535#nixfmt", diff --git a/scripts/act-ci.sh b/scripts/act-ci.sh index f67e136a..9057ea1f 100755 --- a/scripts/act-ci.sh +++ b/scripts/act-ci.sh @@ -2,10 +2,18 @@ set -euo pipefail # Run GitHub Actions workflows locally via act. -# Usage: scripts/act-ci.sh [--job JOB] [--platform ubuntu-latest=node:20-bullseye] +# Usage: scripts/act-ci.sh [--job JOB] [--platform ubuntu-latest=IMAGE] JOB="" -PLATFORM="ubuntu-latest=node:20-bullseye" +PLATFORMS=() + +host_arch="$(uname -m)" +if [[ "$host_arch" == "arm64" || "$host_arch" == "aarch64" ]]; then + PLATFORMS+=("ubuntu-24.04-arm=ghcr.io/catthehacker/ubuntu:act-24.04") +else + PLATFORMS+=("ubuntu-24.04=ghcr.io/catthehacker/ubuntu:act-24.04") +fi +PLATFORMS+=("ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-24.04") while [[ $# -gt 0 ]]; do case "$1" in @@ -14,7 +22,7 @@ while [[ $# -gt 0 ]]; do shift 2 ;; -p | --platform) - PLATFORM="$2" + PLATFORMS+=("$2") shift 2 ;; *) @@ -26,7 +34,9 @@ done CMD=(act) CMD+=(--pull=false) -CMD+=(--platform "${PLATFORM}") +for platform in "${PLATFORMS[@]}"; do + CMD+=(--platform "$platform") +done CMD+=(--input ACT=true) if [[ -n $JOB ]]; then CMD+=(--job "$JOB") diff --git a/scripts/android-env.sh b/scripts/android/env.sh similarity index 96% rename from scripts/android-env.sh rename to scripts/android/env.sh index 869c4a76..56cfb171 100755 --- a/scripts/android-env.sh +++ b/scripts/android/env.sh @@ -3,9 +3,10 @@ # Load shared platform versions if present. script_dir="$(cd "$(dirname "$0")" && pwd)" -if [ -f "$script_dir/platform-versions.sh" ]; then +platform_versions="$script_dir/../platform-versions.sh" +if [ -f "$platform_versions" ]; then # shellcheck disable=SC1090 - . "$script_dir/platform-versions.sh" + . "$platform_versions" fi if [ -z "${ANDROID_MIN_API:-}" ] && [ -n "${PLATFORM_ANDROID_MIN_API:-}" ]; then diff --git a/scripts/android-manager.sh b/scripts/android/manager.sh similarity index 93% rename from scripts/android-manager.sh rename to scripts/android/manager.sh index 0c0f304f..68ec8177 100755 --- a/scripts/android-manager.sh +++ b/scripts/android/manager.sh @@ -4,7 +4,7 @@ set -euo pipefail action="${1:-}" shift || true -source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/android-env.sh" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/env.sh" start_android() { local flavor="${AVD_FLAVOR:-minsdk}" headless="${EMU_HEADLESS:-}" port="${EMU_PORT:-5554}" @@ -51,7 +51,7 @@ start) start_android ;; stop) stop_android ;; reset) reset_android ;; *) - echo "Usage: android-manager.sh {start|stop|reset}" >&2 + echo "Usage: manager.sh {start|stop|reset}" >&2 exit 1 ;; esac diff --git a/scripts/android-setup.sh b/scripts/android/setup.sh similarity index 97% rename from scripts/android-setup.sh rename to scripts/android/setup.sh index c7f43009..df9b509e 100755 --- a/scripts/android-setup.sh +++ b/scripts/android/setup.sh @@ -24,9 +24,10 @@ require_tool() { } script_dir="$(cd "$(dirname "$0")" && pwd)" -if [ -f "$script_dir/platform-versions.sh" ]; then +platform_versions="$script_dir/../platform-versions.sh" +if [ -f "$platform_versions" ]; then # shellcheck disable=SC1090 - . "$script_dir/platform-versions.sh" + . "$platform_versions" fi detect_sdk_root() { diff --git a/scripts/android/test.sh b/scripts/android/test.sh new file mode 100755 index 00000000..13ccec1d --- /dev/null +++ b/scripts/android/test.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd "$(dirname "$0")" && pwd)" +project_root="$(cd "$script_dir/../.." && pwd)" +bash "$project_root/scripts/android/setup.sh" +yarn install +yarn e2e install +yarn build +yarn e2e build:android +yarn e2e test:android diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 00000000..9094f7fe --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +yarn install --immutable +yarn build +yarn lint +yarn test --coverage diff --git a/scripts/ios/env.sh b/scripts/ios/env.sh new file mode 100755 index 00000000..c5ee0993 --- /dev/null +++ b/scripts/ios/env.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +devbox_omit_nix_env() { + if [ "${DEVBOX_OMIT_NIX_ENV_APPLIED:-}" = "1" ]; then + return 0 + fi + + export DEVBOX_OMIT_NIX_ENV_APPLIED=1 + + dump_env() { + echo "devbox omit-nix-env $1" + echo " PATH=$PATH" + echo " CC=${CC:-}" + echo " CXX=${CXX:-}" + echo " LD=${LD:-}" + echo " CPP=${CPP:-}" + echo " AR=${AR:-}" + echo " SDKROOT=${SDKROOT:-}" + echo " DEVELOPER_DIR=${DEVELOPER_DIR:-}" + } + + dump_env "before" + + eval "$(devbox shellenv --init-hook --install --no-refresh-alias --omit-nix-env=true)" + + if [ "$(uname -s)" = "Darwin" ]; then + PATH="$(printf '%s' "$PATH" | tr ':' '\n' | awk '!/^\/nix\/store\//{print}' | paste -sd ':' -)" + + for var in CC CXX LD CPP AR AS NM RANLIB STRIP OBJC OBJCXX SDKROOT DEVELOPER_DIR; do + value="${!var:-}" + if [ -n "$value" ] && [ "${value#/nix/store/}" != "$value" ]; then + unset "$var" + fi + done + + if [ -x /usr/bin/clang ]; then + export CC=/usr/bin/clang + export CXX=/usr/bin/clang++ + fi + + if command -v xcode-select >/dev/null 2>&1; then + dev_dir="$(xcode-select -p 2>/dev/null || true)" + if [ -n "$dev_dir" ]; then + export DEVELOPER_DIR="$dev_dir" + fi + fi + + unset SDKROOT + fi + + dump_env "after" +} + +devbox_omit_nix_env diff --git a/scripts/ios-manager.sh b/scripts/ios/manager.sh similarity index 89% rename from scripts/ios-manager.sh rename to scripts/ios/manager.sh index 96f1f021..501064b4 100755 --- a/scripts/ios-manager.sh +++ b/scripts/ios/manager.sh @@ -2,9 +2,10 @@ set -euo pipefail script_dir="$(cd "$(dirname "$0")" && pwd)" -if [ -f "$script_dir/platform-versions.sh" ]; then +platform_versions="$script_dir/../platform-versions.sh" +if [ -f "$platform_versions" ]; then # shellcheck disable=SC1090 - . "$script_dir/platform-versions.sh" + . "$platform_versions" fi action="${1:-}" @@ -46,7 +47,7 @@ start) start_ios ;; stop) stop_ios ;; reset) reset_ios ;; *) - echo "Usage: ios-manager.sh {start|stop|reset}" >&2 + echo "Usage: manager.sh {start|stop|reset}" >&2 exit 1 ;; esac diff --git a/scripts/ios-setup.sh b/scripts/ios/setup.sh similarity index 98% rename from scripts/ios-setup.sh rename to scripts/ios/setup.sh index 6da78243..8d2aec83 100755 --- a/scripts/ios-setup.sh +++ b/scripts/ios/setup.sh @@ -2,9 +2,10 @@ set -euo pipefail script_dir="$(cd "$(dirname "$0")" && pwd)" -if [ -f "$script_dir/platform-versions.sh" ]; then +platform_versions="$script_dir/../platform-versions.sh" +if [ -f "$platform_versions" ]; then # shellcheck disable=SC1090 - . "$script_dir/platform-versions.sh" + . "$platform_versions" fi # Creates local iOS simulators for common targets. Requires Xcode command-line tools and jq. diff --git a/scripts/ios/test.sh b/scripts/ios/test.sh new file mode 100755 index 00000000..d87cdaf9 --- /dev/null +++ b/scripts/ios/test.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd "$(dirname "$0")" && pwd)" +project_root="$(cd "$script_dir/../.." && pwd)" + +if [ "$(uname -s)" = "Darwin" ]; then + . "$project_root/scripts/ios/env.sh" +fi + +echo "iOS test env" +echo " PATH=$PATH" +echo " CC=${CC:-}" +echo " CXX=${CXX:-}" +echo " SDKROOT=${SDKROOT:-}" +echo " DEVELOPER_DIR=${DEVELOPER_DIR:-}" + +bash "$project_root/scripts/ios/setup.sh" +yarn install +yarn e2e install +yarn e2e pods +yarn build +yarn e2e build:ios +yarn e2e test:ios diff --git a/shells/devbox-android.json b/shells/devbox-android.json index 26b290d7..60e7ba8e 100644 --- a/shells/devbox-android.json +++ b/shells/devbox-android.json @@ -10,18 +10,11 @@ "shell": { "init_hook": [ "echo 'Android SDK env configured (details: wiki/devbox.md#devbox-android).'", - ". $DEVBOX_PROJECT_ROOT/scripts/android-env.sh" + ". $DEVBOX_PROJECT_ROOT/scripts/android/env.sh" ], "scripts": { - "setup-android": ["bash $DEVBOX_PROJECT_ROOT/scripts/android-setup.sh"], - "test-android": [ - "devbox run setup-android", - "yarn install", - "yarn e2e install", - "yarn build", - "yarn e2e build:android", - "yarn e2e test:android" - ] + "setup-android": ["bash $DEVBOX_PROJECT_ROOT/scripts/android/setup.sh"], + "test-android": ["bash $DEVBOX_PROJECT_ROOT/scripts/android/test.sh"] } } } diff --git a/shells/devbox-fast.json b/shells/devbox-fast.json index 4563fdd8..130ce130 100644 --- a/shells/devbox-fast.json +++ b/shells/devbox-fast.json @@ -9,12 +9,7 @@ }, "shell": { "scripts": { - "build": [ - "yarn install --immutable", - "yarn build", - "yarn lint", - "yarn test --coverage" - ] + "build": ["bash $DEVBOX_PROJECT_ROOT/scripts/build.sh"] } } } diff --git a/shells/devbox-ios.json b/shells/devbox-ios.json index 6bc67f2a..6649cf38 100644 --- a/shells/devbox-ios.json +++ b/shells/devbox-ios.json @@ -9,17 +9,12 @@ "jq": "latest" }, "shell": { + "init_hook": [ + "if [ \"$(uname -s)\" = \"Darwin\" ]; then . $DEVBOX_PROJECT_ROOT/scripts/ios/env.sh; fi" + ], "scripts": { - "setup-ios": ["bash $DEVBOX_PROJECT_ROOT/scripts/ios-setup.sh"], - "test-ios": [ - "devbox run setup-ios", - "yarn install", - "yarn e2e install", - "yarn e2e pods", - "yarn build", - "yarn e2e build:ios", - "yarn e2e test:ios" - ] + "setup-ios": ["bash $DEVBOX_PROJECT_ROOT/scripts/ios/setup.sh"], + "test-ios": ["bash $DEVBOX_PROJECT_ROOT/scripts/ios/test.sh"] } } } diff --git a/shells/devbox.lock b/shells/devbox.lock new file mode 100644 index 00000000..ae82190b --- /dev/null +++ b/shells/devbox.lock @@ -0,0 +1,201 @@ +{ + "lockfile_version": "1", + "packages": { + "cocoapods@latest": { + "last_modified": "2025-12-31T03:27:36Z", + "resolved": "github:NixOS/nixpkgs/f665af0cdb70ed27e1bd8f9fdfecaf451260fc55#cocoapods", + "source": "devbox-search", + "version": "1.16.2", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/av5g6hfp0yiir3iavg72js70ian8hxyf-cocoapods-1.16.2", + "default": true + } + ], + "store_path": "/nix/store/av5g6hfp0yiir3iavg72js70ian8hxyf-cocoapods-1.16.2" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/har71589bwmh6h6skisd20b3c6lrwmz7-cocoapods-1.16.2", + "default": true + } + ], + "store_path": "/nix/store/har71589bwmh6h6skisd20b3c6lrwmz7-cocoapods-1.16.2" + } + } + }, + "github:NixOS/nixpkgs/nixpkgs-unstable": { + "last_modified": "2026-01-27T15:18:14Z", + "resolved": "github:NixOS/nixpkgs/afce96367b2e37fc29afb5543573cd49db3357b7?lastModified=1769527094" + }, + "jq@latest": { + "last_modified": "2026-01-20T02:11:35Z", + "resolved": "github:NixOS/nixpkgs/ed142ab1b3a092c4d149245d0c4126a5d7ea00b0#jq", + "source": "devbox-search", + "version": "1.8.1", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "bin", + "path": "/nix/store/9rm6fm3zq1jq8rgsx528cw8wkmfya2gf-jq-1.8.1-bin", + "default": true + }, + { + "name": "man", + "path": "/nix/store/cv999saj62xhq7xv5i7q6944vljykfmw-jq-1.8.1-man", + "default": true + }, + { + "name": "dev", + "path": "/nix/store/5camppj4hz2mgkdbxs0kr6nvh6qa65wf-jq-1.8.1-dev" + }, + { + "name": "doc", + "path": "/nix/store/lak094rhhxlaj1qycadmxyfphgjadj5r-jq-1.8.1-doc" + }, + { + "name": "out", + "path": "/nix/store/g371yvjasdr552v98p5kav7n35s1dfib-jq-1.8.1" + } + ], + "store_path": "/nix/store/9rm6fm3zq1jq8rgsx528cw8wkmfya2gf-jq-1.8.1-bin" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "bin", + "path": "/nix/store/m8qv4g54q3jmjb8i33v9lljcwhydx2vd-jq-1.8.1-bin", + "default": true + }, + { + "name": "man", + "path": "/nix/store/9x2457g76jikfy7xq4mjqwzl8iz3zvxj-jq-1.8.1-man", + "default": true + }, + { + "name": "dev", + "path": "/nix/store/5ykn83b3hhvnnq0p5vqgcrzihrl9wpsl-jq-1.8.1-dev" + }, + { + "name": "doc", + "path": "/nix/store/37ypy1595g6rj3cymh1mpk2b25fx40g7-jq-1.8.1-doc" + }, + { + "name": "out", + "path": "/nix/store/16lg603jzppwjanlakcak1ais69mkd03-jq-1.8.1" + } + ], + "store_path": "/nix/store/m8qv4g54q3jmjb8i33v9lljcwhydx2vd-jq-1.8.1-bin" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "bin", + "path": "/nix/store/kkb17whpkdrmn9g3gk7y6l69vipxsw0i-jq-1.8.1-bin", + "default": true + }, + { + "name": "man", + "path": "/nix/store/iwr61wi83kflqvz8j5nf7ridaqq6nh2w-jq-1.8.1-man", + "default": true + }, + { + "name": "dev", + "path": "/nix/store/lypnqs272644l8ff6wfji9rg5jw10v7h-jq-1.8.1-dev" + }, + { + "name": "doc", + "path": "/nix/store/nyw97c4pywfcqqap5hyk9xjghczlbshl-jq-1.8.1-doc" + }, + { + "name": "out", + "path": "/nix/store/ri930a557685c64bdh88a5031i7hx3vy-jq-1.8.1" + } + ], + "store_path": "/nix/store/kkb17whpkdrmn9g3gk7y6l69vipxsw0i-jq-1.8.1-bin" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "bin", + "path": "/nix/store/zssasryipb2x4gk2ahzacl4mvvcmk48j-jq-1.8.1-bin", + "default": true + }, + { + "name": "man", + "path": "/nix/store/7d4pv1iymyqk2lykwj1ydml3rjhc6gl3-jq-1.8.1-man", + "default": true + }, + { + "name": "dev", + "path": "/nix/store/rmxxm5jnxq93kvkhbr2b3hzj6v3ldp8z-jq-1.8.1-dev" + }, + { + "name": "doc", + "path": "/nix/store/mkhfvc69grlky3iblibkw9wcc12jcdqq-jq-1.8.1-doc" + }, + { + "name": "out", + "path": "/nix/store/807g765zgpmp1c8fm5y40rw2gbr1k6dk-jq-1.8.1" + } + ], + "store_path": "/nix/store/zssasryipb2x4gk2ahzacl4mvvcmk48j-jq-1.8.1-bin" + } + } + }, + "yarn-berry@latest": { + "last_modified": "2025-12-31T03:27:36Z", + "resolved": "github:NixOS/nixpkgs/f665af0cdb70ed27e1bd8f9fdfecaf451260fc55#yarn-berry", + "source": "devbox-search", + "version": "4.12.0", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/2l7sbyyqardvrzr35zkrw67gbng5gb8y-yarn-berry-4.12.0", + "default": true + } + ], + "store_path": "/nix/store/2l7sbyyqardvrzr35zkrw67gbng5gb8y-yarn-berry-4.12.0" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/klx9ndw1djgx0zhhyrkcn9an094rmmwv-yarn-berry-4.12.0", + "default": true + } + ], + "store_path": "/nix/store/klx9ndw1djgx0zhhyrkcn9an094rmmwv-yarn-berry-4.12.0" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/m6cwiya6hrbwnlprh2cbnmz6c7mkylrf-yarn-berry-4.12.0", + "default": true + } + ], + "store_path": "/nix/store/m6cwiya6hrbwnlprh2cbnmz6c7mkylrf-yarn-berry-4.12.0" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/q1gys3zgijcciiafbh9nfawkx5wj8179-yarn-berry-4.12.0", + "default": true + } + ], + "store_path": "/nix/store/q1gys3zgijcciiafbh9nfawkx5wj8179-yarn-berry-4.12.0" + } + } + } + } +} diff --git a/shells/gradle.properties b/shells/gradle.properties new file mode 100644 index 00000000..a468eda9 --- /dev/null +++ b/shells/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.home=/nix/store/hlm8a8cnp4hm8xkg0a2yy4kv7cq44jii-zulu-ca-jdk-17.0.12 diff --git a/wiki/devbox.md b/wiki/devbox.md index def859af..6f35f7e5 100644 --- a/wiki/devbox.md +++ b/wiki/devbox.md @@ -13,13 +13,13 @@ Enter the environment with `devbox shell`. The init hook wires `ANDROID_SDK_ROOT ## Android -By default, Devbox uses the flake-pinned SDK (`path:./nix#android-sdk`). It sets `ANDROID_SDK_ROOT`/`ANDROID_HOME` and adds emulator/platform-tools/cmdline-tools to `PATH` via `scripts/android-env.sh`. Platform versions live in `nix/platform-versions.json` (single source of truth for min/max API and build tools; loaded by `scripts/platform-versions.sh`). To use a local SDK instead, launch with `ANDROID_HOME="$HOME/Library/Android/sdk" devbox shell` (or set `ANDROID_SDK_ROOT`). Clear both env vars to return to the Nix SDK. Inspect the active SDK with `echo "$ANDROID_SDK_ROOT"` and `which sdkmanager` inside the shell. Create/boot AVDs via `devbox run start-android*` (uses `scripts/android-setup.sh` + `scripts/android-manager.sh`). +By default, Devbox uses the flake-pinned SDK (`path:./nix#android-sdk`). It sets `ANDROID_SDK_ROOT`/`ANDROID_HOME` and adds emulator/platform-tools/cmdline-tools to `PATH` via `scripts/android/env.sh`. Platform versions live in `nix/platform-versions.json` (single source of truth for min/max API and build tools; loaded by `scripts/platform-versions.sh`). To use a local SDK instead, launch with `ANDROID_HOME="$HOME/Library/Android/sdk" devbox shell` (or set `ANDROID_SDK_ROOT`). Clear both env vars to return to the Nix SDK. Inspect the active SDK with `echo "$ANDROID_SDK_ROOT"` and `which sdkmanager` inside the shell. Create/boot AVDs via `devbox run start-android*` (uses `scripts/android/setup.sh` + `scripts/android/manager.sh`). ### Emulator/AVD scripts - `devbox run start-android` launches the default “latest” AVD (API 33, Medium Phone). On arm64 hosts it uses the arm64-v8a image; on Intel it uses x86_64. Override with `AVD_FLAVOR=minsdk` to launch the API 21 Pixel AVD instead. You can also set `DETOX_AVD` to pick an exact AVD name. -- `devbox run start-android-latest` / `start-android-minsdk` explicitly launch the latest (API 33) or minsdk (API 21) AVDs. Both will create the AVD first via `scripts/android-setup.sh` if it does not exist. -- `scripts/android-setup.sh` now accepts env overrides: `AVD_API`, `AVD_DEVICE`, `AVD_TAG`, `AVD_ABI`, `AVD_NAME`. Defaults target API 21 for minsdk; CI passes API 33 for latest. The script auto-selects the best ABI for the host (arm64-v8a on arm, x86_64 on Intel) if `AVD_ABI` is unset. +- `devbox run start-android-latest` / `start-android-minsdk` explicitly launch the latest (API 33) or minsdk (API 21) AVDs. Both will create the AVD first via `scripts/android/setup.sh` if it does not exist. +- `scripts/android/setup.sh` accepts env overrides: `AVD_API`, `AVD_DEVICE`, `AVD_TAG`, `AVD_ABI`, `AVD_NAME`. Defaults target API 21 for minsdk; CI passes API 33 for latest. The script auto-selects the best ABI for the host (arm64-v8a on arm, x86_64 on Intel) if `AVD_ABI` is unset. - `devbox run reset-android` removes local AVDs/adb keys if you need a clean slate. - `EMU_HEADLESS=1 devbox run start-android*` to run the emulator headless (CI sets this); omit for a visible emulator locally. - `EMU_PORT=5554 devbox run start-android*` to set the emulator port/serial (defaults to 5554) and avoid adb conflicts. @@ -43,13 +43,12 @@ By default, Devbox uses the flake-pinned SDK (`path:./nix#android-sdk`). It sets iOS uses the host Xcode toolchain. There is no Nix-provisioned iOS SDK. Run `devbox run setup-ios` to install pods and bootstrap the iOS example/E2E apps. Full Xcode is required for `simctl` (Command Line Tools alone are not enough). Make sure Xcode command line tools are selected (`xcode-select --print-path` or `sudo xcode-select -s /Applications/Xcode.app/Contents/Developer`) and that you have agreed to the license if prompted. -> Important: `devbox run` by itself uses the Nix compiler toolchain on macOS. For iOS work you must route commands through `devbox shell --omit-nix-env --command ""` (or run an interactive `devbox shell --omit-nix-env` first) so the Xcode toolchain is used. CI does this for setup/tests. +> Important: `devbox shell` injects Nix toolchain variables on macOS, which can break Xcode builds. The init hooks source `scripts/ios/env.sh` to undo that and re-select the system toolchain, and `scripts/ios/test.sh` re-applies it before running E2E. ### Simulators and Detox - `devbox run setup-ios` provisions simulators. Defaults are driven by `nix/platform-versions.json` (min/max device/runtime). Override via env vars to target a specific device/runtime. Set `IOS_DOWNLOAD_RUNTIME=0` to skip attempting `xcodebuild -downloadPlatform iOS` when the preferred runtime is missing. Set `IOS_DEVELOPER_DIR` (e.g., `/Applications/Xcode.app/Contents/Developer`) to point at a specific Xcode; otherwise it uses `xcode-select -p` or the default Xcode.app if found. -- On macOS, use `devbox shell --omit-nix-env --command ""` when invoking iOS builds/tests to ensure the Xcode toolchain is used instead of the Nix compiler toolchain. -- `devbox run start-ios` provisions simulators (via `setup-ios`), then boots the chosen device (`DETOX_IOS_DEVICE` or default `iPhone 17`) and opens Simulator. Set `IOS_FLAVOR=minsdk` to target the min sim (per `nix/platform-versions.json`) or leave default for latest. Internally uses `scripts/ios-manager.sh`. +- `devbox run start-ios` provisions simulators (via `setup-ios`), then boots the chosen device (`DETOX_IOS_DEVICE` or default `iPhone 17`) and opens Simulator. Set `IOS_FLAVOR=minsdk` to target the min sim (per `nix/platform-versions.json`) or leave default for latest. Internally uses `scripts/ios/manager.sh`. - `devbox run reset-ios` shuts down/erases and removes all local simulator devices. - `devbox run stop-android` / `stop-ios` / `stop` to shut down running emulators/simulators (handy for headless runs). - Detox defaults to `iPhone 17` for local runs; override with `DETOX_IOS_DEVICE`. CI runs a matrix: min sim (from `nix/platform-versions.json`) and latest (iPhone 17 @ latest runtime). @@ -96,5 +95,5 @@ Run them locally with: ```sh devbox run --config=shells/devbox-fast.json build devbox run --config=shells/devbox-android.json test-android -devbox shell --omit-nix-env -- devbox run --config=shells/devbox-ios.json test-ios +devbox run --config=shells/devbox-ios.json test-ios ``` From e3c28212f86fed889226afdc520986c1e4d8385b Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 28 Jan 2026 13:25:13 -0600 Subject: [PATCH 5/8] script refactor, publish workflow verification --- .../{ci-e2e-nightly.yml => ci-e2e-full.yml} | 4 +- ...{ci-e2e-optional.yml => ci-e2e-latest.yml} | 4 +- .github/workflows/ci-fast.yml | 18 --- .github/workflows/publish.yml | 127 +++++++++++++-- .gitignore | 3 + AGENTS.md | 35 ----- devbox.json | 7 + scripts/act-ci.sh | 2 +- scripts/android/env.sh | 13 +- scripts/android/setup.sh | 15 +- scripts/build.sh | 1 - scripts/ios/manager.sh | 8 +- scripts/ios/setup.sh | 144 +----------------- scripts/ios/simctl.sh | 127 +++++++++++++++ scripts/shared/common.sh | 19 +++ shells/devbox-fast.json | 4 +- wiki/devbox.md | 29 +--- wiki/nix.md | 37 +++++ wiki/scripts.md | 88 +++++++++++ 19 files changed, 426 insertions(+), 259 deletions(-) rename .github/workflows/{ci-e2e-nightly.yml => ci-e2e-full.yml} (98%) rename .github/workflows/{ci-e2e-optional.yml => ci-e2e-latest.yml} (97%) delete mode 100644 AGENTS.md create mode 100644 scripts/ios/simctl.sh create mode 100644 scripts/shared/common.sh create mode 100644 wiki/nix.md create mode 100644 wiki/scripts.md diff --git a/.github/workflows/ci-e2e-nightly.yml b/.github/workflows/ci-e2e-full.yml similarity index 98% rename from .github/workflows/ci-e2e-nightly.yml rename to .github/workflows/ci-e2e-full.yml index c0a50596..b973cee8 100644 --- a/.github/workflows/ci-e2e-nightly.yml +++ b/.github/workflows/ci-e2e-full.yml @@ -1,4 +1,4 @@ -name: E2E (Nightly) +name: E2E (Full) on: schedule: @@ -6,7 +6,7 @@ on: workflow_dispatch: concurrency: - group: e2e-nightly-${{ github.ref }} + group: e2e-full-${{ github.ref }} cancel-in-progress: false jobs: diff --git a/.github/workflows/ci-e2e-optional.yml b/.github/workflows/ci-e2e-latest.yml similarity index 97% rename from .github/workflows/ci-e2e-optional.yml rename to .github/workflows/ci-e2e-latest.yml index cf9406ec..148328e6 100644 --- a/.github/workflows/ci-e2e-optional.yml +++ b/.github/workflows/ci-e2e-latest.yml @@ -1,10 +1,10 @@ -name: E2E (On-Demand) +name: E2E (Latest) on: workflow_dispatch: concurrency: - group: e2e-optional-${{ github.ref }} + group: e2e-latest-${{ github.ref }} cancel-in-progress: false jobs: diff --git a/.github/workflows/ci-fast.yml b/.github/workflows/ci-fast.yml index 0b517efb..7e767fa9 100644 --- a/.github/workflows/ci-fast.yml +++ b/.github/workflows/ci-fast.yml @@ -15,24 +15,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Aggressive disk cleanup (Ubuntu) - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - sudo rm -rf /opt/hostedtoolcache/CodeQL - sudo rm -rf /usr/local/lib/android - sudo rm -rf /usr/local/lib/node_modules - sudo rm -rf /usr/local/share/boost - sudo rm -rf /usr/local/share/chromium - sudo rm -rf /usr/local/share/powershell - sudo rm -rf /usr/local/share/edge_driver - sudo rm -rf /usr/local/share/gecko_driver - sudo rm -rf /usr/local/share/phantomjs - sudo rm -rf "$HOME/.cache" - df -H - - name: Free Disk Space (Ubuntu) - uses: jlumbroso/free-disk-space@v1.3.1 - name: devbox installer uses: jetify-com/devbox-install-action@v0.14.0 with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 42e8ef3f..d49d03f4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,9 +9,118 @@ on: required: true jobs: + fast-checks: + name: Build + Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: devbox installer + uses: jetify-com/devbox-install-action@v0.14.0 + with: + enable-cache: 'false' + - name: build + run: devbox run --config=shells/devbox-fast.json build + + e2e-ios: + name: E2E iOS (min/max) + runs-on: macos-26 + env: + YARN_ENABLE_HARDENED_MODE: 0 + XCODE_VERSION: '26.2' + strategy: + matrix: + include: + - name: ios-min + - name: ios-latest + steps: + - uses: actions/checkout@v4 + - name: Aggressive disk cleanup (macOS) + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + sudo rm -rf /Applications/Android\ Studio.app + sudo rm -rf /usr/local/share/miniconda + sudo rm -rf /opt/homebrew + sudo rm -rf "$HOME/Library/Android" + sudo rm -rf "$HOME/.gradle" + sudo rm -rf "$HOME/Library/Developer/CoreSimulator/Devices" + sudo rm -rf "$HOME/Library/Developer/Xcode/DerivedData" + df -H + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '26.2' + - name: devbox installer + uses: jetify-com/devbox-install-action@v0.14.0 + with: + enable-cache: 'false' + - name: Resolve iOS targets + run: | + . scripts/platform-versions.sh + if [ "${{ matrix.name }}" = "ios-min" ]; then + echo "DETOX_IOS_DEVICE=${PLATFORM_IOS_MIN_DEVICE}" >> "$GITHUB_ENV" + echo "IOS_RUNTIME=${PLATFORM_IOS_MIN_RUNTIME}" >> "$GITHUB_ENV" + else + echo "DETOX_IOS_DEVICE=${PLATFORM_IOS_MAX_DEVICE}" >> "$GITHUB_ENV" + echo "IOS_RUNTIME=${PLATFORM_IOS_MAX_RUNTIME}" >> "$GITHUB_ENV" + fi + - name: iOS E2E Tests + run: devbox run --config=shells/devbox-ios.json test-ios + + e2e-android: + name: E2E Android (min/max) + runs-on: ubuntu-24.04-arm + env: + EMU_HEADLESS: 1 + strategy: + matrix: + include: + - name: android-min + target: min + - name: android-latest + target: max + steps: + - uses: actions/checkout@v4 + - name: Aggressive disk cleanup (Ubuntu) + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo rm -rf /usr/local/lib/android + sudo rm -rf /usr/local/lib/node_modules + sudo rm -rf /usr/local/share/boost + sudo rm -rf /usr/local/share/chromium + sudo rm -rf /usr/local/share/powershell + sudo rm -rf /usr/local/share/edge_driver + sudo rm -rf /usr/local/share/gecko_driver + sudo rm -rf /usr/local/share/phantomjs + sudo rm -rf "$HOME/.cache" + df -H + - name: devbox installer + uses: jetify-com/devbox-install-action@v0.14.0 + with: + enable-cache: 'false' + - name: Resolve Android targets + run: | + . scripts/platform-versions.sh + if [ "${{ matrix.target }}" = "min" ]; then + api="$PLATFORM_ANDROID_MIN_API" + device="$PLATFORM_ANDROID_MIN_DEVICE" + else + api="$PLATFORM_ANDROID_MAX_API" + device="$PLATFORM_ANDROID_MAX_DEVICE" + fi + avd_name="${device}_API${api}_arm64_v8a" + echo "DETOX_AVD=${avd_name}" >> "$GITHUB_ENV" + - name: Android E2E Tests + run: devbox run --config=shells/devbox-android.json test-android + publish: name: Publish to npm environment: Publish + needs: [fast-checks, e2e-ios, e2e-android] runs-on: ubuntu-latest permissions: contents: write # to be able to publish a GitHub release @@ -23,22 +132,10 @@ jobs: fetch-depth: 0 token: ${{ secrets.GH_TOKEN }} - # Workaround for corepack enable in node - # Source: (https://github.com/actions/setup-node/issues/899#issuecomment-1828798029) - - uses: actions/setup-node@v4 - with: - node-version: 20 - - run: corepack enable - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: yarn - # End workaround - - name: devbox installer uses: jetify-com/devbox-install-action@v0.14.0 with: - enable-cache: 'true' + enable-cache: 'false' - name: Config, Build, Release run: devbox run release @@ -49,6 +146,4 @@ jobs: - name: Update Apps run: | - yarn install --no-immutable - yarn e2e install --no-immutable - yarn example install --no-immutable + devbox run update-apps diff --git a/.gitignore b/.gitignore index f144e2ac..73d538db 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,6 @@ tsconfig.tsbuildinfo packages/core/src/info.ts .pnpm/ + +AGENTS.md + diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index c0372124..00000000 --- a/AGENTS.md +++ /dev/null @@ -1,35 +0,0 @@ -# Repository Guidelines - -## Project Structure & Module Organization - -- Yarn workspaces live under `packages`: `core` (SDK runtime), `sovran` (state store), `shared` (cross-package utilities), and `plugins/*` (destination and helper plugins). Native iOS/Android sources reside in each package’s `ios` and `android` folders. -- Example apps sit in `examples/AnalyticsReactNativeExample` (manual QA) and `examples/E2E` (Detox). Scripts are in `scripts/`; configuration lives alongside packages (e.g., `tsconfig.json`, `babel.config.js`, `jest.config.js`). - -## Build, Test, and Development Commands - -- `yarn bootstrap`: install root + workspace deps and pod install for example/e2e apps. -- `yarn build`: run workspace builds in topo order. -- `yarn testAll` / `yarn test`: workspace Jest suite or root Jest run. -- `yarn lint`; `yarn lint --fix`: ESLint across the monorepo. -- `yarn typescript`: type-check without emitting. -- Example app: `yarn example start | ios | android`. Detox: `yarn e2e start:e2e` then platform builds/tests (e.g., `yarn e2e e2e:build:ios` + `yarn e2e e2e:test:ios`). - -## Coding Style & Naming Conventions - -- TypeScript-first; native code should mirror existing Swift/Obj-C/Kotlin style. Two-space indentation and Prettier formatting via ESLint rules. -- Prefer camelCase for variables/functions, PascalCase for React components/classes, and UPPER_SNAKE for constants. Plugin packages follow `plugin-*` folder naming and publish as scoped `@segment/*`. -- Keep public APIs typed and documented; colocate utilities with their feature module (e.g., `src/plugins`, `src/__tests__`). - -## Testing Guidelines - -- Unit tests use Jest with tests under `__tests__` near source; snapshots live in `__tests__/__snapshots__`. -- End-to-end coverage uses Detox in `examples/E2E`; build and run per platform before pushing. Add regression tests for new behaviors and keep existing snapshots updated only when intentional. - -## Commit & Pull Request Guidelines - -- Commit messages follow Conventional Commits (`feat`, `fix`, `chore`, etc.); enforced by commitlint and release automation. -- For PRs, keep scope narrow, link issues when relevant, and note user-facing changes. Ensure `yarn lint`, `yarn typescript`, and the relevant `yarn test*`/Detox flows pass. Include screenshots only when UI changes affect the example app. - -## Security & Configuration Tips - -- Do not commit real Segment write keys or private endpoints; use placeholder values in examples and tests. Keep secrets out of `examples/` and CI config. When testing proxies/CDN settings, prefer environment-driven config rather than hardcoding. diff --git a/devbox.json b/devbox.json index 3035f1f3..873aca04 100644 --- a/devbox.json +++ b/devbox.json @@ -31,6 +31,8 @@ "find $DEVBOX_PROJECT_DIR -type d -name node_modules -exec rmdir {} \\;" ], "build": ["bash $DEVBOX_PROJECT_ROOT/scripts/build.sh"], + "format": ["treefmt"], + "lint": ["treefmt --fail-on-change"], "test-android": ["bash $DEVBOX_PROJECT_ROOT/scripts/android/test.sh"], "test-ios": ["bash $DEVBOX_PROJECT_ROOT/scripts/ios/test.sh"], "act-ci": [ @@ -57,6 +59,11 @@ "yarn build", "yarn release" ], + "update-apps": [ + "yarn install --no-immutable", + "yarn e2e install --no-immutable", + "yarn example install --no-immutable" + ], "reset-android": [ "rm -rf ~/.android/avd", "rm -f ~/.android/adbkey*", diff --git a/scripts/act-ci.sh b/scripts/act-ci.sh index 9057ea1f..60c7f85a 100755 --- a/scripts/act-ci.sh +++ b/scripts/act-ci.sh @@ -8,7 +8,7 @@ JOB="" PLATFORMS=() host_arch="$(uname -m)" -if [[ "$host_arch" == "arm64" || "$host_arch" == "aarch64" ]]; then +if [[ $host_arch == "arm64" || $host_arch == "aarch64" ]]; then PLATFORMS+=("ubuntu-24.04-arm=ghcr.io/catthehacker/ubuntu:act-24.04") else PLATFORMS+=("ubuntu-24.04=ghcr.io/catthehacker/ubuntu:act-24.04") diff --git a/scripts/android/env.sh b/scripts/android/env.sh index 56cfb171..0ffe2437 100755 --- a/scripts/android/env.sh +++ b/scripts/android/env.sh @@ -1,13 +1,12 @@ -#!/usr/bin/env sh +#!/usr/bin/env bash # Sets ANDROID_SDK_ROOT/ANDROID_HOME and PATH to the flake-pinned SDK if not already set. # Load shared platform versions if present. -script_dir="$(cd "$(dirname "$0")" && pwd)" -platform_versions="$script_dir/../platform-versions.sh" -if [ -f "$platform_versions" ]; then - # shellcheck disable=SC1090 - . "$platform_versions" -fi +script_path="${BASH_SOURCE[0]:-$0}" +script_dir="$(cd "$(dirname "$script_path")" && pwd)" +# shellcheck disable=SC1090 +. "$script_dir/../shared/common.sh" +load_platform_versions "$script_dir" if [ -z "${ANDROID_MIN_API:-}" ] && [ -n "${PLATFORM_ANDROID_MIN_API:-}" ]; then ANDROID_MIN_API="$PLATFORM_ANDROID_MIN_API" diff --git a/scripts/android/setup.sh b/scripts/android/setup.sh index df9b509e..ffc0b7ce 100755 --- a/scripts/android/setup.sh +++ b/scripts/android/setup.sh @@ -16,19 +16,10 @@ set -euo pipefail # AVD_SECONDARY_ABI (preferred ABI; optional) # AVD_SECONDARY_NAME (override final name) -require_tool() { - if ! command -v "$1" >/dev/null 2>&1; then - echo "Missing required tool: $1. Ensure devbox shell is active and required packages are installed." >&2 - exit 1 - fi -} - script_dir="$(cd "$(dirname "$0")" && pwd)" -platform_versions="$script_dir/../platform-versions.sh" -if [ -f "$platform_versions" ]; then - # shellcheck disable=SC1090 - . "$platform_versions" -fi +# shellcheck disable=SC1090 +. "$script_dir/../shared/common.sh" +load_platform_versions "$script_dir" detect_sdk_root() { if [[ -n ${ANDROID_SDK_ROOT:-} ]]; then diff --git a/scripts/build.sh b/scripts/build.sh index 9094f7fe..c16ac31c 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -4,4 +4,3 @@ set -euo pipefail yarn install --immutable yarn build yarn lint -yarn test --coverage diff --git a/scripts/ios/manager.sh b/scripts/ios/manager.sh index 501064b4..52359735 100755 --- a/scripts/ios/manager.sh +++ b/scripts/ios/manager.sh @@ -2,11 +2,9 @@ set -euo pipefail script_dir="$(cd "$(dirname "$0")" && pwd)" -platform_versions="$script_dir/../platform-versions.sh" -if [ -f "$platform_versions" ]; then - # shellcheck disable=SC1090 - . "$platform_versions" -fi +# shellcheck disable=SC1090 +. "$script_dir/../shared/common.sh" +load_platform_versions "$script_dir" action="${1:-}" shift || true diff --git a/scripts/ios/setup.sh b/scripts/ios/setup.sh index 8d2aec83..de7827ca 100755 --- a/scripts/ios/setup.sh +++ b/scripts/ios/setup.sh @@ -2,11 +2,11 @@ set -euo pipefail script_dir="$(cd "$(dirname "$0")" && pwd)" -platform_versions="$script_dir/../platform-versions.sh" -if [ -f "$platform_versions" ]; then - # shellcheck disable=SC1090 - . "$platform_versions" -fi +# shellcheck disable=SC1090 +. "$script_dir/../shared/common.sh" +# shellcheck disable=SC1090 +. "$script_dir/simctl.sh" +load_platform_versions "$script_dir" # Creates local iOS simulators for common targets. Requires Xcode command-line tools and jq. # Env overrides: @@ -15,13 +15,6 @@ fi # IOS_DOWNLOAD_RUNTIME=1 to attempt xcodebuild -downloadPlatform iOS when the preferred runtime is missing # IOS_DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer" to override the Xcode path; defaults to xcode-select -p or the standard Xcode.app if found -require_tool() { - if ! command -v "$1" >/dev/null 2>&1; then - echo "Missing required tool: $1. Install Xcode CLI tools before running (xcode-select --install or Xcode.app + xcode-select -s)." >&2 - exit 1 - fi -} - ensure_developer_dir() { local desired="${IOS_DEVELOPER_DIR:-}" if [[ -z $desired ]]; then @@ -44,7 +37,7 @@ ensure_developer_dir() { ensure_developer_dir -require_tool xcrun +require_tool xcrun "Missing required tool: xcrun. Install Xcode CLI tools before running (xcode-select --install or Xcode.app + xcode-select -s)." require_tool jq ensure_simctl() { @@ -63,131 +56,6 @@ EOF ensure_simctl -ensure_core_sim_service() { - local output status - output="$(xcrun simctl list devices -j 2>&1)" || status=$? - if [[ -n ${status:-} ]]; then - echo "simctl failed while listing devices (status ${status}). CoreSimulatorService may be unhealthy." >&2 - echo "Try restarting it:" >&2 - echo " killall -9 com.apple.CoreSimulatorService 2>/dev/null || true" >&2 - echo " launchctl kickstart -k gui/$UID/com.apple.CoreSimulatorService" >&2 - echo "Then open Simulator once and rerun devbox run setup-ios." >&2 - echo "simctl error output:" >&2 - echo "$output" >&2 - return 1 - fi - - if echo "$output" | grep -q "CoreSimulatorService connection became invalid"; then - echo "CoreSimulatorService is not healthy. Try restarting it:" >&2 - echo " killall -9 com.apple.CoreSimulatorService 2>/dev/null || true" >&2 - echo " launchctl kickstart -k gui/$UID/com.apple.CoreSimulatorService" >&2 - echo "Then open Simulator once and rerun devbox run setup-ios." >&2 - echo "simctl error output:" >&2 - echo "$output" >&2 - return 1 - fi -} - -pick_runtime() { - local preferred="$1" - local json choice - json="$(xcrun simctl list runtimes -j)" - choice="$(echo "$json" | jq -r --arg v "$preferred" '.runtimes[] | select(.isAvailable and (.name|startswith("iOS \($v)"))) | "\(.identifier)|\(.name)"' | head -n1)" - if [[ -z $choice || $choice == "null" ]]; then - choice="$(echo "$json" | jq -r '.runtimes[] | select(.isAvailable and (.name|startswith("iOS "))) | "\(.version)|\(.identifier)|\(.name)"' | sort -Vr | head -n1 | cut -d"|" -f2-)" - fi - [[ -n $choice && $choice != "null" ]] || return 1 - echo "$choice" -} - -resolve_runtime() { - local preferred="$1" - if choice=$(pick_runtime "$preferred"); then - echo "$choice" - return 0 - fi - - if [[ ${IOS_DOWNLOAD_RUNTIME:-1} != "0" ]] && command -v xcodebuild >/dev/null 2>&1; then - echo "Preferred runtime iOS ${preferred} not found. Attempting to download via xcodebuild -downloadPlatform iOS..." >&2 - if xcodebuild -downloadPlatform iOS; then - if choice=$(pick_runtime "$preferred"); then - echo "$choice" - return 0 - fi - else - echo "xcodebuild -downloadPlatform iOS failed; continuing with available runtimes." >&2 - fi - fi - - pick_runtime "$preferred" -} - -existing_device_udid_any_runtime() { - local name="$1" - xcrun simctl list devices -j | jq -r --arg name "$name" '.devices[]?[]? | select(.name == $name) | .udid' | head -n1 -} - -device_data_dir_exists() { - local udid="${1:-}" - [[ -n $udid ]] || return 1 - local dir="$HOME/Library/Developer/CoreSimulator/Devices/$udid" - [[ -d $dir ]] -} - -devicetype_id_for_name() { - local name="$1" - xcrun simctl list devicetypes -j | jq -r --arg name "$name" '.devicetypes[] | select((.name|ascii_downcase) == ($name|ascii_downcase)) | .identifier' | head -n1 -} - -ensure_device() { - local base_name="$1" preferred_runtime="$2" - - # If a device with this name already exists anywhere, reuse it. - if - existing_udid=$(existing_device_udid_any_runtime "$base_name") - [[ -n ${existing_udid} ]] - then - if device_data_dir_exists "$existing_udid"; then - echo "Found existing ${base_name}: ${existing_udid}" - return 0 - fi - echo "Existing ${base_name} (${existing_udid}) is missing its data directory. Deleting stale simulator..." - xcrun simctl delete "$existing_udid" || true - fi - - local choice runtime_id runtime_name - if ! choice=$(resolve_runtime "$preferred_runtime"); then - echo "No available iOS simulator runtime found. Install one in Xcode (Settings > Platforms) and retry." >&2 - return 1 - fi - runtime_id="$(echo "$choice" | cut -d'|' -f1)" - runtime_name="$(echo "$choice" | cut -d'|' -f2)" - - local display_name="${base_name} (${runtime_name})" - - if ! device_type=$(devicetype_id_for_name "$base_name"); then - echo "Device type '${base_name}' is unavailable in this Xcode install. Skipping ${display_name}." >&2 - return 0 - fi - - # Also check for an existing device with the runtime-qualified display name. - if - existing_udid=$(existing_device_udid_any_runtime "$display_name") - [[ -n ${existing_udid} ]] - then - if device_data_dir_exists "$existing_udid"; then - echo "Found existing ${display_name}: ${existing_udid}" - return 0 - fi - echo "Existing ${display_name} (${existing_udid}) is missing its data directory. Deleting stale simulator..." - xcrun simctl delete "$existing_udid" || true - fi - - echo "Creating ${display_name}..." - xcrun simctl create "$display_name" "$device_type" "$runtime_id" - echo "Created ${display_name}" -} - main() { ensure_core_sim_service || return 1 IFS=',' read -r -a devices <<<"${IOS_DEVICE_NAMES:-${IOS_MIN_DEVICE:-${PLATFORM_IOS_MIN_DEVICE:-iPhone 13}},${IOS_MAX_DEVICE:-${PLATFORM_IOS_MAX_DEVICE:-iPhone 17}}}" diff --git a/scripts/ios/simctl.sh b/scripts/ios/simctl.sh new file mode 100644 index 00000000..ddfe7bf2 --- /dev/null +++ b/scripts/ios/simctl.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +set -euo pipefail + +ensure_core_sim_service() { + local output status + output="$(xcrun simctl list devices -j 2>&1)" || status=$? + if [[ -n ${status:-} ]]; then + echo "simctl failed while listing devices (status ${status}). CoreSimulatorService may be unhealthy." >&2 + echo "Try restarting it:" >&2 + echo " killall -9 com.apple.CoreSimulatorService 2>/dev/null || true" >&2 + echo " launchctl kickstart -k gui/$UID/com.apple.CoreSimulatorService" >&2 + echo "Then open Simulator once and rerun devbox run setup-ios." >&2 + echo "simctl error output:" >&2 + echo "$output" >&2 + return 1 + fi + + if echo "$output" | grep -q "CoreSimulatorService connection became invalid"; then + echo "CoreSimulatorService is not healthy. Try restarting it:" >&2 + echo " killall -9 com.apple.CoreSimulatorService 2>/dev/null || true" >&2 + echo " launchctl kickstart -k gui/$UID/com.apple.CoreSimulatorService" >&2 + echo "Then open Simulator once and rerun devbox run setup-ios." >&2 + echo "simctl error output:" >&2 + echo "$output" >&2 + return 1 + fi +} + +pick_runtime() { + local preferred="$1" + local json choice + json="$(xcrun simctl list runtimes -j)" + choice="$(echo "$json" | jq -r --arg v "$preferred" '.runtimes[] | select(.isAvailable and (.name|startswith("iOS \($v)"))) | "\(.identifier)|\(.name)"' | head -n1)" + if [[ -z $choice || $choice == "null" ]]; then + choice="$(echo "$json" | jq -r '.runtimes[] | select(.isAvailable and (.name|startswith("iOS "))) | "\(.version)|\(.identifier)|\(.name)"' | sort -Vr | head -n1 | cut -d"|" -f2-)" + fi + [[ -n $choice && $choice != "null" ]] || return 1 + echo "$choice" +} + +resolve_runtime() { + local preferred="$1" + if choice=$(pick_runtime "$preferred"); then + echo "$choice" + return 0 + fi + + if [[ ${IOS_DOWNLOAD_RUNTIME:-1} != "0" ]] && command -v xcodebuild >/dev/null 2>&1; then + echo "Preferred runtime iOS ${preferred} not found. Attempting to download via xcodebuild -downloadPlatform iOS..." >&2 + if xcodebuild -downloadPlatform iOS; then + if choice=$(pick_runtime "$preferred"); then + echo "$choice" + return 0 + fi + else + echo "xcodebuild -downloadPlatform iOS failed; continuing with available runtimes." >&2 + fi + fi + + pick_runtime "$preferred" +} + +existing_device_udid_any_runtime() { + local name="$1" + xcrun simctl list devices -j | jq -r --arg name "$name" '.devices[]?[]? | select(.name == $name) | .udid' | head -n1 +} + +device_data_dir_exists() { + local udid="${1:-}" + [[ -n $udid ]] || return 1 + local dir="$HOME/Library/Developer/CoreSimulator/Devices/$udid" + [[ -d $dir ]] +} + +devicetype_id_for_name() { + local name="$1" + xcrun simctl list devicetypes -j | jq -r --arg name "$name" '.devicetypes[] | select((.name|ascii_downcase) == ($name|ascii_downcase)) | .identifier' | head -n1 +} + +ensure_device() { + local base_name="$1" preferred_runtime="$2" + + # If a device with this name already exists anywhere, reuse it. + if + existing_udid=$(existing_device_udid_any_runtime "$base_name") + [[ -n ${existing_udid} ]] + then + if device_data_dir_exists "$existing_udid"; then + echo "Found existing ${base_name}: ${existing_udid}" + return 0 + fi + echo "Existing ${base_name} (${existing_udid}) is missing its data directory. Deleting stale simulator..." + xcrun simctl delete "$existing_udid" || true + fi + + local choice runtime_id runtime_name + if ! choice=$(resolve_runtime "$preferred_runtime"); then + echo "No available iOS simulator runtime found. Install one in Xcode (Settings > Platforms) and retry." >&2 + return 1 + fi + runtime_id="$(echo "$choice" | cut -d'|' -f1)" + runtime_name="$(echo "$choice" | cut -d'|' -f2)" + + local display_name="${base_name} (${runtime_name})" + + if ! device_type=$(devicetype_id_for_name "$base_name"); then + echo "Device type '${base_name}' is unavailable in this Xcode install. Skipping ${display_name}." >&2 + return 0 + fi + + # Also check for an existing device with the runtime-qualified display name. + if + existing_udid=$(existing_device_udid_any_runtime "$display_name") + [[ -n ${existing_udid} ]] + then + if device_data_dir_exists "$existing_udid"; then + echo "Found existing ${display_name}: ${existing_udid}" + return 0 + fi + echo "Existing ${display_name} (${existing_udid}) is missing its data directory. Deleting stale simulator..." + xcrun simctl delete "$existing_udid" || true + fi + + echo "Creating ${display_name}..." + xcrun simctl create "$display_name" "$device_type" "$runtime_id" + echo "Created ${display_name}" +} diff --git a/scripts/shared/common.sh b/scripts/shared/common.sh new file mode 100644 index 00000000..84dd04c9 --- /dev/null +++ b/scripts/shared/common.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env sh + +require_tool() { + tool="$1" + message="${2:-Missing required tool: $tool. Ensure devbox shell is active and required packages are installed.}" + if ! command -v "$tool" >/dev/null 2>&1; then + echo "$message" >&2 + exit 1 + fi +} + +load_platform_versions() { + base_dir="$1" + platform_versions="${base_dir%/}/../platform-versions.sh" + if [ -f "$platform_versions" ]; then + # shellcheck disable=SC1090 + . "$platform_versions" + fi +} diff --git a/shells/devbox-fast.json b/shells/devbox-fast.json index 130ce130..4fd6373d 100644 --- a/shells/devbox-fast.json +++ b/shells/devbox-fast.json @@ -9,7 +9,9 @@ }, "shell": { "scripts": { - "build": ["bash $DEVBOX_PROJECT_ROOT/scripts/build.sh"] + "build": ["bash $DEVBOX_PROJECT_ROOT/scripts/build.sh"], + "format": ["treefmt"], + "lint": ["treefmt --fail-on-change"] } } } diff --git a/wiki/devbox.md b/wiki/devbox.md index 6f35f7e5..6011db96 100644 --- a/wiki/devbox.md +++ b/wiki/devbox.md @@ -2,7 +2,7 @@ This repo ships a Devbox environment that preinstalls the Android SDK and common build tools like Gradle and Yarn. Devbox uses Nix under the hood to pin versions so everyone has the same setup. You don’t need to know Nix to use it. -Enter the environment with `devbox shell`. The init hook wires `ANDROID_SDK_ROOT`/`ANDROID_HOME` and PATH. Common scripts run via `devbox run`, for example `devbox run build`. For Devbox basics, see the official docs: https://www.jetify.com/devbox/docs/. +Enter the environment with `devbox shell`. The init hook wires `ANDROID_SDK_ROOT`/`ANDROID_HOME` and PATH. Common scripts run via `devbox run`, for example `devbox run build`. For Devbox basics, see the official docs: https://www.jetify.com/devbox/docs/. For script layout, see `wiki/scripts.md`; for Nix/SDK versions, see `wiki/nix.md`. ## Getting started @@ -13,7 +13,7 @@ Enter the environment with `devbox shell`. The init hook wires `ANDROID_SDK_ROOT ## Android -By default, Devbox uses the flake-pinned SDK (`path:./nix#android-sdk`). It sets `ANDROID_SDK_ROOT`/`ANDROID_HOME` and adds emulator/platform-tools/cmdline-tools to `PATH` via `scripts/android/env.sh`. Platform versions live in `nix/platform-versions.json` (single source of truth for min/max API and build tools; loaded by `scripts/platform-versions.sh`). To use a local SDK instead, launch with `ANDROID_HOME="$HOME/Library/Android/sdk" devbox shell` (or set `ANDROID_SDK_ROOT`). Clear both env vars to return to the Nix SDK. Inspect the active SDK with `echo "$ANDROID_SDK_ROOT"` and `which sdkmanager` inside the shell. Create/boot AVDs via `devbox run start-android*` (uses `scripts/android/setup.sh` + `scripts/android/manager.sh`). +By default, Devbox uses the flake-pinned SDK (`path:./nix#android-sdk`). It sets `ANDROID_SDK_ROOT`/`ANDROID_HOME` and adds emulator/platform-tools/cmdline-tools to `PATH` via `scripts/android/env.sh`. To use a local SDK instead, launch with `ANDROID_HOME="$HOME/Library/Android/sdk" devbox shell` (or set `ANDROID_SDK_ROOT`). Clear both env vars to return to the Nix SDK. Inspect the active SDK with `echo "$ANDROID_SDK_ROOT"` and `which sdkmanager` inside the shell. Create/boot AVDs via `devbox run start-android*` (uses `scripts/android/setup.sh` + `scripts/android/manager.sh`). Version sources are documented in `wiki/nix.md`. ### Emulator/AVD scripts @@ -28,7 +28,7 @@ By default, Devbox uses the flake-pinned SDK (`path:./nix#android-sdk`). It sets ### Detox defaults - Android Detox defaults to the latest AVD (`medium_phone_API33_arm64_v8a` on arm hosts, x86*64 otherwise). Set `DETOX_AVD=pixel_API21*\*` to run against the minsdk AVD. -- CI Android E2E runs both API 21 (Pixel) and API 33 (Medium Phone) in parallel in the nightly workflow. Override the workflow matrix in `ci-e2e-nightly.yml` if needed. +- CI Android E2E runs both API 21 (Pixel) and API 33 (Medium Phone) in parallel in the full workflow. Override the workflow matrix in `ci-e2e-full.yml` if needed. ### Updating Android min/latest versions @@ -36,12 +36,12 @@ By default, Devbox uses the flake-pinned SDK (`path:./nix#android-sdk`). It sets - Update AVD defaults/names if you change API levels: - `devbox.json` (`start-android-*` scripts) for default AVD names. - `examples/E2E/.detoxrc.js` for the default `DETOX_AVD`. - - CI matrix in `.github/workflows/ci-e2e-nightly.yml` (`android-min`/`android-latest` targets). + - CI matrix in `.github/workflows/ci-e2e-full.yml` (`android-min`/`android-latest` targets). - Gradle uses `buildToolsVersion` from `examples/E2E/android/build.gradle`; Devbox exports `ANDROID_BUILD_TOOLS_VERSION` from `nix/platform-versions.json` (single source of truth) and you can override it if needed. ## iOS -iOS uses the host Xcode toolchain. There is no Nix-provisioned iOS SDK. Run `devbox run setup-ios` to install pods and bootstrap the iOS example/E2E apps. Full Xcode is required for `simctl` (Command Line Tools alone are not enough). Make sure Xcode command line tools are selected (`xcode-select --print-path` or `sudo xcode-select -s /Applications/Xcode.app/Contents/Developer`) and that you have agreed to the license if prompted. +iOS uses the host Xcode toolchain. There is no Nix-provisioned iOS SDK. Run `devbox run setup-ios` to provision simulators and validate Xcode tooling. Full Xcode is required for `simctl` (Command Line Tools alone are not enough). Make sure Xcode command line tools are selected (`xcode-select --print-path` or `sudo xcode-select -s /Applications/Xcode.app/Contents/Developer`) and that you have agreed to the license if prompted. > Important: `devbox shell` injects Nix toolchain variables on macOS, which can break Xcode builds. The init hooks source `scripts/ios/env.sh` to undo that and re-select the system toolchain, and `scripts/ios/test.sh` re-applies it before running E2E. @@ -67,26 +67,13 @@ iOS uses the host Xcode toolchain. There is no Nix-provisioned iOS SDK. Run `dev - Adjust platform defaults in `nix/platform-versions.json` and rebuild Devbox if you change Android SDK versions. - Update Detox default device in `examples/E2E/.detoxrc.js` if the default device changes. -- Update CI matrices in `.github/workflows/ci-e2e-nightly.yml` (ios-min/ios-latest rows) if you want to override the platform defaults in CI. - -### Platform versions source of truth - -`nix/platform-versions.json` is the single source of truth for min/max SDK targets and Android build tools. It feeds: - -- `nix/flake.nix` (Android SDK packages). -- `scripts/platform-versions.sh` (exports env vars via `jq` for devbox scripts). -- CI workflows (`ci-e2e-optional.yml` and `ci-e2e-nightly.yml`) for iOS/Android target selection. - -After updating `nix/platform-versions.json`: - -- Run `refresh` inside a devbox shell to refresh the SDK. -- If you change iOS min/max, re-run the iOS E2E workflow to confirm the runtime/device exists on the runner. +- Update CI matrices in `.github/workflows/ci-e2e-full.yml` (ios-min/ios-latest rows) if you want to override the platform defaults in CI. ### CI devbox shells -CI uses slim Devbox configs under `shells/` to avoid pulling unnecessary SDKs: +The root `devbox.json` is a full local dev environment. CI uses slim Devbox configs under `shells/` to avoid pulling unnecessary SDKs: -- `shells/devbox-fast.json`: build/lint/tests only. +- `shells/devbox-fast.json`: build + lint only. - `shells/devbox-android.json`: Android SDK + JDK/Gradle for Android E2E. - `shells/devbox-ios.json`: CocoaPods + Yarn for iOS E2E (Xcode still required on macOS). diff --git a/wiki/nix.md b/wiki/nix.md new file mode 100644 index 00000000..986eff98 --- /dev/null +++ b/wiki/nix.md @@ -0,0 +1,37 @@ +# Nix + Platform Versions + +This repo uses a Nix flake for the Android SDK, and a JSON file for single-source platform versioning. + +## Files + +- `nix/flake.nix` + + - Defines the pinned Android SDK (emulator, system images, build tools). + - Exposes an `android-sdk` output used by Devbox (`path:./nix#android-sdk`). + +- `nix/platform-versions.json` + + - Single source of truth for Android/iOS min and max targets. + - Contains Android build tools + cmdline tools versions. + +- `scripts/platform-versions.sh` + - Loads `nix/platform-versions.json` via `jq` and exports env vars for scripts and CI. + +## How versions flow + +1. `nix/platform-versions.json` is updated. +2. `nix/flake.nix` reads those values when building the Android SDK output. +3. `scripts/platform-versions.sh` exports the same values for: + - scripts under `scripts/android/` and `scripts/ios/` + - CI workflows that set min/max targets + +## Updating versions + +1. Edit `nix/platform-versions.json`. +2. In a devbox shell, run `refresh` to rebuild the SDK. +3. If iOS min/max changes, re-run the iOS E2E workflow to confirm the runtime/device exists on the runner. + +## CI targets + +- Latest E2E: `.github/workflows/ci-e2e-latest.yml` +- Full (min+max) E2E: `.github/workflows/ci-e2e-full.yml` diff --git a/wiki/scripts.md b/wiki/scripts.md new file mode 100644 index 00000000..e916c22f --- /dev/null +++ b/wiki/scripts.md @@ -0,0 +1,88 @@ +# Scripts Overview + +This repo uses `scripts/` as the entry point for devbox commands and CI helpers. Scripts are organized by platform with a small shared helper layer. + +## Layout + +- `scripts/build.sh`: JS build + lint for fast CI. +- `scripts/platform-versions.sh`: loads `nix/platform-versions.json` and exports platform vars for scripts. +- `scripts/shared/common.sh`: shared helpers (tool checks, platform version loader). +- `scripts/android/`: Android SDK, AVD, and E2E helpers. +- `scripts/ios/`: iOS simulator setup, toolchain fixups, and E2E helpers. +- `scripts/act-ci.sh`: local CI runner helper for `act`. + +## Shared helpers + +- `scripts/shared/common.sh` + - `require_tool`: asserts a tool exists (with an optional custom message). + - `load_platform_versions`: sources `scripts/platform-versions.sh` if present. + +## Android scripts + +- `scripts/android/env.sh` + + - Sets `ANDROID_SDK_ROOT`/`ANDROID_HOME` and PATH for the Nix SDK. + - Loads platform defaults via `scripts/platform-versions.sh`. + - Used by devbox init hooks in `devbox.json` and `shells/devbox-android.json`. + +- `scripts/android/setup.sh` + + - Creates/ensures AVDs for min and max API levels. + - Depends on `sdkmanager`, `avdmanager`, `emulator` in PATH (Devbox shell). + - Uses platform defaults from `scripts/platform-versions.sh`. + +- `scripts/android/manager.sh` + + - Starts/stops/resets AVDs and applies emulator defaults. + - Uses `devbox run setup-android` to ensure AVDs exist. + +- `scripts/android/test.sh` + - Runs setup + yarn build + Android E2E (Detox). + - Used by `devbox run test-android` and CI Android workflows. + +## iOS scripts + +- `scripts/ios/env.sh` + + - Workaround for Devbox macOS toolchain injection. + - Removes Nix toolchain variables and re-selects system clang/Xcode. + - Sourced by devbox init hooks and re-applied in `scripts/ios/test.sh`. + +- `scripts/ios/simctl.sh` + + - Helpers for runtime selection and simulator management. + - Used by `scripts/ios/setup.sh`. + +- `scripts/ios/setup.sh` + + - Ensures Xcode tools are selected and simulators exist. + - Uses `scripts/ios/simctl.sh` to choose runtimes/devices. + +- `scripts/ios/manager.sh` + + - Boots/shuts down simulators via `simctl`. + - Uses platform defaults from `scripts/platform-versions.sh`. + +- `scripts/ios/test.sh` + - Applies `scripts/ios/env.sh`, then runs setup + yarn build + iOS E2E. + - Used by `devbox run test-ios` and CI iOS workflows. + +## Devbox wiring + +Root devbox (`devbox.json`) exposes: + +- `build` -> `scripts/build.sh` +- `test-android` -> `scripts/android/test.sh` +- `test-ios` -> `scripts/ios/test.sh` +- `setup-android` -> `scripts/android/setup.sh` +- `setup-ios` -> `scripts/ios/setup.sh` +- `start-android*` -> `scripts/android/manager.sh` +- `start-ios` -> `scripts/ios/manager.sh` + +Slim CI shells: + +- `shells/devbox-fast.json` -> `scripts/build.sh` +- `shells/devbox-android.json` -> `scripts/android/test.sh` +- `shells/devbox-ios.json` -> `scripts/ios/test.sh` + +See `wiki/devbox.md` for usage and `wiki/nix.md` for platform version sources. From 938fc20703fb300a84428ea6b5243be668ba3f8f Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 28 Jan 2026 14:03:26 -0600 Subject: [PATCH 6/8] fix script path issues --- devbox.json | 23 ++++++++++++----------- scripts/android/manager.sh | 2 +- scripts/android/test.sh | 6 ++++-- scripts/build.sh | 4 ++++ scripts/ios/test.sh | 7 ++++--- scripts/shared/common.sh | 35 +++++++++++++++++++++++++++++++++++ shells/devbox-android.json | 7 ++++--- shells/devbox-fast.json | 5 ++++- shells/devbox-ios.json | 7 ++++--- wiki/devbox.md | 2 ++ wiki/scripts.md | 2 ++ 11 files changed, 76 insertions(+), 24 deletions(-) diff --git a/devbox.json b/devbox.json index 873aca04..37c1d4fb 100644 --- a/devbox.json +++ b/devbox.json @@ -18,6 +18,7 @@ "shell": { "init_hook": [ "echo 'Welcome to analytics-react-native devbox!' > /dev/null", + ". $DEVBOX_PROJECT_ROOT/scripts/shared/common.sh", "if [ \"$(uname -s)\" = \"Darwin\" ]; then . $DEVBOX_PROJECT_ROOT/scripts/ios/env.sh; fi", ". $DEVBOX_PROJECT_ROOT/scripts/android/env.sh", "echo 'Android SDK env configured (details: wiki/devbox.md#devbox-android).'" @@ -30,28 +31,28 @@ "yarn cache clean", "find $DEVBOX_PROJECT_DIR -type d -name node_modules -exec rmdir {} \\;" ], - "build": ["bash $DEVBOX_PROJECT_ROOT/scripts/build.sh"], + "build": ["bash $SCRIPTS_DIR/build.sh"], "format": ["treefmt"], "lint": ["treefmt --fail-on-change"], - "test-android": ["bash $DEVBOX_PROJECT_ROOT/scripts/android/test.sh"], - "test-ios": ["bash $DEVBOX_PROJECT_ROOT/scripts/ios/test.sh"], + "test-android": ["bash $SCRIPTS_DIR/android/test.sh"], + "test-ios": ["bash $SCRIPTS_DIR/ios/test.sh"], "act-ci": [ - "bash $DEVBOX_PROJECT_ROOT/scripts/act-ci.sh --platform ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-24.04" + "bash $SCRIPTS_DIR/act-ci.sh --platform ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-24.04" ], - "setup-android": ["bash $DEVBOX_PROJECT_ROOT/scripts/android/setup.sh"], - "setup-ios": ["bash $DEVBOX_PROJECT_ROOT/scripts/ios/setup.sh"], + "setup-android": ["bash $SCRIPTS_DIR/android/setup.sh"], + "setup-ios": ["bash $SCRIPTS_DIR/ios/setup.sh"], "start-emulator": [ - "bash $DEVBOX_PROJECT_ROOT/scripts/android/manager.sh start" + "bash $SCRIPTS_DIR/android/manager.sh start" ], - "start-ios": ["bash $DEVBOX_PROJECT_ROOT/scripts/ios/manager.sh start"], + "start-ios": ["bash $SCRIPTS_DIR/ios/manager.sh start"], "start-android-minsdk": [ - "bash $DEVBOX_PROJECT_ROOT/scripts/android/manager.sh start" + "bash $SCRIPTS_DIR/android/manager.sh start" ], "start-android-latest": [ - "AVD_FLAVOR=latest bash $DEVBOX_PROJECT_ROOT/scripts/android/manager.sh start" + "AVD_FLAVOR=latest bash $SCRIPTS_DIR/android/manager.sh start" ], "start-android": [ - "bash $DEVBOX_PROJECT_ROOT/scripts/android/manager.sh start" + "bash $SCRIPTS_DIR/android/manager.sh start" ], "release": [ "npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}", diff --git a/scripts/android/manager.sh b/scripts/android/manager.sh index 68ec8177..a1275adb 100755 --- a/scripts/android/manager.sh +++ b/scripts/android/manager.sh @@ -7,7 +7,7 @@ shift || true source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/env.sh" start_android() { - local flavor="${AVD_FLAVOR:-minsdk}" headless="${EMU_HEADLESS:-}" port="${EMU_PORT:-5554}" + local flavor="${AVD_FLAVOR:-latest}" headless="${EMU_HEADLESS:-}" port="${EMU_PORT:-5554}" local avd="${DETOX_AVD:-}" if [[ -z $avd ]]; then diff --git a/scripts/android/test.sh b/scripts/android/test.sh index 13ccec1d..342a187a 100755 --- a/scripts/android/test.sh +++ b/scripts/android/test.sh @@ -2,8 +2,10 @@ set -euo pipefail script_dir="$(cd "$(dirname "$0")" && pwd)" -project_root="$(cd "$script_dir/../.." && pwd)" -bash "$project_root/scripts/android/setup.sh" +# shellcheck disable=SC1090 +. "$script_dir/../shared/common.sh" + +bash "$SCRIPTS_DIR/android/setup.sh" yarn install yarn e2e install yarn build diff --git a/scripts/build.sh b/scripts/build.sh index c16ac31c..d22efb73 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -1,6 +1,10 @@ #!/usr/bin/env bash set -euo pipefail +script_dir="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1090 +. "$script_dir/shared/common.sh" + yarn install --immutable yarn build yarn lint diff --git a/scripts/ios/test.sh b/scripts/ios/test.sh index d87cdaf9..7bf5a418 100755 --- a/scripts/ios/test.sh +++ b/scripts/ios/test.sh @@ -2,10 +2,11 @@ set -euo pipefail script_dir="$(cd "$(dirname "$0")" && pwd)" -project_root="$(cd "$script_dir/../.." && pwd)" +# shellcheck disable=SC1090 +. "$script_dir/../shared/common.sh" if [ "$(uname -s)" = "Darwin" ]; then - . "$project_root/scripts/ios/env.sh" + . "$SCRIPTS_DIR/ios/env.sh" fi echo "iOS test env" @@ -15,7 +16,7 @@ echo " CXX=${CXX:-}" echo " SDKROOT=${SDKROOT:-}" echo " DEVELOPER_DIR=${DEVELOPER_DIR:-}" -bash "$project_root/scripts/ios/setup.sh" +bash "$SCRIPTS_DIR/ios/setup.sh" yarn install yarn e2e install yarn e2e pods diff --git a/scripts/shared/common.sh b/scripts/shared/common.sh index 84dd04c9..a62edbf0 100644 --- a/scripts/shared/common.sh +++ b/scripts/shared/common.sh @@ -9,6 +9,34 @@ require_tool() { fi } +ensure_project_root() { + if [ -n "${PROJECT_ROOT:-}" ]; then + return 0 + fi + + base_dir="${1:-}" + if [ -z "$base_dir" ]; then + base_dir="$PWD" + fi + + git_root="" + if command -v git >/dev/null 2>&1; then + git_root="$(git -C "$base_dir" rev-parse --show-toplevel 2>/dev/null || true)" + fi + + if [ -n "$git_root" ]; then + PROJECT_ROOT="$git_root" + elif [ -f "$base_dir/../shared/common.sh" ] && [ -f "$base_dir/../build.sh" ]; then + PROJECT_ROOT="$(cd "$base_dir/.." && pwd)" + elif [ -f "$base_dir/shared/common.sh" ] && [ -f "$base_dir/build.sh" ]; then + PROJECT_ROOT="$(cd "$base_dir" && pwd)" + fi + + if [ -n "${PROJECT_ROOT:-}" ]; then + export PROJECT_ROOT + fi +} + load_platform_versions() { base_dir="$1" platform_versions="${base_dir%/}/../platform-versions.sh" @@ -17,3 +45,10 @@ load_platform_versions() { . "$platform_versions" fi } + +ensure_project_root "${SCRIPT_DIR:-${script_dir:-${PWD}}}" + +if [ -z "${SCRIPTS_DIR:-}" ] && [ -n "${PROJECT_ROOT:-}" ]; then + SCRIPTS_DIR="$PROJECT_ROOT/scripts" + export SCRIPTS_DIR +fi diff --git a/shells/devbox-android.json b/shells/devbox-android.json index 60e7ba8e..a1be49c5 100644 --- a/shells/devbox-android.json +++ b/shells/devbox-android.json @@ -9,12 +9,13 @@ }, "shell": { "init_hook": [ + ". $DEVBOX_PROJECT_ROOT/../scripts/shared/common.sh", "echo 'Android SDK env configured (details: wiki/devbox.md#devbox-android).'", - ". $DEVBOX_PROJECT_ROOT/scripts/android/env.sh" + ". $SCRIPTS_DIR/android/env.sh" ], "scripts": { - "setup-android": ["bash $DEVBOX_PROJECT_ROOT/scripts/android/setup.sh"], - "test-android": ["bash $DEVBOX_PROJECT_ROOT/scripts/android/test.sh"] + "setup-android": ["bash $SCRIPTS_DIR/android/setup.sh"], + "test-android": ["bash $SCRIPTS_DIR/android/test.sh"] } } } diff --git a/shells/devbox-fast.json b/shells/devbox-fast.json index 4fd6373d..d955b564 100644 --- a/shells/devbox-fast.json +++ b/shells/devbox-fast.json @@ -8,8 +8,11 @@ "shfmt": "latest" }, "shell": { + "init_hook": [ + ". $DEVBOX_PROJECT_ROOT/../scripts/shared/common.sh" + ], "scripts": { - "build": ["bash $DEVBOX_PROJECT_ROOT/scripts/build.sh"], + "build": ["bash $SCRIPTS_DIR/build.sh"], "format": ["treefmt"], "lint": ["treefmt --fail-on-change"] } diff --git a/shells/devbox-ios.json b/shells/devbox-ios.json index 6649cf38..ba43b7d3 100644 --- a/shells/devbox-ios.json +++ b/shells/devbox-ios.json @@ -10,11 +10,12 @@ }, "shell": { "init_hook": [ - "if [ \"$(uname -s)\" = \"Darwin\" ]; then . $DEVBOX_PROJECT_ROOT/scripts/ios/env.sh; fi" + ". $DEVBOX_PROJECT_ROOT/../scripts/shared/common.sh", + "if [ \"$(uname -s)\" = \"Darwin\" ]; then . $SCRIPTS_DIR/ios/env.sh; fi" ], "scripts": { - "setup-ios": ["bash $DEVBOX_PROJECT_ROOT/scripts/ios/setup.sh"], - "test-ios": ["bash $DEVBOX_PROJECT_ROOT/scripts/ios/test.sh"] + "setup-ios": ["bash $SCRIPTS_DIR/ios/setup.sh"], + "test-ios": ["bash $SCRIPTS_DIR/ios/test.sh"] } } } diff --git a/wiki/devbox.md b/wiki/devbox.md index 6011db96..76e710ed 100644 --- a/wiki/devbox.md +++ b/wiki/devbox.md @@ -84,3 +84,5 @@ devbox run --config=shells/devbox-fast.json build devbox run --config=shells/devbox-android.json test-android devbox run --config=shells/devbox-ios.json test-ios ``` + +Note: when you use `devbox run --config=shells/...`, Devbox treats `shells/` as the config root. The init hooks set `SCRIPTS_DIR` to point back at the repo-level `scripts/` folder. diff --git a/wiki/scripts.md b/wiki/scripts.md index e916c22f..483d447c 100644 --- a/wiki/scripts.md +++ b/wiki/scripts.md @@ -16,6 +16,8 @@ This repo uses `scripts/` as the entry point for devbox commands and CI helpers. - `scripts/shared/common.sh` - `require_tool`: asserts a tool exists (with an optional custom message). - `load_platform_versions`: sources `scripts/platform-versions.sh` if present. + - `PROJECT_ROOT`: auto-detected git root when unset. + - `SCRIPTS_DIR`: defaults to `$PROJECT_ROOT/scripts` when unset. ## Android scripts From 04dcb395554a0fcc033a311733bbc065522f6603 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 28 Jan 2026 14:07:27 -0600 Subject: [PATCH 7/8] devbox run update-shells task --- devbox.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/devbox.json b/devbox.json index 37c1d4fb..1574351a 100644 --- a/devbox.json +++ b/devbox.json @@ -65,6 +65,12 @@ "yarn e2e install --no-immutable", "yarn example install --no-immutable" ], + "update-shells": [ + "devbox update", + "devbox update --config=shells/devbox-fast.json", + "devbox update --config=shells/devbox-android.json", + "devbox update --config=shells/devbox-ios.json" + ], "reset-android": [ "rm -rf ~/.android/avd", "rm -f ~/.android/adbkey*", From f4853d99430984af5fef976c725aa323b9a7aff5 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 28 Jan 2026 14:12:15 -0600 Subject: [PATCH 8/8] run format --- devbox.json | 12 +++--------- shells/devbox-fast.json | 4 +--- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/devbox.json b/devbox.json index 1574351a..30bc3037 100644 --- a/devbox.json +++ b/devbox.json @@ -41,19 +41,13 @@ ], "setup-android": ["bash $SCRIPTS_DIR/android/setup.sh"], "setup-ios": ["bash $SCRIPTS_DIR/ios/setup.sh"], - "start-emulator": [ - "bash $SCRIPTS_DIR/android/manager.sh start" - ], + "start-emulator": ["bash $SCRIPTS_DIR/android/manager.sh start"], "start-ios": ["bash $SCRIPTS_DIR/ios/manager.sh start"], - "start-android-minsdk": [ - "bash $SCRIPTS_DIR/android/manager.sh start" - ], + "start-android-minsdk": ["bash $SCRIPTS_DIR/android/manager.sh start"], "start-android-latest": [ "AVD_FLAVOR=latest bash $SCRIPTS_DIR/android/manager.sh start" ], - "start-android": [ - "bash $SCRIPTS_DIR/android/manager.sh start" - ], + "start-android": ["bash $SCRIPTS_DIR/android/manager.sh start"], "release": [ "npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}", "yarn install --immutable", diff --git a/shells/devbox-fast.json b/shells/devbox-fast.json index d955b564..0870e0cd 100644 --- a/shells/devbox-fast.json +++ b/shells/devbox-fast.json @@ -8,9 +8,7 @@ "shfmt": "latest" }, "shell": { - "init_hook": [ - ". $DEVBOX_PROJECT_ROOT/../scripts/shared/common.sh" - ], + "init_hook": [". $DEVBOX_PROJECT_ROOT/../scripts/shared/common.sh"], "scripts": { "build": ["bash $SCRIPTS_DIR/build.sh"], "format": ["treefmt"],