Compare commits

...
Author SHA1 Message Date
Hassan Khan 8c15065eb9 ci: test on Node 22 and 24 (#338)
Node 20 reached end of life in April 2026.
2026-10-19 17:47:57 +01:00
Hassan Khan d299fbb361 chore: move lint and formatting to oxlint and oxfmt (#337)
* chore: add `oxlint` and `oxfmt`

* chore: switch lint to oxlint

Root oxlint.config.mjs extends oxlint-config-universe/node. Each package
re-exports it and runs oxlint --config oxlint.config.mjs ., since oxlint
does not discover a root .mjs config from a package directory. CI lints
with --deny-warnings and checks formatting with oxfmt.

Removes eslintrc.base.js, every .eslintrc.js, and the eslint,
eslint-config-universe and prettier devDependencies. Bumps oxlint and
oxfmt and aligns .oxfmtrc.json with expo/expo, ignoring the generated
SettingsPlist.ts that json2ts rewrites in prettier's default style.

Also runs pnpm dedupe. The package.json files in this commit carry the
oxfmt key reorder as well; the rest of the reformat is its own commit.

* chore: format with oxfmt

Mechanical reformat of the repo with oxfmt. No code changes.
2026-10-19 17:38:33 +01:00
Hassan Khan 3765a79aac chore: replace expo-module-scripts with direct tooling (#335)
expo-module-scripts stops at 56.x on npm and its successor is private
to expo/expo. The packages only used it for tsc, jest and eslint.

Shared tsconfig.base.json, jest.config.base.js and eslintrc.base.js now
live at the root and packages reference them. Package scripts call
tsc, eslint, jest and rm -rf build directly. Tests run through
@swc/jest, as in expo/expo. Lint stays on ESLint 8 with universe for
now; the oxlint move is a separate change.

Also drops the expo-module-scripts catalog entry and the
babel-preset-expo package extension, adds the missing jest.config.js to
react-native-adjust, and makes the plugin generator copy jest and
eslint configs from the template.
2026-10-19 17:11:16 +01:00
Hassan Khan 765211130a chore: move to pnpm 12 (#334)
Pin pnpm 12.2.1 through engines and devEngines.packageManager, matching
expo/expo. Enable the global virtual store and set the esbuild and
msgpackr-extract builds to true, replacing the placeholders that made
pnpm install fail on pnpm 12.

CI: switch to pnpm/setup, bump checkout and setup-node to v7, use the
matrix Node version instead of a hardcoded 20, and run tests on every
pull request.
2026-10-19 16:35:18 +01:00
Hassan Khan a7cc2b2fed chore(apple-settings): bump json-schema-to-typescript to v16 (#333) 2026-10-19 14:19:47 +01:00
Christian FalchandClaude Opus 5 5d9597dbd6 chore(expo-uiscene-lifecycle): remove plugin (#330)
The UIKit scene lifecycle support moved into expo-build-properties, so
this standalone config plugin is no longer needed. The package was never
published to npm, so removal needs no deprecation step.

pnpm-lock.yaml is restored to its pre-plugin state, which also undoes the
repo-wide @types/node downgrade that the package's devDependency caused.

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-10-19 13:42:15 +01:00
Christopher Pruijsen 2b36569912 fix(react-native-pdf): use correct react-native-pdf version for SDK 54 in compatibility table (#331) 2026-10-19 13:33:35 +01:00
Hassan Khan e83c44c05e ci: pin pnpm version so the job can install (#332) 2026-10-19 13:31:29 +01:00
Alan Hughes 1fe140bae4 Add publish config to expo-uiscene-lifecycle (#328) 2026-09-15 19:00:09 +01:00
Alan Hughes 4a27ef455a Publish for 58 (#327) 2026-09-15 18:55:09 +01:00
Christian Falch 9d8dccc6fc feat(expo-uiscene-lifecycle): add SDK 57 scene lifecycle plugin (#326)
* feat(expo-uiscene-lifecycle): add SDK 57 scene lifecycle plugin

* chore: update plugin automation metadata

* chore: remove unrelated metadata churn

* fix(expo-uiscene-lifecycle): add lint configuration

* test(expo-uiscene-lifecycle): clarify SDK 57 fixture

* refactor(expo-uiscene-lifecycle): use Expo scene runtime

* refactor(expo-uiscene-lifecycle): simplify plugin tests

* style(expo-uiscene-lifecycle): format plugin

* fix(expo-uiscene-lifecycle): require Expo 57.0.23

* fix(expo-uiscene-lifecycle): require Expo 57.0.23

* test(expo-uiscene-lifecycle): cover version boundaries

* fix(expo-uiscene-lifecycle): remove Expo peer dependency

* fix(expo-uiscene-lifecycle): keep Expo peer unpinned
2026-09-15 18:08:06 +02:00
Phil Pluckthun bced4c7b0d Release patch update 2026-08-15 10:55:56 +01:00
Phil Pluckthun af26cb0d15 Open up expo peer (#325) 2026-08-15 10:54:30 +01:00
Phil Pluckthun 300770f462 release (#321) 2026-06-03 21:14:51 +01:00
Phil Pluckthun 6d6770068f chore: Update tsconfigs, prepublishOnly script, type errors (SDK 56) (#320)
* apple-settings: Update for TS strictness

* ios-stickers: Update for TS strictness

* react-native-callkeep: Fix up

* react-native-branch: Resolve SDK 56 deprecations

* chore: Update template

* chore: Update tsconfigs and publishing package.json:scripts

* ci: Update test worklow

* chore: Apply lints
2026-06-03 21:12:35 +01:00
Phil Pluckthun c8547c07ba publish (#317) 2026-06-03 13:46:32 +01:00
Hassan Khan f7a291fc55 feat: upgrade to v56 (#316)
* feat: upgrade to v56

* chore: remove `@config-plugins/react-native-dynamic-app-icon`
2026-05-31 12:17:14 +01:00
Vivek JM fc33478f00 fix(react-native-branch): defer API key validation to mods (#313) 2026-05-29 14:03:40 +01:00
evanbacon a48bb42c19 pnpm bump 2026-04-09 13:03:33 -07:00
c6292b104a chore(react-native-branch): update react-native-branch (#310)
* feat: introduce test environment handling into config plugin

* chore: configure Branch test app correctly

* fix(react-native-branch): re-init branch session when resumed from background

* chore(react-native-branch): emit warning when `apiKey` from `ExpoConfig.config` is present

---------

Co-authored-by: Rodolfo Perottoni <[email protected]>
Co-authored-by: Morgan Cromell <[email protected]>
2026-04-09 20:58:54 +01:00
Jason SafaiyehandClaude Opus 4.6 3ea7b43e64 fix(ios-stickers): inherit DEVELOPMENT_TEAM from main app target (#303)
* fix(ios-stickers): inherit DEVELOPMENT_TEAM from main app target

The sticker pack extension target was created without setting
DEVELOPMENT_TEAM, requiring developers to manually select the
team in Xcode after every prebuild. This follows the same pattern
used by expo-share-intent and Expo's own DevelopmentTeam utilities:
read the team from the main app target's build configurations and
apply it to the extension's build settings and TargetAttributes.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* refactor(ios-stickers): use DevelopmentTeam helper

* refactor(ios-stickers): simplify development team handling

* fix(ios-stickers): satisfy prettier lint

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-08 11:16:23 +01:00
Hassan Khan d971f378d7 chore: replace Yarn with pnpm (#306)
* chore: replace Yarn with pnpm

* chore: update remaining Yarn references in scripts and CI workflows

* chore: remove `useWorkspaces` from `lerna.json`

* chore: add Jest

* chore: remove `prepublishOnly` scripts

* chore: replace Lerna commands with pnpm equivalents in root `package.json`

* chore: update template

* chore: replace Lerna commands with pnpm equivalents

* chore: use pnpm's `workspace:*` protocol for `apps/*`

* chore: use pnpm catalogs

* docs: update publishing instructions to be more accurate
2026-04-07 13:12:49 +01:00
Hassan Khan a2fdafe0d9 chore: slim down dependencies (#312)
* chore: replace `string.prototype.replaceall` with `replace()`

* chore: replace `ts-node` with `tsx`
2026-04-07 13:01:34 +01:00
Hassan Khan 17c928a7c6 chore: sync GitHub issue template configuration (#311) 2026-04-07 12:12:32 +01:00
Kasymbekov Sultanmyrza 93b0fda31b fix(react-native-callkeep): add missing permissions for CallKeep integration on Android (#285) 2026-04-06 18:25:51 +01:00
Hassan Khan 27cdb03c04 chore: add CLAUDE.md (#304) 2026-04-03 19:00:06 +01:00
Hassan Khan c1150cb723 chore: update README.md with link to React Native Directory (#305) 2026-04-02 02:23:49 +01:00
Raxit Majithiya 639473d539 chore(react-native-branch): update README.md with instructions for iosUniversalLinkDomains configuration (#275)
Document the `iosUniversalLinkDomains` field.
2026-03-24 21:32:26 +00:00
Hassan Khan 6a850e7902 feat: upgrade to v55 (#302)
* chore(apple-settings): bump to `expo@^55`

* chore(ios-stickers): bump to `expo@^55`

* chore(react-native-adjust): bump to `expo@^55`

* chore(react-native-blob-util,react-native-pdf): bump to `expo@^55`

* chore(react-native-branch): bump to `expo@^55`

* chore(react-native-callkeep): bump to `expo@^55`

* chore(react-native-siri-shortcut): bump to `expo@^55`

* chore(react-native-webrtc): bump to `expo@^55`

* chore: bump to `expo-module-scripts@^55`

* chore(react-native-dynamic-icon): bump to `expo@^55`

* chore: update SDK version in scripts and template
2026-03-24 21:10:34 +00:00
1de01f582c chore(react-native-adjust): update react-native-adjust (#301)
* feat(react-native-adjust): remove iAd.framework as it's legacy

* feat(react-native-adjust): update Google Play Services implementation

* feat(react-native-adjust): add support for SDK signature

* feat(adjust): add meta install referrer

---------

Co-authored-by: Hugo EXTRAT <[email protected]>
Co-authored-by: kenan.tendzo <[email protected]>
Co-authored-by: youssef eddibili <[email protected]>
2026-03-24 16:42:34 +00:00
Hassan Khan 69c2af5b87 chore: add WebStorm configuration directory to .gitignore (#300) 2026-03-24 16:40:19 +00:00
evanbacon bd1ffe70c5 publish 2025-09-24 08:24:19 -07:00
45c6742c6b feat: upgrade to v54 (#293)
* chore(apple-settings): update `@config-plugins/apple-settings` to Expo v54

* chore(ios-stickers): update `@config-plugins/ios-stickers` to Expo v54

* chore(react-native-branch): update `@config-plugins/react-native-branch` to Expo v54

Supersedes #279, #281
Fixes #283

* chore(react-native-pdf): update `@config-plugins/react-native-pdf` to Expo v54

* chore(react-native-siri-shortcuts): update `@config-plugins/react-native-siri-shortcuts` to Expo v54

* chore(android-jsc-intl,react-native-adjust,react-native-callkeep): update `@config-plugins/android-jsc-intl`, `@config-plugins/react-native-adjust` and `@config-pluginsreact-native-callkeep` to Expo v54

* chore(react-native-webrtc): update `@config-plugins/react-native-webrtc` to Expo v54

* chore: upgrade monorepo root to Expo v54

* chore: upgrade config plugin scripts to use Expo v54

* docs(CONTRIBUTING.md): add instructions for upgrading config plugins to newer Expo versions

* chore(react-native-dynamic-app-icon): update `@config-plugins/react-native-dynamic-app-icon` to Expo v54

* chore(android-jsc-intl): remove config plugin

* chore: align deprecated/removed package READMEs

* chore: remove `expo-build-properties` from example apps

* docs: sync readme versions with published versions

Closes #284

* docs: update READMEs with correct versions for SDK 54

* docs(react-native-branch): use correct version of `react-native-branch`

Closes #281

* docs(CONTRIBUTING.md): improve instructions for upgrading config plugins to newer Expo versions

---------

Co-authored-by: Anthony VanLent <[email protected]>
Co-authored-by: Lizzi Lindboe <[email protected]>
2025-09-23 18:32:15 +01:00
Hassan Khan 9412250df6 test: update Node versions in test workflow to use maintenance and active LTS releases (#291) 2025-09-15 22:49:11 +01:00
Hassan Khan bd70addec7 chore: restore index.js in apps/app (#292) 2025-09-15 22:48:57 +01:00
Hassan Khan 5deff9c0a0 chore: remove references to detox and ffmpeg-kit-react-native (#290) 2025-09-15 22:48:36 +01:00
Brent Vatne 566f54e785 Update README.md (#280) 2025-06-03 13:59:30 -07:00
Brent Vatne 59f94769fa Update README 2025-06-02 17:27:19 -07:00
Brent Vatne c30e5baa66 Update README 2025-06-02 17:24:01 -07:00
Brent Vatne 480485b5aa Update README 2025-06-02 17:22:04 -07:00
Brent Vatne 700dbf00dd Update README 2025-06-02 17:20:36 -07:00
Brent Vatne 3091b2236b Remove react-native-google-cast plugin directory 2025-06-02 17:14:30 -07:00
Brent Vatne ff43a0ea09 Remove detox from tests 2025-06-02 17:13:11 -07:00
Brent Vatne 00197c766b Remove @config-plugins/detox 2025-06-02 17:07:47 -07:00
Brent Vatne 5ec55510bc Remove ffmpegkit config plugin from tests 2025-06-02 17:07:41 -07:00
Brent Vatne 31975371cf Remove ffmpeg config plugin. Closes #268 2025-06-02 16:58:37 -07:00
Brent Vatne 0a1d687d61 Add publish script 2025-06-02 16:50:29 -07:00
248 changed files with 11037 additions and 23259 deletions

No files matched your search

+1 -4
View File
@@ -29,10 +29,7 @@ body:
attributes:
label: Config Plugin
options:
- '@config-plugins/android-jsc-intl'
- '@config-plugins/apple-settings'
- '@config-plugins/detox'
- '@config-plugins/ffmpeg-kit-react-native'
- '@config-plugins/ios-stickers'
- '@config-plugins/react-native-adjust'
- '@config-plugins/react-native-blob-util'
@@ -62,7 +59,7 @@ body:
label: Reproducible demo
description: >-
This should include as little code as possible, do not simply link your
entire project. If a reproducible demo is not provided, it is very
entire project. If a reproducible demo is not provided, it is very
likely your issue will be closed. Read [here more
guidance](https://stackoverflow.com/help/mcve).
validations:
+4 -6
View File
@@ -6,12 +6,6 @@ contact_links:
If you have a question about an Expo Config Plugin, please ask it on the
forums, discord, Twitter, or Stack Overflow.
url: https://forums.expo.io/
- about: E2E tests and automation for mobile
name: 📦 detox issues
url: https://github.com/wix/Detox/issues
- about: FFmpeg Kit for React Native
name: 📦 ffmpeg-kit-react-native issues
url: https://github.com/arthenica/ffmpeg-kit/issues
- about: Adjust React Native SDK
name: 📦 react-native-adjust issues
url: https://github.com/adjust/react_native_sdk/issues
@@ -20,6 +14,10 @@ contact_links:
stream read/write for process large files.
name: 📦 react-native-blob-util issues
url: https://github.com/RonRadtke/react-native-blob-util/issues
- about: Branch Metrics React Native SDK
name: 📦 react-native-branch issues
url: >-
https://github.com/BranchMetrics/react-native-branch-deep-linking-attribution/issues
- about: iOS 10 CallKit and Android ConnectionService Framework For React Native
name: 📦 react-native-callkeep issues
url: https://github.com/react-native-webrtc/react-native-callkeep/issues
+2 -2
View File
@@ -1,5 +1,5 @@
name: "⭐️ Config Plugin Request"
description: "Suggest an idea for a new Expo Config Plugin."
name: '⭐️ Config Plugin Request'
description: 'Suggest an idea for a new Expo Config Plugin.'
labels: enhancement
body:
- type: markdown
+1 -1
View File
@@ -2,7 +2,7 @@
🚨 We use semantic release (a bot publishes everything automatically)
🚨 so please use conventional commit style for the PR title:
🚨 https://www.conventionalcommits.org/en/v1.0.0/
🚨 Example: feat(detox): add new step
🚨 Example: feat(ios-stickers): add new step
-->
# Why
+23 -5
View File
@@ -5,16 +5,34 @@ on:
branches: [main]
paths:
- .github/workflows/lint-md.yml
- "**.md"
- '**.md'
pull_request:
paths:
- .github/workflows/lint-md.yml
- "**.md"
- '**.md'
jobs:
lint-md:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: yarn install --frozen-lockfile --check-files
- run: npx remark --use validate-links . .github
- name: 👀 Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
- name: 🔨 Install pnpm
uses: pnpm/setup@703c52620218391530e48b9e8870d5c0082e1b9b # v2.1.0
with:
install: false
- name: 🔨 Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: pnpm
- name: Install packages
run: pnpm install --frozen-lockfile
- name: Check Markdown links
run: pnpm exec remark --use validate-links . .github
+30 -46
View File
@@ -2,63 +2,47 @@ name: Test Packages
on:
push:
branches: [main]
pull_request:
types: [opened, synchronize]
pull_request: {}
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node: ["18"]
name: Build with Node ${{ matrix.node }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Setup node
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- run: yarn install --frozen-lockfile --check-files
- run: yarn lerna run prepare --stream
- uses: actions/cache@v4
with:
path: "*"
key: v2-${{ github.sha }}-${{ matrix.node }}
test:
runs-on: ubuntu-latest
needs: build
strategy:
fail-fast: false
matrix:
node: ["18"]
package: [
# Manually add packages here
detox,
ffmpeg-kit-react-native,
ios-stickers,
react-native-blob-util,
react-native-branch,
react-native-siri-shortcut,
react-native-pdf,
]
name: Test ${{ matrix.package }} on Node ${{ matrix.node }}
node: ['22', '24']
name: Build and test on Node ${{ matrix.node }}
steps:
- uses: actions/cache@v4
- name: 👀 Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: "*"
key: v2-${{ github.sha }}-${{ matrix.node }}
- name: Set up Node
uses: actions/setup-node@v1
fetch-depth: 1
- name: 🔨 Install pnpm
uses: pnpm/setup@703c52620218391530e48b9e8870d5c0082e1b9b # v2.1.0
with:
install: false
- name: 🔨 Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ matrix.node }}
- name: Lint ${{ matrix.package }}
run: yarn lint --max-warnings 0
working-directory: packages/${{ matrix.package }}
- name: Test ${{ matrix.package }}
run: yarn test
working-directory: packages/${{ matrix.package }}
cache: pnpm
- name: Install packages
run: pnpm install --frozen-lockfile
- name: Build all packages
run: pnpm --filter './packages/*' run build
- name: Lint all packages
run: pnpm --filter './packages/*' run lint --deny-warnings
- name: Check formatting
run: pnpm format --check
- name: Test all packages
run: pnpm --filter './packages/*' run test --passWithNoTests
env:
CI: true
EXPO_DEBUG: true
+5 -2
View File
@@ -9,7 +9,7 @@ web-build
# Node
node_modules
npm-debug.log
yarn-error.log
pnpm-debug.log
# Ruby
.direnv
@@ -48,6 +48,9 @@ xcuserdata
.project
.settings
# WebStorm
.idea
# VSCode
.history/
@@ -63,4 +66,4 @@ packages/@*/*/build/**
apps/*/ios/*
apps/*/android/*
apps/*/web-build/*
apps/*/dist/*
apps/*/dist/*
+30
View File
@@ -0,0 +1,30 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"printWidth": 100,
"tabWidth": 2,
"singleQuote": true,
"bracketSameLine": true,
"trailingComma": "es5",
"ignorePatterns": [
"**/node_modules/**",
"**/build/**",
"**/dist/**",
"**/__generated__/**",
"**/fixtures/**",
"**/.expo/**",
"**/assets/**",
"**/android/**",
"**/ios/**",
"**/expo-env.d.ts",
"packages/apple-settings/src/schema/SettingsPlist.ts"
],
"sortImports": {
"groups": [
["builtin", "external"],
["internal", "subpath"],
["parent", "sibling", "index"],
"unknown"
],
"ignoreCase": false
}
}
+107
View File
@@ -0,0 +1,107 @@
# CLAUDE.md
## Project Overview
Monorepo of community-maintained Expo config plugins for React Native libraries. Plugins auto-configure native Android/iOS projects during `npx expo prebuild`.
- **Namespace**: `@config-plugins/*`
- **Package manager**: pnpm 12 with workspaces (version pinned in `devEngines.packageManager` in the root `package.json`)
- **Publishing**: `pnpm publish` (independent versioning)
## Common Commands
```bash
# Install dependencies
pnpm install
# Build all packages
pnpm prepare
# Watch build all packages
pnpm start
# Generate a new plugin
pnpm gen
# Run tests for a specific package
cd packages/<name> && pnpm test
# Lint a specific package
cd packages/<name> && pnpm lint --deny-warnings
# Format the whole repo (add --check to only verify)
pnpm format
# Build a specific package
cd packages/<name> && pnpm build
# Clean a specific package
cd packages/<name> && pnpm clean
# Rebuild an example app to test config plugins
cd apps/<name> && pnpm expo prebuild --clean
```
## Project Structure
- `packages/` — Config plugin packages (each has `src/`, `__tests__/`)
- `apps/` — Example apps for testing plugins
- `fixtures/` — Shared test fixtures (native config files: AppDelegate, build.gradle, Podfile, etc.)
- `scripts/` — Repo tooling (plugin generator, dependabot config, issue templates)
## Conventions
- Use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) (e.g., `feat(react-native-foo): add bar`)
- Branch naming: `@your-github-username/type/description`
- Each package runs `tsc`, `jest` and `oxlint` directly. Shared config lives at the root: `tsconfig.base.json`, `jest.config.base.js`, `oxlint.config.mjs`. Formatting is `oxfmt` with `.oxfmtrc.json`
- TypeScript source in `src/`, compiled output in `build/`
- Shared dependency versions are defined in the `catalog:` block in `pnpm-workspace.yaml` and referenced with `catalog:` in each package's `package.json`
- Peer dependency on `expo@^56`
- Tests use Jest with `memfs` for virtual filesystem testing
## Upgrading Plugins for a New Expo SDK Version
When a new Expo SDK is released (e.g., SDK 54 → SDK 55), every package and example app must be updated. Use conventional commits scoped to the package name (e.g., `chore(react-native-foo): bump to expo@^55`).
### Catalog updates (`pnpm-workspace.yaml`)
Update the `catalog:` block with the new SDK versions for shared dependencies:
1. **`expo`** — Update to `^<NEW_SDK>` (used as `peerDependencies` in all packages).
2. **`@expo/config-plugins`** — Update to `^<NEW_SDK>`.
3. Review other catalog entries (`@expo/image-utils`, `@expo/plist`, etc.) and bump if needed.
### Per-package updates (`packages/<name>/`)
For each package:
1. **`README.md`** — Add a new row to the version compatibility table with the new SDK version, the latest compatible upstream package version, and the next config plugin major version.
2. **Build & test** — Run `pnpm build` and `pnpm test` (if tests exist) to verify nothing broke.
### Example app updates (`apps/<name>/`)
For each example app:
1. **`package.json`** — Update `expo` to the new SDK version (e.g., `~54` → `~55`).
2. Run `npx expo install --fix` to align all other dependencies (`react`, `react-native`, `expo-splash-screen`, etc.) to compatible versions.
3. Remove any stale vendored templates or files (e.g., old `expo-template-bare-minimum-*.tgz`).
### Monorepo root updates
1. **`scripts/generate-plugin.ts`** — Update `SDK_VERSION` to the new SDK number.
### Final steps
1. Run `pnpm install` from the root to regenerate `pnpm-lock.yaml`.
2. Run `pnpm prepare` to verify all packages build.
3. Run tests for packages that have them.
4. Update `CLAUDE.md` if any conventions changed (e.g., the peer dependency version listed under Conventions).
## After Adding/Removing Packages
Run these from the root:
```bash
pnpm update-dependabot-config
pnpm update-issue-template
```
+18 -5
View File
@@ -5,7 +5,7 @@
To submit a pull request:
1. Fork the [repository](https://github.com/expo/config-plugins) and create a feature branch. (Existing contributors can create feature branches without forking. Prefix the branch name with `@your-github-username/`.)
2. Run `yarn` in the root directory to generate the build files.
2. Run `pnpm install` in the root directory to generate the build files.
3. Use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) including in the PR title.
4. Fill out the entire PR template. Ensure your code is tested locally and has continuous tests in CI.
5. Make sure all tests pass on GitHub Actions.
@@ -13,15 +13,28 @@ To submit a pull request:
## Creating a plugin
- Plugins can be generated by running `yarn gen`.
- Plugins can be generated by running `pnpm gen`.
- Add plugins to the example in [`apps/app`](/apps/app/package.json) to test them.
- The example app should always stay in a state of regeneration.
Be sure to run the following scripts in the **root directory** to update automation tools:
- Update dependabot: `yarn update-dependabot-config`
- Update the issue template: `yarn update-issue-template`
- Update dependabot: `pnpm update-dependabot-config`
- Update the issue template: `pnpm update-issue-template`
## Upgrading plugins for a new Expo version
- Update the `catalog:` block in `pnpm-workspace.yaml` with the new SDK versions for `expo`, `@expo/config-plugins`, and any other shared dependencies
- Ensure every project under `apps/` is updated to latest version of Expo
- Update each library’s `README.md`
- Ensuring the latest version of Expo is listed
- Ensure the upstream package version is correct
- Use the next version for the matching config plugin
- Update `SDK_VERSION` in [`scripts/generate-plugin.ts`](https://github.com/expo/config-plugins/blob/566f54e785147d88cb4c98490c3e536ee7ef3001/scripts/generate-plugin.ts#L67)
## Publishing a release
All publishing should be handled automatically whenever code is merged to `main`.
Packages are published manually using the scripts in the root `package.json`:
1. `pnpm bump`: Increments the major version of all packages.
2. `pnpm release`: Publishes all packages to npm.
+6 -58
View File
@@ -1,68 +1,16 @@
# Expo Config Plugins
- A collection of [Expo config plugins](https://docs.expo.dev/guides/config-plugins/) for configuring the `npx expo prebuild` command.
- This repo is to Expo config plugins as [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) is to TypeScript.
- We prefer packages ship their own Expo config plugin (to ensure versioning is aligned), but if they haven't adopted the system yet, the community can add a package here.
- All packages here are community maintained. Feel free to improve docs, packages, tests, etc.
- We maintain a 1-1 mapping of **native packages** ⇔ `@config-plugins/*`. This means there should be no general utility packages here -- those are better suited for a different repo / NPM namespace.
This repository is a collection of assorted [Expo config plugins](https://docs.expo.dev/guides/config-plugins/) for configuring the `npx expo prebuild` command. It is meant to be a temporary home for packages that don't have a built-in Config Plugin.
## First-party plugins
- **We prefer packages to ship their own Expo config plugin**. This helps ensure that plugin and package versions are always aligned, and that new features are made available in both the package and plugin simultaneously. If packages haven't adopted config plugins yet, the community can add a package here as a temporary home.
Here is a list of known packages that have a built-in Config Plugin.
- **All packages here are community maintained.** They are not maintained by Expo. Feel free to improve docs, packages, tests, etc.
> Not all packages need a config plugin, packages that don't appear here might still work with managed EAS.
- **We maintain a one-to-one mapping between native packages and `@config-plugins/*`**. This means there should be no general utility packages here — those are better suited for a different repository or npm namespace.
- [React Native Firebase](https://rnfirebase.io/) (`@react-native-firebase/perf`, `@react-native-firebase/app`, `@react-native-firebase/crashlytics`)
- [react-native-google-cast](https://github.com/react-native-google-cast/react-native-google-cast)
- [react-native-nfc-manager](https://github.com/revtel/react-native-nfc-manager)
- [react-native-health](https://github.com/agencyenterprise/react-native-health)
- [@react-native-mapbox-gl/maps](https://github.com/rnmapbox/maps)
- [@logrocket/react-native](https://www.npmjs.com/package/@logrocket/react-native)
- [sentry-expo](https://www.npmjs.com/package/sentry-expo)
- [@leanplum/react-native-sdk](https://github.com/Leanplum/Leanplum-ReactNative-SDK#readme)
- [react-native-fbsdk-next](https://www.npmjs.com/package/react-native-fbsdk-next)
- [@stripe/stripe-react-native](https://www.npmjs.com/package/@stripe/stripe-react-native)
- [@react-native-voice/voice](https://www.npmjs.com/package/@react-native-voice/voice)
- [react-native-imglysdk](https://www.npmjs.com/package/react-native-imglysdk)
- [@viro-community/react-viro](https://github.com/virocommunity/viro) -- [Guide](https://viro-community.readme.io/docs/integrating-with-expo)
- [react-native-msal](https://www.npmjs.com/package/react-native-msal)
- [react-native-spotlight-search](https://www.npmjs.com/package/react-native-spotlight-search)
- [newrelic-react-native-agent](https://www.npmjs.com/package/newrelic-react-native-agent)
- [react-native-videoeditorsdk](https://www.npmjs.com/package/react-native-videoeditorsdk)
- [notifee](https://notifee.app/)
- [App Clips](https://github.com/bndkt/react-native-app-clip/)
- [iOS Safari Extensions](https://github.com/andrew-levy/react-native-safari-extension/)
- [react-native-ble-plx](https://github.com/dotintent/react-native-ble-plx)
## Looking for other config plugins?
### Complementary
More out-of-tree plugins which can be used to configure more packages.
- [onesignal-expo-plugin](https://github.com/OneSignal/onesignal-expo-plugin) (for use with `react-native-onesignal`)
- Configures **iOS Notification Service Extensions** for rich push notification and custom notification actions.
- [@lyrahealth-inc/react-native-orientation-plugin](https://www.npmjs.com/package/@lyrahealth-inc/react-native-orientation-plugin) (for use with `react-native-orientation-plugin`)
- [airship-expo-plugin](https://www.npmjs.com/package/airship-expo-plugin) (for use with `urbanairship-react-native`)
- [config-plugin-react-native-intercom](https://www.npmjs.com/package/config-plugin-react-native-intercom) (for use with `intercom-react-native`)
- [@allboatsrise/expo-marketingcloudsdk](https://www.npmjs.com/package/@allboatsrise/expo-marketingcloudsdk) (for use with `react-native-marketingcloudsdk`)
- [@ouvio/react-native-background-location-plugin](https://www.npmjs.com/package/@ouvio/react-native-background-location-plugin) (for use with `react-native-background-location-plugin`)
- [expo-community-flipper](https://www.npmjs.com/package/expo-community-flipper) (for use with Flipper)
- [expo-appcenter](https://www.npmjs.com/package/expo-appcenter) (for use with `appcenter`)
- [expo-react-native-freshchat](https://www.npmjs.com/package/expo-react-native-freshchat) (for use with `react-native-freshchat-sdk`)
- [react-native-keyevent-expo-config-plugin](https://github.com/chronsyn/react-native-keyevent-expo-config-plugin) (for use with `react-native-keyevent`)
- [watermelondb-expo-plugin](https://github.com/morrowdigital/watermelondb-expo-plugin) (for use with `@nozbe/watermelondb`)
### No Plugin Required
Just install and rebuild! If a package doesn't require any futher setup then it most likely doesn't need an Expo config plugin. Most packages work without a config plugin (including packages not listed below):
- [react-native-share](https://github.com/react-native-share/react-native-share)
- [@react-native-menu/menu](https://github.com/react-native-menu/menu)
- [react-native-blurhash](https://github.com/mrousavy/react-native-blurhash)
- [react-native-app-shortcuts](https://github.com/lokyoung/react-native-app-shortcuts)
- [react-native-multiple-image-picker](https://github.com/baronha/react-native-multiple-image-picker)
- [`@shopify/flash-list`](https://github.com/Shopify/flash-list)
> Feel free to [open a PR](https://github.com/expo/config-plugins/edit/main/README.md) with missing packages.
Many React Native libraries ship their own built-in config plugins. You can find them on the [React Native Directory](https://reactnative.directory/).
## Contributing!
+1 -3
View File
@@ -1,3 +1 @@
export { default } from "./src/detox/App";
// export { default } from "./src/android-jsc-intl/App";
// export { default } from "./src/react-native-adjust/App";
export { default } from './src/react-native-adjust/App';
+1 -3
View File
@@ -1,6 +1,6 @@
{
"expo": {
"name": "detoxapp",
"name": "app",
"slug": "app",
"version": "1.0.0",
"orientation": "portrait",
@@ -29,10 +29,8 @@
"favicon": "./assets/favicon.png"
},
"plugins": [
"@config-plugins/detox",
"@config-plugins/react-native-adjust",
"@config-plugins/react-native-callkeep",
"@config-plugins/android-jsc-intl",
"expo-localization"
]
}
-80
View File
@@ -1,80 +0,0 @@
const iosName = "detoxapp";
const simulator = "iPhone 16";
const derivedDataPath = "ios/build";
const sdk = "iphonesimulator";
module.exports = {
testRunner: {
$0: "jest",
args: {
config: require.resolve("./e2e/jest.config.js"),
_: ["e2e"],
},
jest: {
setupTimeout: 300000,
reportSpecs: true,
reportWorkerAssign: true,
},
},
behavior: {
init: {
exposeGlobals: false,
},
},
apps: {
"ios.release": {
type: "ios.app",
binaryPath: `${derivedDataPath}/Build/Products/Release-${sdk}/${iosName}.app`,
build: `./scripts/build-detox-ios.sh ${iosName} Release YES`,
},
"ios.debug": {
type: "ios.app",
binaryPath: `${derivedDataPath}/Build/Products/Debug-${sdk}/${iosName}.app`,
build: `./scripts/start-metro.sh && ./scripts/build-detox-ios.sh ${iosName} Debug`,
},
"android.debug": {
type: "android.apk",
binaryPath: "android/app/build/outputs/apk/debug/app-debug.apk",
build:
"./scripts/start-metro.sh && pushd android; ./gradlew app:assembleDebug app:assembleAndroidTest -DtestBuildType=debug; popd",
},
"android.release": {
type: "android.apk",
binaryPath: "android/app/build/outputs/apk/release/app-release.apk",
build:
"pushd android; ./gradlew app:assembleRelease app:assembleAndroidTest -DtestBuildType=release; popd",
},
},
devices: {
simulator: {
type: "ios.simulator",
device: {
type: simulator,
},
},
emulator: {
type: "android.emulator",
device: {
avdName: "Pixel_6_Pro_API_33",
},
},
},
configurations: {
"ios.sim.release": {
device: "simulator",
app: "ios.release",
},
"ios.sim.debug": {
device: "simulator",
app: "ios.debug",
},
"android.emu.debug": {
device: "emulator",
app: "android.debug",
},
"android.emu.release": {
device: "emulator",
app: "android.release",
},
},
};
-39
View File
@@ -1,39 +0,0 @@
import { by, device, element, expect } from "detox";
import * as path from "path";
import { createCapture } from "../lib";
const projectRoot = path.resolve(__dirname, "../../");
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPInternational/LanguageandLocaleIDs/LanguageandLocaleIDs.html
const languages = ["en-US", "nl-NL", "zh-CN"];
for (const locale of languages) {
const { captureAsync, resetAppAsync } = createCapture({
projectRoot,
locale,
});
describe(locale, () => {
beforeAll(resetAppAsync);
beforeEach(async () => {
await device.reloadReactNative();
});
it("should have welcome screen", async () => {
await expect(element(by.id("welcome"))).toBeVisible();
await captureAsync();
});
xit("should show hello screen after tap", async () => {
await element(by.id("hello_button")).tap();
await captureAsync();
});
xit("should show world screen after tap", async () => {
await element(by.id("world_button")).tap();
await captureAsync();
});
});
}
-14
View File
@@ -1,14 +0,0 @@
module.exports = {
testRunner: require.resolve("jest-circus/runner"),
testTimeout: 120000,
rootDir: "..",
testMatch: ["<rootDir>/**/__tests__/*"],
transform: {
"^.+\\.[jt]sx?$": "ts-jest",
},
reporters: [require.resolve("detox/runners/jest/reporter")],
globalSetup: "detox/runners/jest/globalSetup",
globalTeardown: "detox/runners/jest/globalTeardown",
testEnvironment: "detox/runners/jest/testEnvironment",
verbose: true,
};
-76
View File
@@ -1,76 +0,0 @@
import { device } from "detox";
import * as fs from "fs";
import * as path from "path";
import { setStatusBar } from "./setStatusBar";
export function getOutputDir(projectRoot: string, locale: string) {
return path.resolve(
projectRoot,
`meta/screenshots/${device.getPlatform()}/${locale}/`
);
}
export async function takeScreenshotAsync({
info,
projectRoot,
index,
locale,
}: {
projectRoot: string;
locale: string;
info?: string;
index?: number;
}) {
const outputDir = getOutputDir(projectRoot, locale);
fs.mkdirSync(outputDir, { recursive: true });
if (index == null) {
index = fs.readdirSync(outputDir).length;
}
const name = `APP_${device.name}_${index}`.toUpperCase();
const imgPath = path.resolve(outputDir, `${name}.png`);
const imagePath = await device.takeScreenshot(name);
await fs.promises.copyFile(imagePath, imgPath);
}
export function createCapture({
projectRoot,
locale,
}: {
locale: string;
projectRoot: string;
}) {
const captureAsync = () =>
takeScreenshotAsync({
projectRoot,
locale,
});
const clearAsync = async () => {
// Clear old screen shots
const outputDir = getOutputDir(projectRoot, locale);
await fs.promises.rmdir(outputDir, { recursive: true }).catch(() => null);
};
return {
captureAsync,
clearAsync,
async resetAppAsync() {
// Clear old screen shots
await clearAsync();
// configure status bars
setStatusBar();
// reset expo-localization which doesn't account for simulators dynamically updating without resetting the phone.
await device.terminateApp();
// launch with lang
await device.launchApp({
languageAndLocale: { locale, language: locale },
launchArgs: { locale },
});
},
};
}
-32
View File
@@ -1,32 +0,0 @@
import { execSync } from "child_process";
import { device } from "detox";
export async function setStatusBar() {
if (device.getPlatform() === "ios") {
// TODO: Set date on iPad - "Tue Jan 9"
// assumes only one simulator is booted
// use Apple's magic time "9:41" - new Date('1/9/2007 09:41').toISOString()
execSync(
"xcrun simctl status_bar booted override --time 9:41 --batteryState charged --batteryLevel 100 --wifiBars 3 --cellularMode active --cellularBars 4"
);
} else {
// enter demo mode
execSync("adb shell settings put global sysui_demo_allowed 1");
// display time 6:00
execSync(
"adb shell am broadcast -a com.android.systemui.demo -e command clock -e hhmm 1850"
);
// Display full mobile data with 4g type and no wifi
execSync(
"adb shell am broadcast -a com.android.systemui.demo -e command network -e mobile show -e level 4 -e datatype 4g -e wifi false"
);
// Hide notifications
execSync(
"adb shell am broadcast -a com.android.systemui.demo -e command notifications -e visible false"
);
// Show full battery but not in charging state
execSync(
"adb shell am broadcast -a com.android.systemui.demo -e command battery -e plugged false -e level 100"
);
}
}
+2 -11
View File
@@ -1,15 +1,6 @@
import { registerRootComponent } from "expo";
import { registerRootComponent } from 'expo';
import Constants from "expo-constants";
let App;
// Only use the Detox test in CI
if ((Constants.manifest.extra || {}).CI) {
App = require("./src/detox/App").default;
} else {
App = require("./App").default;
}
// import App from "./App";
import App from './App';
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
+13 -29
View File
@@ -1,42 +1,27 @@
{
"name": "@expo/app",
"main": "./index.js",
"version": "1.0.0",
"private": true,
"main": "./index.js",
"scripts": {
"build:ios": "detox build -c ios.sim.debug",
"test:ios": "detox test -c ios.sim.debug",
"e2e:ios": "npm run build:ios && npm run test:ios",
"build:ios-release": "detox build -c ios.sim.release",
"test:ios-release": "detox test -c ios.sim.release",
"e2e:ios-release": "npm run build:ios-release && npm run test:ios-release",
"build:android": "detox build -c android.emu.debug",
"test:android": "detox test -c android.emu.debug",
"e2e:android": "npm run build:android && npm run test:android",
"build:android-release": "detox build -c android.emu.release",
"test:android-release": "detox test -c android.emu.release",
"e2e:android-release": "npm run build:android-release && npm run test:android-release",
"clean:android": "pushd android && ./gradlew clean && popd",
"start": "expo start --dev-client",
"ios": "expo run:ios",
"android": "expo run:android"
},
"dependencies": {
"@config-plugins/android-jsc-intl": "*",
"@config-plugins/detox": "*",
"@config-plugins/react-native-adjust": "*",
"@config-plugins/react-native-callkeep": "*",
"detox": "^20.37.0",
"expo": "~53",
"expo-localization": "~16.1.5",
"expo-splash-screen": "~0.30.7",
"@config-plugins/react-native-adjust": "workspace:*",
"@config-plugins/react-native-callkeep": "workspace:*",
"expo": "~56",
"expo-localization": "~56.0.6",
"expo-splash-screen": "~56.0.10",
"fbjs": "^0.8.18",
"i18n-js": "^3.8.0",
"luxon": "^1.27.0",
"react": "19.0.0",
"react-native": "0.79.2",
"react-native-adjust": "^5.1.0",
"react": "19.2.3",
"react-native": "0.85.3",
"react-native-adjust": "^5.6.0",
"react-native-callkeep": "^4.3.16",
"react-native-safe-area-context": "5.3.0"
"react-native-safe-area-context": "~5.7.0"
},
"devDependencies": {
"@babel/plugin-syntax-jsx": "^7.16.0",
@@ -46,7 +31,6 @@
"jest": "~29.7.0",
"jest-circus": "^29.5.0",
"ts-jest": "^29.0.5",
"typescript": "~5.8.3"
},
"private": true
"typescript": "~5.9.2"
}
}
-30
View File
@@ -1,30 +0,0 @@
#!/usr/bin/env bash
# This script wraps the xcodebuild command and exits with non-zero if the build fails.
#
# This ensures that CI fails on the correct step instead of attempting to run Detox tests without a
# build.
set -eu
iosName=$1
# Debug or Release
configuration=${3:-"Debug"}
# YES or NO
UseModernBuildSystem=${3:-"NO"}
xcodebuild \
-workspace ios/$iosName.xcworkspace \
-scheme $iosName \
-configuration "$configuration" \
-sdk iphonesimulator \
-derivedDataPath "ios/build" \
-UseModernBuildSystem="$UseModernBuildSystem" 2>&1 | npx excpretty ./
if [ "${PIPESTATUS[0]}" -ne "0" ]; then
echo 'Build Failed'
set +e
exit 1
fi
echo 'Build Succeeded'
-28
View File
@@ -1,28 +0,0 @@
import React from "react";
import { Text, View } from "react-native";
import { DateTime } from "luxon";
const currentDate = DateTime.fromObject({
locale: "en-US",
zone: "America/New_York",
});
export default function App() {
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "blue",
}}
>
<Text style={{ fontSize: 18, color: "white", textAlign: "center" }}>
Intl support for Android, without Hermes 🤖
</Text>
<Text style={{ fontSize: 18, color: "white", textAlign: "center" }}>
{currentDate.toISO()}
</Text>
</View>
);
}
-62
View File
@@ -1,62 +0,0 @@
import React from "react";
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
import * as Localization from "expo-localization";
import i18n from "i18n-js";
// Set the key-value pairs for the different languages you want to support.
i18n.translations = {
"en-US": { welcome: "Welcome", hello: "Hello", world: "World" },
"nl-NL": { welcome: "Welkom", hello: "Hallo", world: "Wereld" },
"zh-CN": { welcome: "欢迎", hello: "你好", world: "世界" },
};
// Set the locale once at the beginning of your app.
i18n.locale = Localization.locale;
export default function App() {
const [greeting, setGreeting] = React.useState("");
if (greeting) {
return (
<View style={styles.container}>
<Text style={styles.text}>{greeting}!!!</Text>
</View>
);
}
return (
<View testID="welcome" style={styles.container}>
<Text style={styles.text}>
{i18n.t("welcome")} {JSON.stringify(Localization.locales)}
</Text>
<TouchableOpacity
testID="hello_button"
onPress={() => setGreeting(i18n.t("hello"))}
>
<Text style={styles.buttonText}>Say {i18n.t("hello")}</Text>
</TouchableOpacity>
<TouchableOpacity
testID="world_button"
onPress={() => setGreeting(i18n.t("world"))}
>
<Text style={styles.buttonText}>Say {i18n.t("world")}</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 20,
justifyContent: "center",
alignItems: "center",
},
text: {
fontSize: 25,
marginBottom: 30,
},
buttonText: {
color: "blue",
marginBottom: 20,
},
});
+7 -9
View File
@@ -1,13 +1,11 @@
import React from "react";
import { SafeAreaView, Text, StyleSheet } from "react-native";
import { Adjust, AdjustConfig } from "react-native-adjust";
import React from 'react';
import { SafeAreaView, Text, StyleSheet } from 'react-native';
import { Adjust, AdjustConfig } from 'react-native-adjust';
export default function App() {
React.useEffect(() => {
const adjustConfig = new AdjustConfig(
"APP_TOKEN",
AdjustConfig.EnvironmentSandbox
);
// https://github.com/adjust/react_native_sdk/blob/bd4fe59403a57599767606b2bcaa116852df973d/example/App.js#L45
const adjustConfig = new AdjustConfig('APP_TOKEN', AdjustConfig.EnvironmentSandbox);
Adjust.enable();
}, []);
@@ -21,7 +19,7 @@ export default function App() {
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
justifyContent: 'center',
alignItems: 'center',
},
});
+49
View File
@@ -0,0 +1,49 @@
appId: dev.bacon.applesettings
---
# Kill the Settings app to ensure a clean state
- killApp: 'com.apple.Preferences'
# Launch the app
- launchApp
# Verify main UI elements are visible
- assertVisible:
id: 'name-input'
- assertVisible:
id: 'enabled-switch'
- assertVisible:
id: 'slider'
# Clear any existing text and type a new value
- tapOn:
id: 'name-input'
- eraseText: 50
- inputText: 'Maestro'
# Toggle the switch on (default is off)
- tapOn:
id: 'enabled-switch'
# Open iOS Settings for this app
- tapOn: 'Launch Settings'
# Navigate to the app's settings page
- scrollUntilVisible:
element: 'Apps'
direction: DOWN
- tapOn: 'Apps'
- scrollUntilVisible:
element: 'apple-settings'
direction: DOWN
- tapOn: 'apple-settings'
- scrollUntilVisible:
element: 'apple-settings Settings'
direction: DOWN
# Verify updated values in the iOS Settings app
- assertVisible: 'Maestro'
# Verify the Enabled toggle is on (checked = true when on)
- assertVisible:
id: 'PSToggleSwitchSpecifier.enabled_preference.Enabled'
checked: true
+19 -19
View File
@@ -6,14 +6,14 @@ import withAppleSettings, {
Switch,
TextField,
Title,
} from "@config-plugins/apple-settings";
import { MultiValue } from "@config-plugins/apple-settings/build/models";
import { ConfigContext, ExpoConfig } from "expo/config";
import path from "node:path";
} from '@config-plugins/apple-settings';
import { MultiValue } from '@config-plugins/apple-settings/build/models';
import { ConfigContext, ExpoConfig } from 'expo/config';
import path from 'node:path';
const folderName = path.basename(__dirname);
const cleanName = folderName.replace(/[_\s-]/g, "");
const appId = "dev.bacon." + cleanName;
const cleanName = folderName.replace(/[_\s-]/g, '');
const appId = 'dev.bacon.' + cleanName;
function withWhiteLabel(config: Partial<ExpoConfig>) {
if (!config.extra) config.extra = {};
@@ -39,7 +39,7 @@ module.exports = ({ config }: ConfigContext): Partial<ExpoConfig> => {
// The Apple locale code. This will be used to generate the .strings file.
en: {
// Name of the localized key.
Name: "Text Field",
Name: 'Text Field',
},
},
// The page object is required. It will be used to generate the .plist file.
@@ -48,27 +48,27 @@ module.exports = ({ config }: ConfigContext): Partial<ExpoConfig> => {
// The `PreferenceSpecifiers` defines the UI elements to generate.
PreferenceSpecifiers: [
Title({
title: "Title",
value: "Default Title",
key: "title_preference",
title: 'Title',
value: 'Default Title',
key: 'title_preference',
}),
// Helper models can be used to generate the UI elements using
// an API that's similar to React Native.
TextField({
title: "Name",
key: "name_preference",
value: "",
keyboardType: "Alphabet",
autoCapitalize: "None",
autoCorrect: "No",
title: 'Name',
key: 'name_preference',
value: '',
keyboardType: 'Alphabet',
autoCapitalize: 'None',
autoCorrect: 'No',
}),
Switch({
title: "Enabled",
key: "enabled_preference",
title: 'Enabled',
key: 'enabled_preference',
value: true,
}),
Slider({
key: "slider_preference",
key: 'slider_preference',
value: 0.5,
}),
// Broken https://forums.developer.apple.com/forums/thread/764519
+1 -2
View File
@@ -2,7 +2,6 @@
"expo": {
"name": "apple-settings",
"icon": "https://github.com/expo.png",
"platforms": ["ios"],
"plugins": [["expo-build-properties", { "ios": { "ccacheEnabled": true } }]]
"platforms": ["ios"]
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { registerRootComponent } from "expo";
import { registerRootComponent } from 'expo';
import App from "./src/App";
import App from './src/App';
registerRootComponent(App);
+12 -11
View File
@@ -1,20 +1,21 @@
{
"name": "@config-plugins-app/apple-settings",
"main": "./index.js",
"version": "1.0.0",
"private": true,
"main": "./index.js",
"scripts": {
"start": "expo start --dev-client",
"ios": "expo run:ios",
"android": "expo run:android"
"android": "expo run:android",
"e2e": "maestro test .maestro/"
},
"dependencies": {
"@config-plugins/apple-settings": "*",
"@react-native-community/slider": "4.5.6",
"@react-native-picker/picker": "2.11.0",
"expo": "~53",
"expo-splash-screen": "~0.30.7",
"react": "19.0.0",
"react-native": "0.79.2"
},
"private": true
"@config-plugins/apple-settings": "workspace:*",
"@react-native-community/slider": "5.2.0",
"@react-native-picker/picker": "2.11.4",
"expo": "~56",
"expo-splash-screen": "~56.0.10",
"react": "19.2.3",
"react-native": "0.85.3"
}
}
+15 -13
View File
@@ -1,5 +1,5 @@
import React from "react";
import { Button, Linking, StyleSheet, View } from "react-native";
import React from 'react';
import { Button, Linking, StyleSheet, View } from 'react-native';
import {
SettingsRadioGroup,
@@ -7,33 +7,35 @@ import {
SettingsSwitch,
SettingsTextInput,
SettingsTitle,
} from "./SettingsViews";
} from './SettingsViews';
const App = () => {
return (
<View style={styles.container}>
{/* <SettingsTitle settingsKey="title_preference" /> */}
<SettingsTextInput
testID="name-input"
style={{
height: 40,
borderColor: "gray",
borderColor: 'gray',
borderWidth: 1,
padding: 8,
}}
settingsKey="name_preference"
/>
<SettingsSwitch settingsKey="enabled_preference" />
<SettingsSlider settingsKey="slider_preference" />
<SettingsSwitch testID="enabled-switch" settingsKey="enabled_preference" />
<SettingsSlider testID="slider" settingsKey="slider_preference" />
<View />
<SettingsRadioGroup settingsKey="radio_preference">
<SettingsRadioGroup.Item label="Option 1" value="option1" />
<SettingsRadioGroup.Item label="Option 2" value="option2" />
</SettingsRadioGroup>
{/* Broken https://forums.developer.apple.com/forums/thread/764519 */}
{/*<SettingsRadioGroup settingsKey="radio_preference">*/}
{/* <SettingsRadioGroup.Item label="Option 1" value="option1" />*/}
{/* <SettingsRadioGroup.Item label="Option 2" value="option2" />*/}
{/*</SettingsRadioGroup>*/}
<View />
<Button
title="Launch Settings"
onPress={() => {
Linking.openSettings();
void Linking.openSettings();
}}
/>
</View>
@@ -43,8 +45,8 @@ const App = () => {
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "stretch",
justifyContent: 'center',
alignItems: 'stretch',
paddingHorizontal: 56,
gap: 12,
},
+10 -22
View File
@@ -1,7 +1,7 @@
import Slider from "@react-native-community/slider";
import { Picker } from "@react-native-picker/picker";
import React from "react";
import { Settings, Switch, Text, TextInput } from "react-native";
import Slider from '@react-native-community/slider';
import { Picker } from '@react-native-picker/picker';
import React from 'react';
import { Settings, Switch, Text, TextInput } from 'react-native';
export function useSetting<T = string>(key: string): [T, (value: T) => void] {
const [value, setValue] = React.useState<T>(() => Settings.get(key));
@@ -32,7 +32,7 @@ export function SettingsSlider({
...props
}: { settingsKey: string } & Omit<
React.ComponentProps<typeof Slider>,
"selectedValue" | "onValueChange"
'selectedValue' | 'onValueChange'
>) {
const [value, setValue] = useSetting<number>(settingsKey);
return <Slider {...props} value={value ?? 0} onValueChange={setValue} />;
@@ -43,16 +43,10 @@ export function SettingsRadioGroup({
...props
}: { settingsKey: string } & Omit<
React.ComponentProps<typeof Picker>,
"selectedValue" | "onValueChange"
'selectedValue' | 'onValueChange'
>) {
const [value, setValue] = useSetting<string>(settingsKey);
return (
<Picker
{...props}
selectedValue={value ?? "option1"}
onValueChange={setValue}
/>
);
return <Picker {...props} selectedValue={value ?? 'option1'} onValueChange={setValue} />;
}
SettingsRadioGroup.Item = Picker.Item;
@@ -60,10 +54,7 @@ SettingsRadioGroup.Item = Picker.Item;
export function SettingsSwitch({
settingsKey,
...props
}: { settingsKey: string } & Omit<
React.ComponentProps<typeof Switch>,
"value" | "onValueChange"
>) {
}: { settingsKey: string } & Omit<React.ComponentProps<typeof Switch>, 'value' | 'onValueChange'>) {
const [value, setValue] = useSetting<boolean>(settingsKey);
return <Switch {...props} value={!!value} onValueChange={setValue} />;
}
@@ -71,10 +62,7 @@ export function SettingsSwitch({
export function SettingsTitle({
settingsKey,
...props
}: { settingsKey: string } & Omit<
React.ComponentProps<typeof Text>,
"children"
>) {
}: { settingsKey: string } & Omit<React.ComponentProps<typeof Text>, 'children'>) {
const [value] = useSetting<string>(settingsKey);
return <Text {...props} children={value} />;
}
@@ -84,7 +72,7 @@ export function SettingsTextInput({
...props
}: { settingsKey: string } & Omit<
React.ComponentProps<typeof TextInput>,
"value" | "onChangeText"
'value' | 'onChangeText'
>) {
const [value, setValue] = useSetting<string>(settingsKey);
return <TextInput {...props} value={value} onChangeText={setValue} />;
@@ -1,16 +0,0 @@
const path = require("path");
const folderName = path.basename(__dirname);
const cleanName = folderName.replace(/[_\s-]/g, "");
const appId = "dev.bacon." + cleanName;
module.exports = ({ config }) => {
if (!config.extra) config.extra = {};
// Expose CI env variables to the app
config.extra.CI = process.env.CI;
if (!config.ios) config.ios = {};
config.ios.bundleIdentifier = appId;
if (!config.android) config.android = {};
config.android.package = appId;
return config;
};
-17
View File
@@ -1,17 +0,0 @@
{
"expo": {
"name": "ffmpeg",
"icon": "https://github.com/expo.png",
"splash": {
"image": "https://github.com/expo.png"
},
"plugins": [
[
"@config-plugins/ffmpeg-kit-react-native",
{
"package": "min"
}
]
]
}
}
-5
View File
@@ -1,5 +0,0 @@
import { registerRootComponent } from "expo";
import App from "./src/App";
registerRootComponent(App);
-19
View File
@@ -1,19 +0,0 @@
{
"name": "@config-plugins-app/ffmpeg-kit-react-native",
"main": "./index.js",
"version": "1.0.0",
"scripts": {
"start": "expo start --dev-client",
"ios": "expo run:ios",
"android": "expo run:android"
},
"dependencies": {
"@config-plugins/ffmpeg-kit-react-native": "*",
"expo": "~53",
"expo-splash-screen": "~0.30.7",
"ffmpeg-kit-react-native": "^6.0.2",
"react": "19.0.0",
"react-native": "0.79.2"
},
"private": true
}
-54
View File
@@ -1,54 +0,0 @@
import { Session } from "ffmpeg-kit-react-native";
import React from "react";
import {
ActivityIndicator,
SafeAreaView,
ScrollView,
Text,
View,
} from "react-native";
import { useMediaInfo, useResolvedValue } from "./useMediaInfo";
export default function App() {
const session = useMediaInfo(
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4"
);
if (!session) {
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
<ActivityIndicator />
<Text>Loading...</Text>
</View>
);
}
return <SessionInfo session={session} />;
}
function SessionInfo(props: { session: Session }) {
const { session } = props;
const duration = useResolvedValue(session.getDuration.bind(session));
const output = useResolvedValue(session.getOutput.bind(session));
return (
<SafeAreaView
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
<ScrollView>
<Text>Duration: {duration}</Text>
<Text>Output: {output}</Text>
</ScrollView>
</SafeAreaView>
);
}
@@ -1,55 +0,0 @@
import { FFprobeKit, Session } from "ffmpeg-kit-react-native";
import React from "react";
function useMounted() {
const isMounted = React.useRef(true);
React.useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
return isMounted;
}
export function useResolvedValue<T>(
method: () => Promise<T>
): [T | null, Error | null] {
const [error, setError] = React.useState<Error | null>(null);
const [value, setValue] = React.useState<T | null>(null);
React.useEffect(() => {
let isMounted = true;
method()
.then((value) => {
if (isMounted) {
setValue(value);
}
})
.catch((error) => {
if (isMounted) {
setError(error);
}
});
return () => {
isMounted = false;
};
}, []);
return [value, error];
}
export function useMediaInfo(filePathOrUrl: string) {
const [results, setResults] = React.useState<Session | undefined>(undefined);
const isMounted = useMounted();
React.useEffect(() => {
FFprobeKit.getMediaInformationAsync(filePathOrUrl, async (session) => {
if (isMounted) {
setResults(session);
}
});
}, [filePathOrUrl]);
return results;
}
+3 -3
View File
@@ -1,7 +1,7 @@
const path = require("path");
const path = require('path');
const folderName = path.basename(__dirname);
const cleanName = folderName.replace(/[_\s-]/g, "");
const appId = "dev.bacon." + cleanName;
const cleanName = folderName.replace(/[_\s-]/g, '');
const appId = 'dev.bacon.' + cleanName;
module.exports = ({ config }) => {
if (!config.extra) config.extra = {};
+1 -2
View File
@@ -25,8 +25,7 @@
"./assets/stickers/teapot.png"
]
}
],
["expo-build-properties", { "ios": { "ccacheEnabled": true } }]
]
]
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { registerRootComponent } from "expo";
import { registerRootComponent } from 'expo';
import App from "./src/App";
import App from './src/App';
registerRootComponent(App);
+7 -9
View File
@@ -1,19 +1,17 @@
{
"name": "@config-plugins-app/ios-stickers",
"main": "./index.js",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "expo start --dev-client",
"prebuild": "expo prebuild -p ios --template ./expo-template-bare-minimum-50.0.17.tgz",
"ios": "expo run:ios",
"android": "expo run:android"
},
"dependencies": {
"@config-plugins/ios-stickers": "*",
"expo": "~53",
"expo-splash-screen": "~0.30.7",
"react": "19.0.0",
"react-native": "0.79.2"
},
"private": true
"@config-plugins/ios-stickers": "workspace:*",
"expo": "~56",
"expo-splash-screen": "~56.0.10",
"react": "19.2.3",
"react-native": "0.85.3"
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
import { Text, View } from "react-native";
import { Text, View } from 'react-native';
export default function App() {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Open iMessage to find the app</Text>
</View>
);
File renamed without changes.
+3 -3
View File
@@ -1,7 +1,7 @@
const path = require("path");
const path = require('path');
const folderName = path.basename(__dirname);
const cleanName = folderName.replace(/[_\s-]/g, "");
const appId = "dev.bacon." + cleanName;
const cleanName = folderName.replace(/[_\s-]/g, '');
const appId = 'dev.bacon.' + cleanName;
module.exports = ({ config }) => {
if (!config.extra) config.extra = {};
+6 -16
View File
@@ -5,23 +5,13 @@
"splash": {
"image": "https://github.com/expo.png"
},
"ios": {
"config": {
"branch": {
"apiKey": "key_live_f9f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8"
}
}
},
"android": {
"config": {
"branch": {
"apiKey": "key_live_f9f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8"
}
}
},
"plugins": [
"@config-plugins/react-native-branch",
["expo-build-properties", { "ios": { "ccacheEnabled": true } }]
[
"@config-plugins/react-native-branch",
{
"apiKey": "key_live_f9f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8"
}
]
]
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { registerRootComponent } from "expo";
import { registerRootComponent } from 'expo';
import App from "./src/App";
import App from './src/App';
registerRootComponent(App);
+9 -9
View File
@@ -1,19 +1,19 @@
{
"name": "@config-plugins-app/react-native-branch",
"main": "./index.js",
"version": "1.0.0",
"private": true,
"main": "./index.js",
"scripts": {
"start": "expo start --dev-client",
"ios": "expo run:ios",
"android": "expo run:android"
},
"dependencies": {
"@config-plugins/react-native-branch": "*",
"expo": "~53",
"expo-splash-screen": "~0.30.7",
"react": "19.0.0",
"react-native": "0.79.2",
"react-native-branch": "^6.6.0"
},
"private": true
"@config-plugins/react-native-branch": "workspace:*",
"expo": "~56",
"expo-splash-screen": "~56.0.10",
"react": "19.2.3",
"react-native": "0.85.3",
"react-native-branch": "^6.10.0"
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
import { Text, View } from "react-native";
import { Text, View } from 'react-native';
export default function App() {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Branch</Text>
</View>
);
@@ -1,16 +0,0 @@
const path = require("path");
const folderName = path.basename(__dirname);
const cleanName = folderName.replace(/[_\s-]/g, "");
const appId = "dev.bacon." + cleanName;
module.exports = ({ config }) => {
if (!config.extra) config.extra = {};
// Expose CI env variables to the app
config.extra.CI = process.env.CI;
if (!config.ios) config.ios = {};
config.ios.bundleIdentifier = appId;
if (!config.android) config.android = {};
config.android.package = appId;
return config;
};
@@ -1,23 +0,0 @@
{
"expo": {
"name": "app-icon",
"icon": "./assets/icons/winter.png",
"platforms": ["ios"],
"ios": {
"supportsTablet": true
},
"plugins": [
[
"@config-plugins/react-native-dynamic-app-icon",
[
"./assets/icons/spring.png",
"./assets/icons/summer.png",
"./assets/icons/autumn.png",
"./assets/icons/fall.png",
"./assets/icons/solstice.png"
]
],
["expo-build-properties", { "ios": { "ccacheEnabled": true } }]
]
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

-5
View File
@@ -1,5 +0,0 @@
import { registerRootComponent } from "expo";
import App from "./src/App";
registerRootComponent(App);
@@ -1,19 +0,0 @@
{
"name": "@config-plugins-app/react-native-dynamic-app-icon",
"main": "./index.js",
"version": "1.0.0",
"scripts": {
"start": "expo start --dev-client",
"ios": "expo run:ios",
"android": "expo run:android"
},
"dependencies": {
"@config-plugins/react-native-dynamic-app-icon": "*",
"expo": "~53",
"expo-splash-screen": "~0.30.7",
"react": "19.0.0",
"react-native": "0.79.2",
"react-native-dynamic-app-icon": "^1.1.0"
},
"private": true
}
-159
View File
@@ -1,159 +0,0 @@
import Entypo from "@expo/vector-icons/Entypo";
import React from "react";
import { Image, SafeAreaView, Text, View } from "react-native";
import AppIcon from "react-native-dynamic-app-icon";
import TouchableBounce from "react-native/Libraries/Components/Touchable/TouchableBounce";
function useIconName() {
const [icon, _setIcon] = React.useState(null);
React.useEffect(() => {
let isMounted = true;
AppIcon.getIconName((result) => {
if (isMounted) _setIcon(result.iconName);
});
return () => (isMounted = false);
}, []);
const setIcon = React.useCallback(
(icon) => {
AppIcon.setAppIcon(icon);
_setIcon(icon || null);
},
[_setIcon]
);
return [icon === "default" ? null : icon, setIcon];
}
const icons = [
{
name: "Auto",
source: require("../assets/icons/winter.png"),
iconId: null,
},
{
name: "Spring",
source: require("../assets/icons/spring.png"),
iconId: "0",
},
{
name: "Summer",
source: require("../assets/icons/summer.png"),
iconId: "1",
},
{
name: "Autumn",
source: require("../assets/icons/autumn.png"),
iconId: "2",
},
{ name: "Fall", source: require("../assets/icons/fall.png"), iconId: "3" },
{
name: "Solstice",
source: require("../assets/icons/solstice.png"),
iconId: "4",
},
];
export default function App() {
const [_icon, setIcon] = useIconName();
return (
<SafeAreaView
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
<View style={{ width: "100%" }}>
{icons.map((icon, index) => {
return (
<Item
onPress={() => {
setIcon(icon.iconId);
}}
isSelected={icon.iconId === _icon}
name={icon.name}
source={icon.source}
key={String(index)}
/>
);
})}
</View>
</SafeAreaView>
);
}
function Item({ ...props }) {
return (
<TouchableBounce
onPress={props.onPress}
style={{ marginVertical: 8, marginHorizontal: 24 }}
>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "stretch",
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: props.isSelected ? 0.35 : 0.2,
shadowRadius: 8.46,
backgroundColor: props.isSelected ? "#000" : "#F1F1F1",
elevation: 9,
paddingHorizontal: 12,
paddingVertical: 12,
borderRadius: 20,
}}
>
<View
style={{
flexDirection: "row",
flex: 1,
alignItems: "center",
}}
>
<View
style={{
flexDirection: "row",
flex: 1,
alignItems: "center",
}}
>
<Image
source={props.source}
style={{
width: 64,
aspectRatio: 1,
resizeMode: "cover",
borderRadius: 16,
}}
/>
<Text
style={{
marginLeft: 20,
fontSize: 18,
fontWeight: "700",
color: props.isSelected ? "white" : "black",
}}
>
{props.name}
</Text>
</View>
{props.isSelected && (
<Entypo
style={{ marginRight: 8 }}
name="check"
size={20}
color="white"
/>
)}
</View>
</View>
</TouchableBounce>
);
}
+3 -3
View File
@@ -1,7 +1,7 @@
const path = require("path");
const path = require('path');
const folderName = path.basename(__dirname);
const cleanName = folderName.replace(/[_\s-]/g, "");
const appId = "dev.bacon." + cleanName;
const cleanName = folderName.replace(/[_\s-]/g, '');
const appId = 'dev.bacon.' + cleanName;
module.exports = ({ config }) => {
if (!config.extra) config.extra = {};
+1 -5
View File
@@ -5,10 +5,6 @@
"splash": {
"image": "https://github.com/expo.png"
},
"plugins": [
"@config-plugins/react-native-blob-util",
"@config-plugins/react-native-pdf",
["expo-build-properties", { "ios": { "ccacheEnabled": true } }]
]
"plugins": ["@config-plugins/react-native-blob-util", "@config-plugins/react-native-pdf"]
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { registerRootComponent } from "expo";
import { registerRootComponent } from 'expo';
import App from "./src/App";
import App from './src/App';
registerRootComponent(App);
+11 -11
View File
@@ -1,21 +1,21 @@
{
"name": "@config-plugins-app/react-native-pdf",
"main": "./index.js",
"version": "1.0.0",
"private": true,
"main": "./index.js",
"scripts": {
"start": "expo start --dev-client",
"ios": "expo run:ios",
"android": "expo run:android"
},
"dependencies": {
"@config-plugins/react-native-blob-util": "*",
"@config-plugins/react-native-pdf": "*",
"expo": "~53",
"expo-splash-screen": "~0.30.7",
"react": "19.0.0",
"react-native": "0.79.2",
"react-native-blob-util": "^0.21.2",
"react-native-pdf": "^6.7.7"
},
"private": true
"@config-plugins/react-native-blob-util": "workspace:*",
"@config-plugins/react-native-pdf": "workspace:*",
"expo": "~56",
"expo-splash-screen": "~56.0.10",
"react": "19.2.3",
"react-native": "0.85.3",
"react-native-blob-util": "^0.24.9",
"react-native-pdf": "^7.0.4"
}
}
+5 -5
View File
@@ -1,8 +1,8 @@
import { SafeAreaView, StyleSheet, useWindowDimensions } from "react-native";
import Pdf from "react-native-pdf";
import { SafeAreaView, StyleSheet, useWindowDimensions } from 'react-native';
import Pdf from 'react-native-pdf';
const source = {
uri: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
uri: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
cache: true,
};
export default function App() {
@@ -34,7 +34,7 @@ export default function App() {
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "flex-start",
alignItems: "center",
justifyContent: 'flex-start',
alignItems: 'center',
},
});
@@ -1,7 +1,7 @@
const path = require("path");
const path = require('path');
const folderName = path.basename(__dirname);
const cleanName = folderName.replace(/[_\s-]/g, "");
const appId = "dev.bacon." + cleanName;
const cleanName = folderName.replace(/[_\s-]/g, '');
const appId = 'dev.bacon.' + cleanName;
module.exports = ({ config }) => {
if (!config.extra) config.extra = {};
+9 -4
View File
@@ -9,9 +9,14 @@
"plugins": [
[
"@config-plugins/react-native-siri-shortcut",
["com.example.InitiateWorkout", "com.example.FinishWorkout"]
],
["expo-build-properties", { "ios": { "ccacheEnabled": true } }]
]
[
"com.github.gustash.SiriShortcutsModuleExample.sayHello",
"com.github.gustash.SiriShortcutsModuleExample.somethingElse"
]
]
],
"ios": {
"appleTeamId": "7PSDAY43Y6"
}
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { registerRootComponent } from "expo";
import { registerRootComponent } from 'expo';
import App from "./src/App";
import App from './src/App';
registerRootComponent(App);
+8 -8
View File
@@ -1,19 +1,19 @@
{
"name": "@config-plugins-app/react-native-siri-shortcut",
"main": "./index.js",
"version": "1.0.0",
"private": true,
"main": "./index.js",
"scripts": {
"start": "expo start --dev-client",
"ios": "expo run:ios",
"android": "expo run:android"
},
"dependencies": {
"@config-plugins/react-native-siri-shortcut": "*",
"expo": "~53",
"expo-splash-screen": "~0.30.7",
"react": "19.0.0",
"react-native": "0.79.2",
"@config-plugins/react-native-siri-shortcut": "workspace:*",
"expo": "~56",
"expo-splash-screen": "~56.0.10",
"react": "19.2.3",
"react-native": "0.85.3",
"react-native-siri-shortcut": "^3.2.4"
},
"private": true
}
}
+193 -227
View File
@@ -1,264 +1,230 @@
import React, { Component } from 'react';
import {
StyleSheet,
Text,
ScrollView,
Button,
SafeAreaView,
View,
EmitterSubscription,
StyleSheet,
Text,
ScrollView,
Button,
SafeAreaView,
View,
EmitterSubscription,
} from 'react-native';
import {
ShortcutOptions,
ShortcutData,
ShortcutInfo,
donateShortcut,
suggestShortcuts,
clearAllShortcuts,
clearShortcutsWithIdentifiers,
presentShortcut,
getShortcuts,
addShortcutListener,
getInitialShortcut,
AddToSiriButton,
SiriButtonStyles,
ShortcutOptions,
ShortcutData,
ShortcutInfo,
donateShortcut,
suggestShortcuts,
clearAllShortcuts,
clearShortcutsWithIdentifiers,
presentShortcut,
getShortcuts,
addShortcutListener,
getInitialShortcut,
AddToSiriButton,
SiriButtonStyles,
} from 'react-native-siri-shortcut';
const opts1: ShortcutOptions = {
activityType: 'com.github.gustash.SiriShortcutsModuleExample.sayHello',
title: 'Say Hi',
userInfo: {
foo: 1,
bar: 'baz',
baz: 34.5,
},
requiredUserInfoKeys: ['foo', 'bar', 'baz'],
keywords: ['kek', 'foo', 'bar'],
persistentIdentifier:
'com.github.gustash.SiriShortcutsModuleExample.sayHello',
isEligibleForSearch: true,
isEligibleForPrediction: true,
suggestedInvocationPhrase: 'Say something',
needsSave: true,
activityType: 'com.github.gustash.SiriShortcutsModuleExample.sayHello',
title: 'Say Hi',
userInfo: {
foo: 1,
bar: 'baz',
baz: 34.5,
},
requiredUserInfoKeys: ['foo', 'bar', 'baz'],
keywords: ['kek', 'foo', 'bar'],
persistentIdentifier: 'com.github.gustash.SiriShortcutsModuleExample.sayHello',
isEligibleForSearch: true,
isEligibleForPrediction: true,
suggestedInvocationPhrase: 'Say something',
needsSave: true,
};
const opts2: ShortcutOptions = {
activityType: 'com.github.gustash.SiriShortcutsModuleExample.somethingElse',
title: 'Something Else',
persistentIdentifier: 'some.persistent.identifier',
isEligibleForSearch: true,
isEligibleForPrediction: true,
suggestedInvocationPhrase: "What's up?",
description: 'Just a random description',
activityType: 'com.github.gustash.SiriShortcutsModuleExample.somethingElse',
title: 'Something Else',
persistentIdentifier: 'some.persistent.identifier',
isEligibleForSearch: true,
isEligibleForPrediction: true,
suggestedInvocationPhrase: "What's up?",
description: 'Just a random description',
};
type State = {
shortcutInfo: any | null,
shortcutActivityType: string | null,
addToSiriStyle: 0 | 1 | 2 | 3 | 4 | 5,
shortcuts: Array<ShortcutData>,
shortcutInfo: any | null;
shortcutActivityType: string | null;
addToSiriStyle: 0 | 1 | 2 | 3 | 4 | 5;
shortcuts: Array<ShortcutData>;
};
export default class App extends Component<void, State> {
listener: EmitterSubscription | null = null;
listener: EmitterSubscription | null = null;
state: State = {
shortcutInfo: null,
shortcutActivityType: null,
addToSiriStyle: SiriButtonStyles.blackOutline,
shortcuts: [],
};
state: State = {
shortcutInfo: null,
shortcutActivityType: null,
addToSiriStyle: SiriButtonStyles.blackOutline,
shortcuts: [],
};
componentDidMount() {
this.handleInitialShortcut();
this.listener = addShortcutListener(this.handleSiriShortcut.bind(this));
componentDidMount() {
this.handleInitialShortcut();
this.listener = addShortcutListener(this.handleSiriShortcut.bind(this));
// This will suggest these two shortcuts so that they appear
// in Settings > Siri & Search, even if they are not yet
// donated. Suitable for shortcuts that you expect the user
// may want to use. (https://developer.apple.com/documentation/sirikit/shortcut_management/suggesting_shortcuts_to_users)
suggestShortcuts([opts1, opts2]);
// This will suggest these two shortcuts so that they appear
// in Settings > Siri & Search, even if they are not yet
// donated. Suitable for shortcuts that you expect the user
// may want to use. (https://developer.apple.com/documentation/sirikit/shortcut_management/suggesting_shortcuts_to_users)
suggestShortcuts([opts1, opts2]);
this.updateShortcutList();
this.updateShortcutList();
}
componentWillUnmount() {
if (this.listener) {
this.listener.remove();
}
}
async handleInitialShortcut() {
const initialShortcut = await getInitialShortcut();
if (!initialShortcut) {
return;
}
componentWillUnmount() {
if (this.listener) {
this.listener.remove();
}
this.handleSiriShortcut(initialShortcut);
}
handleSiriShortcut({ userInfo, activityType }: ShortcutInfo) {
this.setState({
shortcutInfo: userInfo,
shortcutActivityType: activityType,
});
}
setupShortcut1() {
donateShortcut(opts1);
}
setupShortcut2() {
donateShortcut(opts2);
}
async clearShortcut1() {
try {
await clearShortcutsWithIdentifiers([
'com.github.gustash.SiriShortcutsModuleExample.sayHello',
]);
alert('Cleared Shortcut 1');
} catch (e) {
alert("You're not running iOS 12!");
}
}
async handleInitialShortcut() {
const initialShortcut = await getInitialShortcut();
if (!initialShortcut) {
return;
}
this.handleSiriShortcut(initialShortcut);
async clearShortcut2() {
try {
await clearShortcutsWithIdentifiers(['some.persistent.identifier']);
alert('Cleared Shortcut 2');
} catch (e) {
alert("You're not running iOS 12!");
}
}
handleSiriShortcut({ userInfo, activityType }: ShortcutInfo) {
this.setState({
shortcutInfo: userInfo,
shortcutActivityType: activityType,
});
async updateShortcutList() {
try {
const shortcuts = await getShortcuts();
this.setState({
shortcuts,
});
} catch (e) {
alert("You're not running iOS 12!");
}
}
setupShortcut1() {
donateShortcut(opts1);
async clearBothShortcuts() {
try {
await clearShortcutsWithIdentifiers([
'com.github.gustash.SiriShortcutsModuleExample.sayHello',
'some.persistent.identifier',
]);
alert('Cleared Both Shortcuts');
} catch (e) {
alert("You're not running iOS 12!");
}
}
setupShortcut2() {
donateShortcut(opts2);
async clearShortcuts() {
try {
await clearAllShortcuts();
alert('Deleted all the shortcuts');
} catch (e) {
alert("You're not running iOS 12!");
}
}
async clearShortcut1() {
try {
await clearShortcutsWithIdentifiers([
'com.github.gustash.SiriShortcutsModuleExample.sayHello',
]);
alert('Cleared Shortcut 1');
} catch (e) {
alert("You're not running iOS 12!");
}
}
swapSiriButtonTheme() {
const { addToSiriStyle } = this.state;
async clearShortcut2() {
try {
await clearShortcutsWithIdentifiers(['some.persistent.identifier']);
alert('Cleared Shortcut 2');
} catch (e) {
alert("You're not running iOS 12!");
}
}
const styles = Object.keys(SiriButtonStyles).map((key) => SiriButtonStyles[key]);
const index = styles.findIndex((style) => style === addToSiriStyle);
if (index === styles.length - 1) this.setState({ addToSiriStyle: styles[0] });
else this.setState({ addToSiriStyle: styles[index + 1] });
}
async updateShortcutList() {
try {
const shortcuts = await getShortcuts();
render() {
const { shortcutInfo, shortcutActivityType, addToSiriStyle, shortcuts } = this.state;
this.setState({
shortcuts,
});
} catch (e) {
alert("You're not running iOS 12!");
}
}
async clearBothShortcuts() {
try {
await clearShortcutsWithIdentifiers([
'com.github.gustash.SiriShortcutsModuleExample.sayHello',
'some.persistent.identifier',
]);
alert('Cleared Both Shortcuts');
} catch (e) {
alert("You're not running iOS 12!");
}
}
async clearShortcuts() {
try {
await clearAllShortcuts();
alert('Deleted all the shortcuts');
} catch (e) {
alert("You're not running iOS 12!");
}
}
swapSiriButtonTheme() {
const { addToSiriStyle } = this.state;
const styles = Object.keys(SiriButtonStyles).map(
key => SiriButtonStyles[key],
);
const index = styles.findIndex(style => style === addToSiriStyle);
if (index === styles.length - 1) this.setState({ addToSiriStyle: styles[0] });
else this.setState({ addToSiriStyle: styles[index + 1] });
}
render() {
const { shortcutInfo, shortcutActivityType, addToSiriStyle, shortcuts } =
this.state;
return (
<SafeAreaView style={styles.container}>
<ScrollView
style={styles.container}
contentContainerStyle={styles.contentContainer}>
<Text>Shortcut Activity Type: {shortcutActivityType || 'None'}</Text>
<Text>
Shortcut Info:{' '}
{shortcutInfo ? JSON.stringify(shortcutInfo) : 'No shortcut data.'}
</Text>
<Button
title="Create Shortcut 1"
onPress={this.setupShortcut1.bind(this)}
/>
<Button
title="Create Shortcut 2"
onPress={this.setupShortcut2.bind(this)}
/>
<Button
title="Clear Shortcut 1"
onPress={this.clearShortcut1.bind(this)}
/>
<Button
title="Clear Shortcut 2"
onPress={this.clearShortcut2.bind(this)}
/>
<Button
title="Clear Both Shortcuts"
onPress={this.clearBothShortcuts.bind(this)}
/>
<Button
title="Delete All Shortcuts"
onPress={this.clearShortcuts.bind(this)}
/>
<Button
title="Update list of shortcuts"
onPress={this.updateShortcutList.bind(this)}
/>
<AddToSiriButton
buttonStyle={addToSiriStyle}
onPress={() => {
presentShortcut(opts1, ({ status }) => {
console.log(`I was ${status}`);
});
}}
shortcut={opts1}
/>
<Button
title="Swap Siri Button Theme"
onPress={this.swapSiriButtonTheme.bind(this)}
/>
{shortcuts.length ? (
shortcuts.map(({ identifier, phrase, options }, i) => (
<View key={identifier}>
<Text>Shortcut {i + 1}:</Text>
<Text>Identifier - {identifier}</Text>
<Text>Phrase - {phrase}</Text>
<Text>Options - {JSON.stringify(options)}</Text>
</View>
))
) : (
<Text>No Shortcuts yet</Text>
)}
</ScrollView>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container}>
<ScrollView style={styles.container} contentContainerStyle={styles.contentContainer}>
<Text>Shortcut Activity Type: {shortcutActivityType || 'None'}</Text>
<Text>
Shortcut Info: {shortcutInfo ? JSON.stringify(shortcutInfo) : 'No shortcut data.'}
</Text>
<Button title="Create Shortcut 1" onPress={this.setupShortcut1.bind(this)} />
<Button title="Create Shortcut 2" onPress={this.setupShortcut2.bind(this)} />
<Button title="Clear Shortcut 1" onPress={this.clearShortcut1.bind(this)} />
<Button title="Clear Shortcut 2" onPress={this.clearShortcut2.bind(this)} />
<Button title="Clear Both Shortcuts" onPress={this.clearBothShortcuts.bind(this)} />
<Button title="Delete All Shortcuts" onPress={this.clearShortcuts.bind(this)} />
<Button title="Update list of shortcuts" onPress={this.updateShortcutList.bind(this)} />
<AddToSiriButton
buttonStyle={addToSiriStyle}
onPress={() => {
presentShortcut(opts1, ({ status }) => {
console.log(`I was ${status}`);
});
}}
shortcut={opts1}
/>
<Button title="Swap Siri Button Theme" onPress={this.swapSiriButtonTheme.bind(this)} />
{shortcuts.length ? (
shortcuts.map(({ identifier, phrase, options }, i) => (
<View key={identifier}>
<Text>Shortcut {i + 1}:</Text>
<Text>Identifier - {identifier}</Text>
<Text>Phrase - {phrase}</Text>
<Text>Options - {JSON.stringify(options)}</Text>
</View>
))
) : (
<Text>No Shortcuts yet</Text>
)}
</ScrollView>
</SafeAreaView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
contentContainer: {
justifyContent: 'center',
alignItems: 'center',
},
});
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
contentContainer: {
justifyContent: 'center',
alignItems: 'center',
},
});
+2 -2
View File
@@ -1,8 +1,8 @@
import { View, Text } from "react-native";
import { View, Text } from 'react-native';
export default function App() {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>No-op on this platform</Text>
</View>
);
+3 -3
View File
@@ -1,7 +1,7 @@
const path = require("path");
const path = require('path');
const folderName = path.basename(__dirname);
const cleanName = folderName.replace(/[_\s-]/g, "");
const appId = "dev.bacon." + cleanName;
const cleanName = folderName.replace(/[_\s-]/g, '');
const appId = 'dev.bacon.' + cleanName;
module.exports = ({ config }) => {
if (!config.extra) config.extra = {};
+1 -4
View File
@@ -5,9 +5,6 @@
"splash": {
"image": "https://github.com/expo.png"
},
"plugins": [
"@config-plugins/react-native-webrtc",
["expo-build-properties", { "ios": { "ccacheEnabled": true } }]
]
"plugins": ["@config-plugins/react-native-webrtc"]
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { registerRootComponent } from "expo";
import { registerRootComponent } from 'expo';
import App from "./src/App";
import App from './src/App';
registerRootComponent(App);
+9 -9
View File
@@ -1,19 +1,19 @@
{
"name": "@config-plugins-app/react-native-webrtc",
"main": "./index.js",
"version": "1.0.0",
"private": true,
"main": "./index.js",
"scripts": {
"start": "expo start --dev-client",
"ios": "expo run:ios",
"android": "expo run:android"
},
"dependencies": {
"@config-plugins/react-native-webrtc": "*",
"expo": "~53",
"expo-splash-screen": "~0.30.7",
"react": "19.0.0",
"react-native": "0.79.2",
"react-native-webrtc": "^124.0.5"
},
"private": true
"@config-plugins/react-native-webrtc": "workspace:*",
"expo": "~56",
"expo-splash-screen": "~56.0.10",
"react": "19.2.3",
"react-native": "0.85.3",
"react-native-webrtc": "^124.0.7"
}
}
+6 -12
View File
@@ -1,17 +1,11 @@
import { useState } from "react";
import {
Button,
SafeAreaView,
StatusBar,
StyleSheet,
View,
} from "react-native";
import { mediaDevices, RTCView } from "react-native-webrtc";
import { useState } from 'react';
import { Button, SafeAreaView, StatusBar, StyleSheet, View } from 'react-native';
import { mediaDevices, RTCView } from 'react-native-webrtc';
const App = () => {
const [stream, setStream] = useState(null);
const start = async () => {
console.log("start");
console.log('start');
if (!stream) {
let s;
try {
@@ -23,7 +17,7 @@ const App = () => {
}
};
const stop = () => {
console.log("stop");
console.log('stop');
if (stream) {
stream.release();
setStream(null);
@@ -51,7 +45,7 @@ const styles = StyleSheet.create({
flex: 1,
},
footer: {
position: "absolute",
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
+23
View File
@@ -0,0 +1,23 @@
// Shared Jest configuration for every package under packages/.
// Packages require this file from their own jest.config.js.
/** @type {import('jest').Config} */
module.exports = {
testEnvironment: 'node',
testRegex: '/__tests__/.*(test|spec)\\.[jt]sx?$',
transform: {
'^.+\\.[jt]sx?$': [
require.resolve('@swc/jest'),
{
jsc: {
parser: { syntax: 'typescript', dynamicImport: true },
target: 'es2022',
},
module: { type: 'commonjs' },
},
],
},
// Jest 29 cannot format inline snapshots with prettier 3. Skip formatting
// instead of pinning a second prettier.
prettierPath: null,
};
-8
View File
@@ -1,8 +0,0 @@
{
"npmClient": "yarn",
"registry": "https://registry.npmjs.org/",
"useWorkspaces": true,
"version": "independent",
"packages": ["packages/*"]
}
+43
View File
@@ -0,0 +1,43 @@
import { defineConfig } from 'oxlint';
import node from 'oxlint-config-universe/node';
// Shared oxlint configuration for every package under packages/ and for scripts/.
// Derived from the base config in expo/expo's @expo/internal-scripts, trimmed to
// the rules that apply to plain Node TypeScript.
export default defineConfig({
extends: [node],
// NOTE: oxlint does not inherit `ignorePatterns` through `extends`, so packages
// re-export this whole config instead of extending it.
ignorePatterns: ['**/node_modules/**', '**/build/**', '**/.expo/**'],
overrides: [
{
files: ['**/__tests__/**', '**/__mocks__/**', '**/jest/**'],
env: { jest: true, node: true },
rules: {
// Tests need to re-assign imported bindings to install mocks and spies.
'no-import-assign': 'off',
},
},
{
files: ['*.config.js', '*.config.mjs', '.*rc.js'],
env: { node: true },
},
{
files: ['**/*.ts', '**/*.d.ts'],
rules: {
// Handled by TypeScript.
'no-unused-expressions': 'off',
'no-unused-vars': 'off',
'no-useless-return': 'off',
// Enabling it blocks legitimate type overloads, which TypeScript already validates.
'no-dupe-class-members': 'off',
},
},
],
rules: {
// Handled by TypeScript.
'no-unsafe-optional-chaining': 'off',
// Kept off to match expo/expo, where it was suppressed by eslint-config-prettier.
curly: 'off',
},
});
+28 -21
View File
@@ -3,38 +3,45 @@
"version": "1.0.0",
"private": true,
"scripts": {
"postinstall": "expo-yarn-workspaces check-workspace-dependencies",
"gen": "ts-node ./scripts/generate-plugin.ts",
"update-issue-template": "ts-node ./scripts/gh-issues-config.ts",
"update-dependabot-config": "ts-node ./scripts/update-dependabot-config.ts",
"start": "lerna run build --parallel -- --watch",
"prepare": "lerna run prepare --parallel"
},
"workspaces": [
"apps/*",
"packages/*"
],
"resolutions": {
"expo": "^53"
"gen": "tsx ./scripts/generate-plugin.ts",
"update-issue-template": "tsx ./scripts/gh-issues-config.ts",
"update-dependabot-config": "tsx ./scripts/update-dependabot-config.ts",
"start": "pnpm --filter './packages/*' --parallel run build -- --watch",
"bump": "pnpm --filter './packages/*' -r exec pnpm version patch --no-git-tag-version",
"release": "pnpm publish -r --filter './packages/*'",
"prepare": "pnpm --filter './packages/*' --parallel run prepare",
"format": "oxfmt",
"lint": "oxlint --config oxlint.config.mjs scripts"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@expo/package-manager": "^1.8.4",
"@swc/core": "^1.16.18",
"@swc/jest": "^0.2.39",
"@types/jest": "^29.5.14",
"@types/js-yaml": "^4.0.5",
"@types/node": "^22.14.0",
"@types/prompts": "^2.0.13",
"@types/react": "~18.3.12",
"eslint": "^8.10.0",
"expo-module-scripts": "^4.1.6",
"expo-yarn-workspaces": "^2.3.2",
"@types/react": "^19.1.1",
"js-yaml": "^4.1.0",
"memfs": "^3.4.4",
"lerna": "3.4.1",
"prettier": "^3",
"oxfmt": "^0.73.0",
"oxlint": "^1.88.0",
"oxlint-config-universe": "^0.2.0",
"prompts": "^2.4.1",
"remark-cli": "^9.0.0",
"remark-validate-links": "^10.0.2",
"string.prototype.replaceall": "^1.0.5",
"ts-node": "^10.0.0",
"tsx": "^4.19.0",
"typescript": "^5.1.3"
},
"devEngines": {
"packageManager": {
"name": "pnpm",
"version": "12.2.1",
"onFail": "warn"
}
},
"engines": {
"pnpm": "^12.2.1"
}
}
-2
View File
@@ -1,2 +0,0 @@
// @generated by expo-module-scripts
module.exports = require('expo-module-scripts/eslintrc.base.js');
-56
View File
@@ -1,56 +0,0 @@
### @config-plugins/android-jsc-intl 5.0.0
- Expo SDK 48 support
## @config-plugins/android-jsc-intl [2.0.0](https://github.com/expo/config-plugins/compare/@config-plugins/[email protected]...@config-plugins/[email protected]) (2022-05-10)
### ⚠ BREAKING CHANGES
- **android-jsc-intl:** add SDK 45 support (#77)
### Features
- **android-jsc-intl:** add SDK 45 support ([#77](https://github.com/expo/config-plugins/issues/77)) ([aed0904](https://github.com/expo/config-plugins/commit/aed09045d61bc67b375a277b96bdc90ed5245d0f))
### @config-plugins/android-jsc-intl [1.0.4](https://github.com/expo/config-plugins/compare/@config-plugins/[email protected]...@config-plugins/[email protected]) (2022-03-23)
### Other chores
- **android-jsc-intl:** lint fix ([5ca4e44](https://github.com/expo/config-plugins/commit/5ca4e44a49ad5c3153b4a2b15acf69db594d76b8))
### @config-plugins/android-jsc-intl [1.0.3](https://github.com/expo/config-plugins/compare/@config-plugins/[email protected]...@config-plugins/[email protected]) (2022-03-22)
### Other chores
- git ignore build folders ([#59](https://github.com/expo/config-plugins/issues/59)) ([d6050be](https://github.com/expo/config-plugins/commit/d6050beb2a5c68dc59287c27ec388c2002ec7904))
### @config-plugins/android-jsc-intl [1.0.2](https://github.com/expo/config-plugins/compare/@config-plugins/[email protected]...@config-plugins/[email protected]) (2022-03-16)
### Continuous Integration
- fix versions [skip ci] ([#53](https://github.com/expo/config-plugins/issues/53)) ([5de4ae3](https://github.com/expo/config-plugins/commit/5de4ae3e6182c32b7aa24d70ccd23a11663bb089))
### @config-plugins/android-jsc-intl [1.0.1](https://github.com/expo/config-plugins/compare/@config-plugins/[email protected]...@config-plugins/[email protected]) (2022-03-15)
### Bug Fixes
- **ci:** use correct internal versions ([ab8076a](https://github.com/expo/config-plugins/commit/ab8076adf51e9ba0439eba60b153d729a0996b8d))
## @config-plugins/android-jsc-intl 1.0.0 (2022-03-15)
### Features
- Intl support for android via withAndroidJscIntl ([eb6642c](https://github.com/expo/config-plugins/commit/eb6642c0e7b37d6d9baf196ec40cc520f559e665))
### Bug Fixes
- remove createPluginOnce ([68cf1c0](https://github.com/expo/config-plugins/commit/68cf1c07b2c48249cbbd0732668ea95455636a0b))
- run expo prepare ([8d364ed](https://github.com/expo/config-plugins/commit/8d364ed99ea4b0e0e3af73dbcc780819bf55d6c2))
### Other chores
- upgrade @expo/config-plugins ([#49](https://github.com/expo/config-plugins/issues/49)) ([119e31e](https://github.com/expo/config-plugins/commit/119e31edf110409272ace750f02d651124e1a22d))
### Documentation changes
- clarify readme ([b524633](https://github.com/expo/config-plugins/commit/b5246338cb37ae41d138bc6253703b0d438fc3e3))
+2 -23
View File
@@ -1,25 +1,4 @@
# @config-plugins/android-jsc-intl
Expo Config Plugin to auto-configure Android JSC Intl when the native code is generated (`npx expo prebuild`).
Adding this plugin lets you use `Intl` in your Android app, without using Hermes.
## Expo installation
> This package cannot be used in the "Expo Go" app because [it requires custom native code](https://docs.expo.io/workflow/customizing/).
First install the package with yarn, npm, or [`npx expo install`](https://docs.expo.io/workflow/expo-cli/#expo-install).
```sh
npx expo install @config-plugins/android-jsc-intl
```
After installing this npm package, add the [config plugin](https://docs.expo.io/guides/config-plugins/) to the [`plugins`](https://docs.expo.io/versions/latest/config/app/#plugins) array of your `app.json` or `app.config.js`:
```json
{
"plugins": ["@config-plugins/android-jsc-intl"]
}
```
Next, rebuild your app as described in the ["Adding custom native code"](https://docs.expo.io/workflow/customizing/) guide.
> [!IMPORTANT]
> React Native uses the Hermes engine by default. If you're looking for a replacement, see the config plugin in [`react-native-community/jsc-android-buildscripts`](https://github.com/react-native-community/jsc-android-buildscripts/blob/dbbf3a7d2c23b25bc37af9ada1674eeb94cf83a9/test/plugins/withJscAndroid.js).
-1
View File
@@ -1 +0,0 @@
module.exports = require("./build/withAndroidJscIntl");
-32
View File
@@ -1,32 +0,0 @@
{
"name": "@config-plugins/android-jsc-intl",
"version": "11.0.0",
"description": "Config plugin for android-jsc-intl package",
"main": "build/withAndroidJscIntl.js",
"types": "build/withAndroidJscIntl.d.ts",
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/expo/config-plugins.git",
"directory": "packages/android-jsc-intl"
},
"scripts": {
"build": "expo-module build",
"clean": "expo-module clean",
"lint": "expo-module lint",
"test": "expo-module test",
"prepare": "expo-module prepare",
"prepublishOnly": "expo-module prepublishOnly",
"expo-module": "expo-module"
},
"keywords": [
"react",
"expo",
"config-plugins",
"prebuild",
"android-jsc-intl"
],
"peerDependencies": {
"expo": "^53"
}
}
@@ -1,24 +0,0 @@
import { type ConfigPlugin, withAppBuildGradle } from "expo/config-plugins";
/**
* Apply android-jsc-intl native configuration.
*
* This plugin lets you access the `Intl` API in Android apps (without Hermes).
*/
const withAndroidJscIntl: ConfigPlugin = (config) =>
withAppBuildGradle(config, (config) => {
if (config.modResults.language !== "groovy") {
throw new Error(
"[@expo/config-plugins][withAndroidJscIntl] Cannot enable Intl in Android JSC app gradle because the build.gradle is not groovy.",
);
}
config.modResults.contents = config.modResults.contents.replace(
"org.webkit:android-jsc:+",
"org.webkit:android-jsc-intl:+",
);
return config;
});
export default withAndroidJscIntl;
-10
View File
@@ -1,10 +0,0 @@
{
"extends": "expo-module-scripts/tsconfig.plugin",
"compilerOptions": {
"outDir": "build",
"rootDir": "src",
"types": ["node", "jest"]
},
"include": ["./src"],
"exclude": ["**/__mocks__/*", "**/__tests__/*"]
}
-2
View File
@@ -1,2 +0,0 @@
// @generated by expo-module-scripts
module.exports = require('expo-module-scripts/eslintrc.base.js');
+19 -16
View File
@@ -10,7 +10,10 @@ Ensure you use versions that work together!
| `expo` | `@config-plugins/apple-settings` |
| ------ | -------------------------------- |
| 53.0.0 | ^4.0.0 |
| 56.0.0 | ^8.0.0 |
| 55.0.0 | ^7.0.0 |
| 54.0.0 | ^6.0.0 |
| 53.0.0 | ^5.0.0 |
| 52.0.0 | ^3.0.0 |
| 51.0.0 | ^2.0.0 |
| 50.0.0 | ^1.0.0 |
@@ -40,7 +43,7 @@ const {
Slider,
ChildPane,
Group,
} = require("@config-plugins/apple-settings");
} = require('@config-plugins/apple-settings');
module.exports = (config) => {
return withAppleSettings(config, {
@@ -51,7 +54,7 @@ module.exports = (config) => {
// The Apple locale code. This will be used to generate the .strings file.
en: {
// Name of the localized key.
Name: "Text Field",
Name: 'Text Field',
},
},
// The page object is required. It will be used to generate the .plist file.
@@ -62,25 +65,25 @@ module.exports = (config) => {
// Helper models can be used to generate the UI elements using a syntax that's
// similar to React Native.
TextField({
title: "Name",
key: "name_preference",
value: "",
keyboardType: "Alphabet",
autoCapitalize: "None",
autoCorrect: "No",
title: 'Name',
key: 'name_preference',
value: '',
keyboardType: 'Alphabet',
autoCapitalize: 'None',
autoCorrect: 'No',
}),
Switch({
title: "Enabled",
key: "enabled_preference",
title: 'Enabled',
key: 'enabled_preference',
value: true,
}),
Slider({
key: "slider_preference",
key: 'slider_preference',
value: 0.5,
}),
// Child panes can be used to create nested pages.
ChildPane({
title: "About",
title: 'About',
}),
],
},
@@ -89,7 +92,7 @@ module.exports = (config) => {
page: {
PreferenceSpecifiers: [
Group({
title: "About Info",
title: 'About Info',
}),
],
},
@@ -114,7 +117,7 @@ const {
// Group,
// MultiValue,
// RadioGroup,
} = require("@config-plugins/apple-settings");
} = require('@config-plugins/apple-settings');
```
## Types
@@ -140,7 +143,7 @@ import {
AnyPreferenceSpecifier,
UserInterfaceIdiom,
ScalarType,
} from "@config-plugins/apple-settings";
} from '@config-plugins/apple-settings';
```
## Dynamic usage
@@ -1,5 +1,5 @@
export const generateImageAsync = jest.fn(async (input: any, { src }) => {
const fs = require("fs");
const fs = require('fs');
return { source: fs.readFileSync(src) };
});
+5 -5
View File
@@ -1,12 +1,12 @@
const path = require("path");
const path = require('path');
const roots = ["__mocks__", "src"];
const roots = ['__mocks__', 'src'];
module.exports = {
...require("expo-module-scripts/jest-preset-plugin"),
...require('../../jest.config.base.js'),
rootDir: path.resolve(__dirname),
displayName: require("./package").name,
displayName: require('./package').name,
roots,
setupFiles: ["<rootDir>/jest/setup.js"],
setupFiles: ['<rootDir>/jest/setup.js'],
clearMocks: true,
};
+2 -2
View File
@@ -1,2 +1,2 @@
jest.mock("@expo/image-utils");
jest.mock("fs");
jest.mock('@expo/image-utils');
jest.mock('fs');
@@ -0,0 +1 @@
export { default } from '../../oxlint.config.mjs';
+26 -26
View File
@@ -1,43 +1,43 @@
{
"name": "@config-plugins/apple-settings",
"version": "5.0.0",
"version": "8.0.3",
"description": "Config plugin to generate custom Apple settings UI",
"license": "MIT",
"main": "build/index.js",
"types": "build/index.d.ts",
"sideEffects": false,
"files": [
"build"
"keywords": [
"expo",
"react"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/expo/config-plugins.git",
"directory": "packages/apple-settings"
},
"scripts": {
"build": "expo-module build",
"clean": "expo-module clean",
"lint": "expo-module lint",
"test": "expo-module test",
"generate-types": "json2ts -i ./src/schema/SettingsPlist.json -o ./src/schema/SettingsPlist.ts --additionalProperties=false --unknownAny=false",
"prepare": "expo-module prepare && yarn generate-types",
"prepublishOnly": "expo-module prepublishOnly",
"expo-module": "expo-module"
},
"keywords": [
"react",
"expo"
"files": [
"build"
],
"sideEffects": false,
"main": "build/index.js",
"types": "build/index.d.ts",
"scripts": {
"build": "tsc",
"clean": "rm -rf build",
"lint": "oxlint --config oxlint.config.mjs .",
"test": "jest",
"generate-types": "json2ts -i ./src/schema/SettingsPlist.json -o ./src/schema/SettingsPlist.ts --additionalProperties=false --unknownAny=false",
"prepare": "pnpm generate-types",
"prepublishOnly": "pnpm generate-types && pnpm run clean && pnpm run build"
},
"dependencies": {
"@bacons/xcode": "^1.0.0-alpha.12",
"@expo/plist": "^0.3.4",
"@expo/plist": "catalog:",
"iconv-lite": "^0.6.3",
"schema-utils": "^4.2.0"
},
"peerDependencies": {
"expo": "^53"
"schema-utils": "catalog:"
},
"devDependencies": {
"json-schema-to-typescript": "^13.0.2"
"jest": "catalog:",
"json-schema-to-typescript": "^16.0.0"
},
"peerDependencies": {
"expo": "catalog:"
}
}
@@ -1,15 +1,15 @@
// Ports https://github.com/iteufel/node-strings-file/blob/master/index.js to TyeScript and ESM:
import fs from "fs";
import iconv from "iconv-lite";
import fs from 'fs';
import iconv from 'iconv-lite';
type ParsedStrings = {
[key: string]: string | { value: string; comment: string };
};
export function parse(data: string, wantComments?: boolean): ParsedStrings {
if (data.indexOf("\n") === -1) {
data += "\n";
if (data.indexOf('\n') === -1) {
data += '\n';
}
const re = /(?:\/\*(.+)\*\/\n)?(.+)\s*=\s*"(.+)";\n/gim;
const res: ParsedStrings = {};
@@ -19,30 +19,32 @@ export function parse(data: string, wantComments?: boolean): ParsedStrings {
if (m.index === re.lastIndex) {
re.lastIndex++;
}
if (m[2].substring(0, 1) === '"') {
m[2] = m[2].trim().slice(1, -1);
let key = m[2]!;
const value = m[3]!;
if (key.substring(0, 1) === '"') {
key = key.trim().slice(1, -1);
}
if (wantComments) {
res[m[2]] = {
value: unescapeString(m[3]),
comment: m[1] || "",
res[key] = {
value: unescapeString(value),
comment: m[1] || '',
};
} else {
res[m[2]] = unescapeString(m[3]);
res[key] = unescapeString(value);
}
}
return res;
}
export function build(obj: ParsedStrings): string {
let data = "";
let data = '';
for (const i in obj) {
if (typeof obj[i] === "object") {
if (obj[i]["comment"] && obj[i]["comment"].length > 0) {
data += `\n/*${obj[i]["comment"]}*/\n`;
if (typeof obj[i] === 'object') {
if (obj[i]['comment'] && obj[i]['comment'].length > 0) {
data += `\n/*${obj[i]['comment']}*/\n`;
}
data += `"${i}" = "${escapeString(obj[i]["value"])}";\n`;
} else if (typeof obj[i] === "string") {
data += `"${i}" = "${escapeString(obj[i]['value'])}";\n`;
} else if (typeof obj[i] === 'string') {
data += `\n"${i}" = "${escapeString(obj[i])}";\n`;
}
}
@@ -59,13 +61,9 @@ function unescapeString(str: string): string {
export async function openAsync(file: string, wantComments?: boolean) {
const data = await fs.promises.readFile(file);
return parse(iconv.decode(data, "utf-16"), wantComments);
return parse(iconv.decode(data, 'utf-16'), wantComments);
}
export function writeAsync(filename: string, data: ParsedStrings) {
return fs.promises.writeFile(
filename,
iconv.encode(build(data), "utf-16"),
"binary",
);
return fs.promises.writeFile(filename, iconv.encode(build(data), 'utf-16'), 'binary');
}
@@ -1,27 +1,24 @@
import * as plist from "@expo/plist";
import * as plist from '@expo/plist';
import {
BaseMods,
type ConfigPlugin,
createRunOncePlugin,
type Mod,
withMod,
} from "expo/config-plugins";
import * as fs from "fs";
import path from "path";
import { validate } from "schema-utils";
} from 'expo/config-plugins';
import * as fs from 'fs';
import path from 'path';
import { validate } from 'schema-utils';
import { SettingsPlist } from "../schema/SettingsPlist";
import schema from "../schema/SettingsPlist.json";
import { SettingsPlist } from '../schema/SettingsPlist';
import schema from '../schema/SettingsPlist.json';
export function createModSetForSettingsPage({ name }: { name: string }) {
const customModName = "settings" + name + "Plist";
const customModName = 'settings' + name + 'Plist';
const withSettingsPlist: ConfigPlugin<Mod<SettingsPlist>> = (
config,
action,
) => {
const withSettingsPlist: ConfigPlugin<Mod<SettingsPlist>> = (config, action) => {
return withMod(config, {
platform: "ios",
platform: 'ios',
mod: customModName,
action,
});
@@ -29,7 +26,7 @@ export function createModSetForSettingsPage({ name }: { name: string }) {
const withBaseModInternal: ConfigPlugin = (config) => {
return BaseMods.withGeneratedBaseMods(config, {
platform: "ios",
platform: 'ios',
saveToInternal: true,
skipEmptyMod: false,
providers: {
@@ -40,7 +37,7 @@ export function createModSetForSettingsPage({ name }: { name: string }) {
return path.join(
modRequest.platformProjectRoot,
modRequest.projectName!,
`Settings.bundle/${name}.plist`,
`Settings.bundle/${name}.plist`
);
},
async read(filePath) {
@@ -50,12 +47,10 @@ export function createModSetForSettingsPage({ name }: { name: string }) {
PreferenceSpecifiers: [],
};
}
return plist.default.parse(
await fs.promises.readFile(filePath, "utf-8"),
);
return plist.default.parse(await fs.promises.readFile(filePath, 'utf-8'));
} catch (error: any) {
throw new Error(
`Failed to parse the iOS Settings.bundle/${name}.plist: "${filePath}". ${error.message}}`,
`Failed to parse the iOS Settings.bundle/${name}.plist: "${filePath}". ${error.message}}`
);
}
},
@@ -4,28 +4,28 @@ import {
BaseMods,
createRunOncePlugin,
withMod,
} from "expo/config-plugins";
import * as fs from "fs";
import path from "path";
} from 'expo/config-plugins';
import * as fs from 'fs';
import path from 'path';
import * as strings from "./strings";
import * as strings from './strings';
export type SettingsStrings = Record<string, string>;
export function createModSetForSettingsStrings({
name,
lang = "en",
lang = 'en',
}: {
/** Like `Root` */
name: string;
/** Like `en` */
lang?: string;
}) {
const customModName = "settings" + lang + "_" + name + "Strings";
const customModName = 'settings' + lang + '_' + name + 'Strings';
const withBaseModInternal: ConfigPlugin = (config) => {
return BaseMods.withGeneratedBaseMods(config, {
platform: "ios",
platform: 'ios',
saveToInternal: true,
skipEmptyMod: false,
providers: {
@@ -35,7 +35,7 @@ export function createModSetForSettingsStrings({
return path.join(
modRequest.platformProjectRoot,
modRequest.projectName!,
`Settings.bundle/${lang}.lproj/${name}.strings`,
`Settings.bundle/${lang}.lproj/${name}.strings`
);
},
async read(filePath) {
@@ -43,13 +43,10 @@ export function createModSetForSettingsStrings({
if (fs.existsSync(filePath) === false) {
return {};
}
return strings.openAsync(filePath, false) as unknown as Record<
string,
string
>;
return strings.openAsync(filePath, false) as unknown as Record<string, string>;
} catch (error: any) {
throw new Error(
`Failed to parse the Settings.bundle/${lang}.lproj/${name}.strings: "${filePath}". ${error.message}}`,
`Failed to parse the Settings.bundle/${lang}.lproj/${name}.strings: "${filePath}". ${error.message}}`
);
}
},
@@ -70,7 +67,7 @@ export function createModSetForSettingsStrings({
const withStrings: ConfigPlugin<Mod<SettingsStrings>> = (config, action) => {
return withMod(config, {
platform: "ios",
platform: 'ios',
mod: customModName,
action,
});
@@ -1,5 +1,5 @@
import { XcodeProject } from "@bacons/xcode";
import * as xcodeParse from "@bacons/xcode/json";
import { XcodeProject } from '@bacons/xcode';
import * as xcodeParse from '@bacons/xcode/json';
import {
type Mod,
BaseMods,
@@ -7,17 +7,14 @@ import {
createRunOncePlugin,
IOSConfig,
withMod,
} from "expo/config-plugins";
import * as fs from "fs";
} from 'expo/config-plugins';
import * as fs from 'fs';
const customModName = "xcodeProjectBeta-apple-settings";
const customModName = 'xcodeProjectBeta-apple-settings';
export const withXcodeProjectBeta: ConfigPlugin<Mod<XcodeProject>> = (
config,
action,
) => {
export const withXcodeProjectBeta: ConfigPlugin<Mod<XcodeProject>> = (config, action) => {
return withMod(config, {
platform: "ios",
platform: 'ios',
mod: customModName,
action,
});
@@ -25,7 +22,7 @@ export const withXcodeProjectBeta: ConfigPlugin<Mod<XcodeProject>> = (
const withXcodeProjectBetaBaseModInternal: ConfigPlugin = (config) => {
return BaseMods.withGeneratedBaseMods(config, {
platform: "ios",
platform: 'ios',
saveToInternal: true,
skipEmptyMod: false,
providers: {
@@ -40,9 +37,7 @@ const withXcodeProjectBetaBaseModInternal: ConfigPlugin = (config) => {
try {
return XcodeProject.open(filePath);
} catch (error: any) {
throw new Error(
`Failed to parse the Xcode project: "${filePath}". ${error.message}}`,
);
throw new Error(`Failed to parse the Xcode project: "${filePath}". ${error.message}}`);
}
},
async write(filePath, { modResults, modRequest: { introspect } }) {
@@ -61,5 +56,5 @@ const withXcodeProjectBetaBaseModInternal: ConfigPlugin = (config) => {
export const withXcodeProjectBetaBaseMod = createRunOncePlugin(
withXcodeProjectBetaBaseModInternal,
"withXcodeProjectBetaBaseMod",
'withXcodeProjectBetaBaseMod'
);
+4 -4
View File
@@ -1,4 +1,4 @@
import { withStaticSettings, StaticSettings, AppleLocale } from "./static";
import { withStaticSettings, StaticSettings, AppleLocale } from './static';
export {
ChildPane,
@@ -9,10 +9,10 @@ export {
TextField,
Title,
MultiValue,
} from "./models";
} from './models';
export { StaticSettings, AppleLocale };
export type { StaticSettings, AppleLocale };
export * from "./schema/SettingsPlist";
export * from './schema/SettingsPlist';
export default withStaticSettings;
Loaded 100 of 248 files, more files were not shown because too many files have changed in this diff. Show more