Skip to content

feat: launch the Image Marker toolchain #348

feat: launch the Image Marker toolchain

feat: launch the Image Marker toolchain #348

Workflow file for this run

name: CI
on:
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true
jobs:
changes:
runs-on: ubuntu-latest
name: Detect changes
outputs:
android: ${{ steps.filter.outputs.android }}
ios: ${{ steps.filter.outputs.ios }}
js: ${{ steps.filter.outputs.js }}
web: ${{ steps.filter.outputs.web }}
docs: ${{ steps.filter.outputs.docs }}
c2pa: ${{ steps.filter.outputs.c2pa }}
trace: ${{ steps.filter.outputs.trace }}
native: ${{ steps.filter.outputs.native }}
security: ${{ steps.filter.outputs.security }}
editor: ${{ steps.filter.outputs.editor }}
recipe: ${{ steps.filter.outputs.recipe }}
node: ${{ steps.filter.outputs.node }}
cli: ${{ steps.filter.outputs.cli }}
steps:
- name: Checkout the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- name: Verify immutable GitHub Actions references
run: node scripts/verify-github-actions.mjs
- name: Classify changed files
id: filter
env:
EVENT_NAME: ${{ github.event_name }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
python3 <<'PY'
import fnmatch
import os
import subprocess
def changed_files():
if os.environ["EVENT_NAME"] == "workflow_dispatch":
return None
base = os.environ.get("BASE_SHA")
head = os.environ.get("HEAD_SHA") or "HEAD"
if not base:
return None
try:
output = subprocess.check_output(
["git", "diff", "--name-only", base, head],
text=True,
)
except subprocess.CalledProcessError:
output = subprocess.check_output(
["git", "diff", "--name-only", base, "HEAD"],
text=True,
)
return [line for line in output.splitlines() if line]
files = changed_files()
all_jobs = files is None
def matches(path, patterns):
return any(fnmatch.fnmatch(path, pattern) for pattern in patterns)
def any_changed(include, exclude=()):
if all_jobs:
return True
return any(
matches(path, include) and not matches(path, exclude)
for path in files
)
android = any_changed(
[
"android/**",
"cpp/**",
"src/**",
"packages/editor/**",
"packages/recipe/**",
"package.json",
"package-lock.json",
"app.plugin.js",
".github/workflows/ci.yml",
".github/actions/**",
"example/android/**",
"example/app.json",
"example/babel.config.js",
"example/e2e/**",
"example/index.js",
"example/metro.config.js",
"example/package.json",
"example/package-lock.json",
"example/tsconfig.json",
"example/src/**",
"scripts/run-android-instrumentation.sh",
],
["example/ios/**"],
)
ios = any_changed(
[
"ios/**",
"cpp/**",
"src/**",
"packages/editor/**",
"packages/recipe/**",
"package.json",
"package-lock.json",
"app.plugin.js",
"react-native-image-marker.podspec",
".github/workflows/ci.yml",
".github/actions/**",
"example/app.json",
"example/babel.config.js",
"example/e2e/**",
"example/index.js",
"example/ios/**",
"example/metro.config.js",
"example/package.json",
"example/package-lock.json",
"example/tsconfig.json",
"example/src/**",
],
["example/android/**"],
)
js = any_changed(
[
"src/**",
"cpp/**",
"packages/editor/**",
"packages/recipe/**",
"package.json",
"package-lock.json",
"app.plugin.js",
"tsconfig*.json",
"lefthook.yml",
".eslintignore",
".eslintrc.js",
".github/workflows/ci.yml",
".github/actions/**",
"example/package.json",
"example/package-lock.json",
"example/tsconfig.json",
"example/src/**",
"expo-example/**",
]
)
web = any_changed(
[
"src/**",
"packages/recipe/**",
"package.json",
"package-lock.json",
"expo-example/**",
".github/workflows/ci.yml",
".github/actions/**",
]
)
docs = any_changed(
[
"src/**",
"docs/**",
"packages/editor/**",
"packages/recipe/**",
"packages/node/**",
"packages/cli/**",
"website/**",
"README.MD",
"package.json",
".github/workflows/ci.yml",
".github/workflows/docs-site.yml",
".github/actions/**",
]
)
c2pa = any_changed(
[
"examples/c2pa-service/**",
".github/workflows/ci.yml",
]
)
trace = any_changed(
[
"examples/trace-service/**",
"src/batch.ts",
"src/index.ts",
"src/invisible-watermark.ts",
"src/trace-runtime.ts",
"packages/recipe/**",
"trace-runtime.js",
"trace-runtime.d.ts",
"package.json",
"package-lock.json",
".github/workflows/ci.yml",
".github/actions/**",
]
)
security = any_changed(
[
"package.json",
"package-lock.json",
"packages/recipe/package.json",
"packages/node/package.json",
"packages/cli/package.json",
"website/package.json",
"website/package-lock.json",
"example/package.json",
"example/package-lock.json",
"expo-example/package.json",
"expo-example/package-lock.json",
"examples/c2pa-service/package.json",
"examples/c2pa-service/package-lock.json",
"examples/trace-service/package.json",
"examples/trace-service/package-lock.json",
"react-native-image-marker.podspec",
"android/build.gradle",
"android/gradle.properties",
"android/gradle/wrapper/**",
"example/android/build.gradle",
"example/android/app/build.gradle",
"example/android/gradle.properties",
"example/android/gradle/wrapper/**",
"ios/Podfile",
"example/ios/Podfile",
]
)
editor = any_changed(
[
"packages/editor/**",
"src/**",
"scripts/fix-esm-imports.mjs",
"scripts/verify-package-consumer.mjs",
"package.json",
"package-lock.json",
"tsconfig*.json",
".github/workflows/ci.yml",
".github/actions/**",
]
)
recipe = any_changed(
[
"packages/recipe/**",
"scripts/fix-esm-imports.mjs",
"scripts/verify-package-consumer.mjs",
"scripts/verify-size-budgets.mjs",
"scripts/size-budgets.json",
"package.json",
"package-lock.json",
"tsconfig*.json",
".github/workflows/ci.yml",
".github/actions/**",
]
)
node = any_changed(
[
"packages/node/**",
"packages/recipe/**",
"scripts/fix-esm-imports.mjs",
"scripts/release-target.mjs",
"scripts/verify-package-consumer.mjs",
"scripts/verify-size-budgets.mjs",
"scripts/size-budgets.json",
"package.json",
"package-lock.json",
"tsconfig*.json",
".github/workflows/ci.yml",
".github/workflows/npm-publish.yml",
".github/actions/**",
]
)
cli = any_changed(
[
"packages/cli/**",
"packages/node/**",
"packages/recipe/**",
"scripts/fix-esm-imports.mjs",
"scripts/release-target.mjs",
"scripts/verify-package-consumer.mjs",
"scripts/verify-registry-consumer.mjs",
"scripts/verify-size-budgets.mjs",
"scripts/size-budgets.json",
"package.json",
"package-lock.json",
"tsconfig*.json",
".github/workflows/ci.yml",
".github/workflows/npm-publish.yml",
".github/actions/**",
]
)
results = {
"android": android,
"ios": ios,
"js": js,
"web": web,
"docs": docs,
"c2pa": c2pa,
"trace": trace,
"native": android or ios,
"security": security,
"editor": editor,
"recipe": recipe,
"node": node,
"cli": cli,
}
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
for key, value in results.items():
print(f"{key}={'true' if value else 'false'}", file=output)
PY
security:
runs-on: ubuntu-latest
name: Security & Dependency Review
needs: changes
if: needs.changes.outputs.security == 'true'
steps:
- name: Checkout the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Dependency Review
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5
if: github.event_name == 'pull_request'
with:
fail-on-severity: moderate
# ESLint 8 still requires minimatch 3's CommonJS brace-expansion API,
# and the RN 0.73 CLI still pulls ip and fast-xml-parser without
# compatible patched releases. These advisories are accepted only
# for the dev graph; the packed Core and Editor production graphs
# and platform-neutral Recipe, Node, and CLI production graphs are
# audited below.
allow-ghsas: GHSA-mh99-v99m-4gvg,GHSA-2p57-rm9w-gvfp,GHSA-gh4j-gqv2-49f6
- name: Setup Node.js
uses: ./.github/actions/setup
with:
node-version: '22.22.0'
- name: Install dependencies
run: |
npm ci
npm ci --prefix website
- name: Enforce production dependency audit
run: |
npm run verify:consumer
npm audit --prefix website --omit=dev --audit-level=moderate
- name: Report development-tooling audit
run: |
npm audit --audit-level=moderate
npm audit --prefix website --audit-level=moderate
continue-on-error: true
js-check:
runs-on: ubuntu-latest
name: JS Typecheck and Test
needs: changes
if: needs.changes.outputs.js == 'true'
steps:
- name: Checkout the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Setup node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '22.22.0'
- name: Install root and Expo example dependencies
run: |
npm ci
npm --prefix expo-example install --ignore-scripts --no-audit --no-fund
- name: Verify Expo clean-install bundle
run: |
test ! -d example/node_modules
npm run build:recipe
npm --prefix expo-example run bundle:smoke
- name: Install React Native example dependencies
run: npm --prefix example install
- name: Typecheck
run: |
npm run typecheck
npm run typecheck:example
npm run typecheck:expo-example
- name: Lint
run: npm run lint
- name: Verify release notes generation
run: npm run check:release-notes
- name: Test
run: |
npm test -- --coverage --runInBand
npm run test:cpp
npm run test:ci-guards
npm run test:release-routing
- name: Build package
run: |
npm run prepack
npm run verify:size:core
editor-check:
runs-on: ubuntu-latest
name: Editor Package and Consumer
needs: changes
if: needs.changes.outputs.editor == 'true'
steps:
- name: Checkout the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Setup Node.js
uses: ./.github/actions/setup
with:
node-version: '22.22.0'
cache-dependency-path: package-lock.json
- name: Install dependencies
run: npm ci
- name: Test and build Editor independently
run: |
npm run prepack
npm run test:editor
npm run build:recipe
npm run build:editor
npm run check:editor-api
npm run verify:size:editor
- name: Verify packed toolchain consumers
run: npm run verify:consumer
recipe-check:
runs-on: ubuntu-latest
name: Recipe Package and Consumer
needs: changes
if: needs.changes.outputs.recipe == 'true'
steps:
- name: Checkout the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Setup Node.js
uses: ./.github/actions/setup
- name: Install dependencies
run: npm ci
- name: Test, build, and measure Recipe
run: |
npm run test:recipe
npm run build:recipe
npm run verify:size:recipe
- name: Verify packed package consumers
run: npm run verify:consumer
node-check:
runs-on: ubuntu-latest
name: Node Renderer Package and Consumer
needs: changes
if: needs.changes.outputs.node == 'true'
steps:
- name: Checkout the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Setup Node.js
uses: ./.github/actions/setup
with:
node-version: '22.22.0'
- name: Install dependencies
run: npm ci
- name: Test, build, and measure Node renderer
run: |
npx eslint "packages/node/**/*.{js,ts,tsx}"
npm run build:recipe
npm run test:node
npm run build:node
npm run verify:size:node
- name: Verify packed package consumers
run: npm run verify:consumer
cli-check:
runs-on: ubuntu-latest
name: CLI Package and Consumer
needs: changes
if: needs.changes.outputs.cli == 'true'
steps:
- name: Checkout the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Setup Node.js
uses: ./.github/actions/setup
with:
node-version: '22.22.0'
- name: Install dependencies
run: npm ci
- name: Test, build, and measure CLI
run: |
npx eslint "packages/cli/**/*.{js,ts,tsx}"
npm run build:recipe
npm run build:node
npm run test:cli
npm run build:cli
npm run verify:size:cli
- name: Verify packed package consumers
run: npm run verify:consumer
c2pa-service:
runs-on: ubuntu-latest
name: C2PA Service Example
needs: changes
if: needs.changes.outputs.c2pa == 'true'
steps:
- name: Checkout the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Setup Node.js
uses: ./.github/actions/setup
with:
node-version: '22.22.0'
cache-dependency-path: examples/c2pa-service/package-lock.json
- name: Install dependencies
run: npm ci --prefix examples/c2pa-service
- name: Typecheck and test
run: |
npm --prefix examples/c2pa-service run typecheck
npm --prefix examples/c2pa-service test
- name: Audit production dependencies
run: npm audit --prefix examples/c2pa-service --omit=dev --audit-level=moderate
- name: Load official C2PA Node module
working-directory: examples/c2pa-service
run: |
node -e "import('@contentauth/c2pa-node').then(({ Builder, Reader, LocalSigner }) => { if (!Builder || !Reader || !LocalSigner) process.exit(1) })"
trace-service:
runs-on: ubuntu-latest
name: Trace Service (Node ${{ matrix.node }})
needs: changes
if: needs.changes.outputs.trace == 'true'
strategy:
fail-fast: false
matrix:
node: ['22.22.0', '24']
steps:
- name: Checkout the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Setup Node.js
uses: ./.github/actions/setup
with:
node-version: ${{ matrix.node }}
cache-dependency-path: |
package-lock.json
examples/trace-service/package-lock.json
- name: Install and build the core package
run: |
npm ci
npm run prepack
- name: Install Trace Service dependencies
run: npm ci --prefix examples/trace-service
- name: Typecheck and test
run: |
npm --prefix examples/trace-service run typecheck
npm --prefix examples/trace-service test
- name: Load sharp and audit production dependencies
working-directory: examples/trace-service
run: |
node -e "import('sharp').then(({ default: sharp }) => { if (!sharp.versions.sharp) process.exit(1) })"
npm audit --omit=dev --audit-level=moderate
- name: Build Trace Service container
if: matrix.node == '22.22.0'
run: docker build -f examples/trace-service/Dockerfile -t image-marker-trace:test .
- name: Verify on-demand package boundary
if: matrix.node == '22.22.0'
run: |
node - <<'NODE'
const Module = require('module');
const loaded = [];
const original = Module._load;
Module._load = function (request, parent, isMain) {
loaded.push(request);
return original.apply(this, arguments);
};
require('./lib/commonjs/index');
if (loaded.some((request) => request.includes('trace-runtime'))) {
throw new Error('The root entry loaded trace-runtime eagerly.');
}
const runtime = require('./trace-runtime.js');
if (typeof runtime.createInvisibleWatermarkRuntime !== 'function') {
throw new Error('The trace-runtime subpath is missing.');
}
NODE
npm pack --ignore-scripts --dry-run --json > package-files.json
node - <<'NODE'
const names = require('./package-files.json')[0].files.map((file) => file.path);
for (const required of [
'trace-runtime.js',
'trace-runtime.d.ts',
'lib/commonjs/trace-runtime.js',
'lib/typescript/module/trace-runtime.d.ts',
'lib/worker/invisible-watermark.js',
]) {
if (!names.includes(required)) {
throw new Error(`${required} is missing from the package.`);
}
}
if (names.some((name) => name.includes('examples/trace-service') || name.toLowerCase().includes('sharp'))) {
throw new Error('Trace Service dependencies leaked into the package.');
}
NODE
docs-site:
runs-on: ubuntu-latest
name: Documentation Site
needs: changes
if: needs.changes.outputs.docs == 'true'
steps:
- name: Checkout the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Setup Node.js
uses: ./.github/actions/setup
with:
node-version: '22.22.0'
cache-dependency-path: |
package-lock.json
website/package-lock.json
- name: Install dependencies
run: |
npm ci
npm ci --prefix website
- name: Build and verify documentation
run: |
npm run build:recipe
npm run docs
npm run verify:size:website
- name: Install Chromium
run: npm exec --prefix website -- playwright install --with-deps chromium firefox webkit
- name: Smoke test playground and online tools
run: npm --prefix website run smoke
web-example:
runs-on: ubuntu-latest
name: React Native Web Example
needs: changes
if: needs.changes.outputs.web == 'true'
steps:
- name: Checkout the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Setup Node.js
uses: ./.github/actions/setup
with:
node-version: '22.22.0'
cache-dependency-path: |
package-lock.json
expo-example/package-lock.json
- name: Install dependencies
run: |
npm ci
npm --prefix example install --ignore-scripts --no-audit --no-fund
npm ci --prefix expo-example --ignore-scripts --no-audit --no-fund
npm run build:recipe
- name: Test Web SDK
run: npm run test:web -- --coverage
- name: Upload Web coverage report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: web-coverage-${{ github.sha }}
path: coverage
if-no-files-found: error
retention-days: 14
- name: Typecheck web example
run: npm --prefix expo-example run typecheck
- name: Export web example
run: npm --prefix expo-example run bundle:web
- name: Verify web platform resolution
run: npm --prefix expo-example run verify:web-bundle
- name: Install Playwright browsers
run: npm exec --prefix expo-example -- playwright install --with-deps chromium firefox webkit
- name: Smoke test web example
run: npm --prefix expo-example run smoke:web
expo-current:
runs-on: ubuntu-latest
name: Expo 57 / RN 0.86 (New Architecture)
needs: changes
if: needs.changes.outputs.js == 'true'
timeout-minutes: 45
steps:
- name: Checkout the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Setup Android toolchain
uses: ./.github/actions/setup-android
with:
cache-dependency-path: expo-example/package-lock.json
- name: Install dependencies
run: npm ci --prefix expo-example --ignore-scripts --no-audit --no-fund
- name: Verify Expo versions
run: (cd expo-example && npx --yes expo-doctor@1.20.1)
- name: Build workspace dependencies
run: |
npm ci
npm run build:recipe
- name: Typecheck and bundle Expo example
run: |
npm --prefix expo-example run typecheck
npm --prefix expo-example run bundle:smoke
- name: Generate Expo development-build project
working-directory: expo-example
run: npx expo prebuild --platform android --clean --no-install
- name: Compile New Architecture development build
env:
NODE_ENV: production
run: |
cd expo-example/android
./gradlew :app:assembleDebug -PreactNativeArchitectures=arm64-v8a --stacktrace --no-daemon
react-native-current:
runs-on: ubuntu-latest
name: RN 0.86 (New Architecture)
needs: changes
if: needs.changes.outputs.native == 'true'
timeout-minutes: 45
steps:
- name: Checkout the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Setup Android toolchain
uses: ./.github/actions/setup-android
- name: Install and build current React Native fixture
run: |
npm ci
bash scripts/test-react-native-current.sh
android-build:
runs-on: macos-latest
name: RN 0.73 Android (Legacy + New Architecture)
needs: changes
if: needs.changes.outputs.android == 'true'
steps:
- name: Checkout the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Setup Android toolchain
uses: ./.github/actions/setup-android
with:
java-distribution: zulu
cache-dependency-path: |
package-lock.json
example/package-lock.json
- name: Install npm dependencies
run: |
npm ci
npm --prefix example install
npm run build:recipe
- name: Run unit tests
run: |
cd example/android
./gradlew test --stacktrace
- name: Build APK
run: |
npm run prepack
cd example/android
./gradlew assembleRelease
mv app/build/outputs/apk/release/app-release.apk app-release-${{ github.sha }}.apk
- name: Upload APK
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: app-release-${{ github.sha }}.apk
path: ${{ github.workspace }}/example/android/app-release-${{ github.sha }}.apk
- name: Build APK New Architecture
run: |
cd example/android
./gradlew clean
./gradlew -PnewArchEnabled=true assembleDebug --stacktrace
- name: Compile Android tests New Architecture
run: |
cd example/android
./gradlew -PnewArchEnabled=true :app:compileDebugAndroidTestKotlin --stacktrace
android-api-level-test:
runs-on: ubuntu-latest
needs: [changes, android-build]
if: needs.changes.outputs.android == 'true'
name: Android Test
strategy:
fail-fast: false
matrix:
api-level: [24, 31]
target: [default]
steps:
- name: Checkout the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Setup Android toolchain
uses: ./.github/actions/setup-android
with:
java-distribution: zulu
cache-dependency-path: |
package-lock.json
example/package-lock.json
- name: Install npm dependencies
run: |
npm ci
npm --prefix example install
npm run build:recipe
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Instrumentation Tests
uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: x86_64
profile: pixel_5
script: bash scripts/run-android-instrumentation.sh
- name: Upload Reports
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: Test-Reports-api-${{ matrix.api-level }}
path: ${{ github.workspace }}/example/android/app/build/reports
ios-build-test:
runs-on: macos-15
env:
NO_FLIPPER: '1'
needs: changes
if: needs.changes.outputs.ios == 'true'
name: RN 0.73 iOS (Legacy Architecture)
strategy:
matrix:
cocoapods: ['1.15.2']
steps:
- name: Checkout the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Setup iOS toolchain
uses: ./.github/actions/setup-ios
with:
cocoapods-version: ${{ matrix.cocoapods }}
cache-dependency-path: |
package-lock.json
example/package-lock.json
- name: Cache Pods
id: cache-pods
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: example/ios/Pods
key: ${{ runner.os }}-pods-${{ matrix.cocoapods }}-${{ hashFiles('example/ios/Podfile', 'react-native-image-marker.podspec', 'package.json', 'ios/**/*', 'src/NativeImageMarker.ts') }}
restore-keys: |
${{ runner.os }}-pods-${{ matrix.cocoapods }}-
${{ runner.os }}-pods-
- name: Install npm dependencies
run: |
npm ci
npm --prefix example install
npm run build:recipe
- name: Compile shared C++ Swift bridge
run: npm run test:cpp:ios-bridge
- name: Install Pods
run: |
cd example/ios
if [ "${{ steps.cache-pods.outputs.cache-hit }}" != "true" ]; then
pod cache clean --all
fi
pod install
- name: Select iOS simulator
id: ios-simulator
run: |
simulator_id="$(
xcrun simctl list devices available --json | ruby -rjson -e '
devices = JSON.parse(STDIN.read).fetch("devices").values.flatten
simulator = devices.find { |device| device["name"] == "iPhone 16" } ||
devices.find { |device| device["name"].include?("iPhone") }
abort("No available iPhone simulator found") unless simulator
puts simulator.fetch("udid")
'
)"
echo "id=$simulator_id" >> "$GITHUB_OUTPUT"
- name: Build
run: |
set -o pipefail
cd example/ios
xcodebuild -workspace ImageMarkerExample.xcworkspace -scheme ImageMarkerExample -configuration Release -sdk iphonesimulator -destination 'id=${{ steps.ios-simulator.outputs.id }}' 2>&1 | tee xcodebuild-build.log | xcpretty || {
status=$?
tail -n 200 xcodebuild-build.log
exit $status
}
- name: Test
run: |
set -o pipefail
cd example/ios
FORCE_BUNDLING=1 xcodebuild \
-workspace ImageMarkerExample.xcworkspace \
-scheme ImageMarkerExample \
-sdk iphonesimulator \
-destination 'id=${{ steps.ios-simulator.outputs.id }}' \
-parallel-testing-enabled NO \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testApp \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testEditorUndoPreviewAndOriginalExportRunThroughCore \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testCore21SharedRecipeTextConformance \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testCore21ImageInfoReadsEncodedOrientation \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testTextAnchorOffsetFeatureProducesPreview \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testMixedWatermarkFeatureProducesPreview \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testTiledTextFeatureProducesPreview \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testInvisibleWatermarkFeatureEmbedsAndDetects \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testInvisibleWatermarkMatchesCrossPlatformVectors \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testInvisibleWatermarkEmbedsAndAuthenticatesPixels \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testInvisibleWatermarkRecoversLightImageResizing \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testSharpScaledWatermarkFeatureProducesPreview \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testRotationOutputPolicyFeatureProducesPreview \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testWatermarkOrientationMatchesUprightPixelReference \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testNormalizesUIImageOrientationBeforeCGImageRendering \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testKeepsAlreadyUprightUIImageInstance \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testTileLayoutResolvesOffsetsStaggerAndCopyLimit \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testTiledImageRepeatsAcrossCanvas \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testPositionDefaultsAndEdgeInsetOverrides \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testRotatesFortyFiveDegreesWithExpandedTransparentCanvas \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testRotatesFortyFiveDegreesWithCroppedOriginalCanvas \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testJPEGUsesExplicitMatteWhilePNGPreservesTransparency \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testTrimsTransparentWatermarkPaddingBeforeAnchoredPositioning \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testOptionsPreferFilenameAndProvideRenderingDefaults \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testMaxSizeDefaultsValidatesAndBuildsBoundedLoadRequests \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testSharedCppCoreFitsPixelBounds \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testLargeImageDownsamplePerformanceAndMemory \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testImageIODownsamplesDataAndBase64BeforeRendering \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testRejectsInvalidQualityAlphaAndUnsafeFilenames \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testParsesShadowHexColorAndRejectsInvalidValues \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testBackgroundScaleChangesCanvasWithoutImplicitlyScalingLayers \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testBackgroundScaleDoesNotImplicitlyScaleText \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testSequentialAsyncMapBoundsConcurrencyAndPreservesOrder \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testAsyncLimiterBoundsConcurrencyAndSkipsCancelledWaiter \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testCancellableContinuationHandlesCancellationCompletionRace \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testRenderAndReleaseSourcesDropsInputsBeforeEncodingPhase \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testAnchorsRotatedWatermarkByVisibleBounds \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testRendersImageWatermarkUsingAnchoredOffsets \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testRotatesRenderedBackgroundWhenRequested \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testRendersScaledImageWatermarkWithoutInterpolatedEdges \
-only-testing:ImageMarkerExampleUITests/ImageMarkerExampleUITests/testPreservesAsymmetricWatermarkOrientationAcrossScaleAndRotation \
test 2>&1 | tee xcodebuild-test.log | xcpretty || {
status=$?
tail -n 200 xcodebuild-test.log
exit $status
}
- name: Install Pods New Architecture
run: |
cd example/ios
RCT_NEW_ARCH_ENABLED=1 NO_FLIPPER=1 pod install
- name: Build New Architecture
run: |
set -o pipefail
cd example/ios
RCT_NEW_ARCH_ENABLED=1 xcodebuild -workspace ImageMarkerExample.xcworkspace -scheme ImageMarkerExample -configuration Debug -sdk iphonesimulator -destination 'id=${{ steps.ios-simulator.outputs.id }}' 2>&1 | tee xcodebuild-new-arch-build.log | xcpretty || {
status=$?
tail -n 200 xcodebuild-new-arch-build.log
exit $status
}
ci-complete:
name: Complete CI
needs:
[
changes,
security,
js-check,
editor-check,
recipe-check,
c2pa-service,
trace-service,
docs-site,
web-example,
expo-current,
react-native-current,
android-build,
android-api-level-test,
ios-build-test,
]
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- name: Check all job status
if: >-
${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
run: exit 1