From 286a3cad47c3fd1673428521aba89b76e6eb2b19 Mon Sep 17 00:00:00 2001 From: Gauvain Date: Tue, 30 Jun 2026 09:03:47 +0200 Subject: [PATCH] refactor: migrate app.config and Expo config plugins to TypeScript (#1718) --- .github/workflows/build-apps.yml | 2 +- app.config.js => app.config.ts | 20 ++++--- app.json | 24 ++++---- bun.lock | 57 +++++++++++++++++++ docs/tv-discovery.md | 2 +- package.json | 1 + ...s.js => with-runtime-framework-headers.ts} | 8 ++- ...ertColors.js => withAndroidAlertColors.ts} | 24 +++++--- ...roidManifest.js => withAndroidManifest.ts} | 12 ++-- ... => withChangeNativeAndroidTextToWhite.ts} | 10 ++-- ...Media3Dash.js => withExcludeMedia3Dash.ts} | 6 +- plugins/{withGitPod.js => withGitPod.ts} | 14 ++++- ...eProperties.js => withGradleProperties.ts} | 19 +++++-- ...{withTVOSAppIcon.js => withTVOSAppIcon.ts} | 6 +- ...ithTVOSTopShelf.js => withTVOSTopShelf.ts} | 55 +++++++++++------- ...rManagement.js => withTVUserManagement.ts} | 6 +- .../{withTVXcodeEnv.js => withTVXcodeEnv.ts} | 14 ++--- ...stLocalCerts.js => withTrustLocalCerts.ts} | 23 ++++++-- utils/version.ts | 2 +- 19 files changed, 216 insertions(+), 89 deletions(-) rename app.config.js => app.config.ts (76%) rename plugins/{with-runtime-framework-headers.js => with-runtime-framework-headers.ts} (96%) rename plugins/{withAndroidAlertColors.js => withAndroidAlertColors.ts} (60%) rename plugins/{withAndroidManifest.js => withAndroidManifest.ts} (77%) rename plugins/{withChangeNativeAndroidTextToWhite.js => withChangeNativeAndroidTextToWhite.ts} (68%) rename plugins/{withExcludeMedia3Dash.js => withExcludeMedia3Dash.ts} (85%) rename plugins/{withGitPod.js => withGitPod.ts} (63%) rename plugins/{withGradleProperties.js => withGradleProperties.ts} (67%) rename plugins/{withTVOSAppIcon.js => withTVOSAppIcon.ts} (81%) rename plugins/{withTVOSTopShelf.js => withTVOSTopShelf.ts} (80%) rename plugins/{withTVUserManagement.js => withTVUserManagement.ts} (81%) rename plugins/{withTVXcodeEnv.js => withTVXcodeEnv.ts} (91%) rename plugins/{withTrustLocalCerts.js => withTrustLocalCerts.ts} (70%) diff --git a/.github/workflows/build-apps.yml b/.github/workflows/build-apps.yml index 15a7b03a..580dc46d 100644 --- a/.github/workflows/build-apps.yml +++ b/.github/workflows/build-apps.yml @@ -11,7 +11,7 @@ on: push: branches: [develop, master] -# Exposed to `expo prebuild` (app.config.js → extra.build) so Settings can show the +# Exposed to `expo prebuild` (app.config.ts → extra.build) so Settings can show the # branch + commit + Actions run a CI build was made from. EAS cloud builds use # EAS_BUILD_* instead. The run number maps a sideloaded build back to its Actions # run (artifacts + logs) without needing Expo access. diff --git a/app.config.js b/app.config.ts similarity index 76% rename from app.config.js rename to app.config.ts index d29ddc32..ac2b433a 100644 --- a/app.config.js +++ b/app.config.ts @@ -1,9 +1,13 @@ -const { execFileSync } = require("node:child_process"); +// Registers the tsx require hook so the TypeScript config plugins referenced +// from app.json ("./plugins/*.ts") can be loaded by Node during config evaluation. +import "tsx/cjs"; +import { execFileSync } from "node:child_process"; +import type { ConfigContext, ExpoConfig } from "expo/config"; // Build metadata, injected into `extra.build` and read at runtime via // expo-constants (see utils/version.ts). Sources in priority order: // EAS cloud build → GitHub Actions → explicit EXPO_PUBLIC_* → local git → null. -const git = (args) => { +const git = (args: string[]): string | null => { try { return execFileSync("git", args, { stdio: ["ignore", "pipe", "ignore"] }) .toString() @@ -42,16 +46,16 @@ const buildMeta = { builtAt: new Date().toISOString(), }; -module.exports = ({ config }) => { +export default ({ config }: ConfigContext): ExpoConfig => { if (process.env.EXPO_TV !== "1") { - config.plugins.push("expo-background-task"); + config.plugins?.push("expo-background-task"); - config.plugins.push([ + config.plugins?.push([ "react-native-google-cast", { useDefaultExpandedMediaControls: true }, ]); - config.plugins.push([ + config.plugins?.push([ "expo-camera", { cameraPermission: @@ -61,7 +65,7 @@ module.exports = ({ config }) => { } // Only override googleServicesFile if env var is set - const androidConfig = {}; + const androidConfig: { googleServicesFile?: string } = {}; if (process.env.GOOGLE_SERVICES_JSON) { androidConfig.googleServicesFile = process.env.GOOGLE_SERVICES_JSON; } @@ -71,5 +75,5 @@ module.exports = ({ config }) => { return { ...(Object.keys(androidConfig).length > 0 && { android: androidConfig }), ...config, - }; + } as ExpoConfig; }; diff --git a/app.json b/app.json index e7095490..42efc22c 100644 --- a/app.json +++ b/app.json @@ -71,8 +71,8 @@ ], "expo-router", "expo-font", - "./plugins/withExcludeMedia3Dash.js", - "./plugins/withTVUserManagement.js", + "./plugins/withExcludeMedia3Dash.ts", + "./plugins/withTVUserManagement.ts", [ "expo-build-properties", { @@ -134,17 +134,17 @@ } ], "expo-web-browser", - ["./plugins/with-runtime-framework-headers.js"], - ["./plugins/withChangeNativeAndroidTextToWhite.js"], - ["./plugins/withAndroidAlertColors.js"], - ["./plugins/withAndroidManifest.js"], - ["./plugins/withTrustLocalCerts.js"], - ["./plugins/withGradleProperties.js"], - ["./plugins/withTVOSAppIcon.js"], - ["./plugins/withTVOSTopShelf.js"], - ["./plugins/withTVXcodeEnv.js"], + ["./plugins/with-runtime-framework-headers.ts"], + ["./plugins/withChangeNativeAndroidTextToWhite.ts"], + ["./plugins/withAndroidAlertColors.ts"], + ["./plugins/withAndroidManifest.ts"], + ["./plugins/withTrustLocalCerts.ts"], + ["./plugins/withGradleProperties.ts"], + ["./plugins/withTVOSAppIcon.ts"], + ["./plugins/withTVOSTopShelf.ts"], + ["./plugins/withTVXcodeEnv.ts"], [ - "./plugins/withGitPod.js", + "./plugins/withGitPod.ts", { "podName": "MPVKit", "podspecUrl": "https://raw.githubusercontent.com/mpv-ios/MPVKit/0.41.0-av/MPVKit.podspec" diff --git a/bun.lock b/bun.lock index 112ab885..86fff885 100644 --- a/bun.lock +++ b/bun.lock @@ -113,6 +113,7 @@ "husky": "9.1.7", "lint-staged": "17.0.8", "react-test-renderer": "19.2.3", + "tsx": "^4.22.4", "typescript": "6.0.3", }, }, @@ -294,6 +295,58 @@ "@epic-web/invariant": ["@epic-web/invariant@1.0.0", "", {}, "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], + "@expo-google-fonts/material-symbols": ["@expo-google-fonts/material-symbols@0.4.38", "", {}, "sha512-IJkBtN1o8u9BW5fvSii1MyHPQ7Q0HxbWcVBvOrOzgMLpVtZw7R2w94wBTVR7kZwv3w1JNTESMmLA5Sqn1+Z36A=="], "@expo/cli": ["@expo/cli@56.1.16", "", { "dependencies": { "@expo/code-signing-certificates": "^0.0.6", "@expo/config": "~56.0.9", "@expo/config-plugins": "~56.0.9", "@expo/devcert": "^1.2.1", "@expo/env": "~2.3.0", "@expo/image-utils": "^0.10.1", "@expo/inline-modules": "^0.0.12", "@expo/json-file": "^10.2.0", "@expo/log-box": "^56.0.13", "@expo/metro": "~56.0.0", "@expo/metro-config": "~56.0.14", "@expo/metro-file-map": "^56.0.3", "@expo/osascript": "^2.6.0", "@expo/package-manager": "^1.12.1", "@expo/plist": "^0.7.0", "@expo/prebuild-config": "^56.0.16", "@expo/require-utils": "^56.1.3", "@expo/router-server": "^56.0.14", "@expo/schema-utils": "^56.0.0", "@expo/spawn-async": "^1.8.0", "@expo/ws-tunnel": "^2.0.0", "@expo/xcpretty": "^4.4.4", "@react-native/dev-middleware": "0.85.3", "accepts": "^1.3.8", "arg": "^5.0.2", "bplist-creator": "0.1.0", "bplist-parser": "^0.3.1", "chalk": "^4.0.0", "ci-info": "^3.3.0", "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", "dnssd-advertise": "^1.1.4", "expo-server": "^56.0.5", "fetch-nodeshim": "^0.4.10", "getenv": "^2.0.0", "glob": "^13.0.0", "lan-network": "^0.2.1", "multitars": "^1.0.0", "node-forge": "^1.3.3", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "picomatch": "^4.0.4", "pretty-format": "^29.7.0", "progress": "^2.0.3", "prompts": "^2.3.2", "resolve-from": "^5.0.0", "semver": "^7.6.0", "send": "^0.19.0", "slugify": "^1.3.4", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "terminal-link": "^2.1.1", "toqr": "^0.1.1", "wrap-ansi": "^7.0.0", "ws": "^8.12.1", "zod": "^3.25.76" }, "peerDependencies": { "expo": "*", "expo-router": "*", "react-native": "*" }, "optionalPeers": ["expo-router", "react-native"], "bin": { "expo-internal": "main.js" } }, "sha512-VBQn0mqAwc67b9Cn0RVXyeodghomAx5xGRhA/bXaQzuxDjMQk0zIOb6pXMZX7yiIwJW66UZt/zQiJNSv6aWJYw=="], @@ -908,6 +961,8 @@ "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + "esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], @@ -1808,6 +1863,8 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tsx": ["tsx@4.22.4", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg=="], + "type-fest": ["type-fest@0.7.1", "", {}, "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg=="], "type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="], diff --git a/docs/tv-discovery.md b/docs/tv-discovery.md index b1a55165..c2bc67eb 100644 --- a/docs/tv-discovery.md +++ b/docs/tv-discovery.md @@ -28,7 +28,7 @@ Apple TV uses a Top Shelf extension target, not the main app process. Relevant files: -- [plugins/withTVOSTopShelf.js](../plugins/withTVOSTopShelf.js) +- [plugins/withTVOSTopShelf.ts](../plugins/withTVOSTopShelf.ts) - [targets/StreamyfinTopShelf/TopShelfProvider.swift](../targets/StreamyfinTopShelf/TopShelfProvider.swift) - [modules/top-shelf-cache/ios/TopShelfCacheModule.swift](../modules/top-shelf-cache/ios/TopShelfCacheModule.swift) - [utils/topshelf/cache.ts](../utils/topshelf/cache.ts) diff --git a/package.json b/package.json index 6cdba17b..e4e65831 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "husky": "9.1.7", "lint-staged": "17.0.8", "react-test-renderer": "19.2.3", + "tsx": "^4.22.4", "typescript": "6.0.3" }, "expo": { diff --git a/plugins/with-runtime-framework-headers.js b/plugins/with-runtime-framework-headers.ts similarity index 96% rename from plugins/with-runtime-framework-headers.js rename to plugins/with-runtime-framework-headers.ts index 8405239b..43e34046 100644 --- a/plugins/with-runtime-framework-headers.js +++ b/plugins/with-runtime-framework-headers.ts @@ -1,4 +1,4 @@ -const { withPodfile } = require("expo/config-plugins"); +import { type ConfigPlugin, withPodfile } from "expo/config-plugins"; const PATCH_START = "## >>> runtime-framework headers"; const PATCH_END = "## <<< runtime-framework headers"; @@ -13,7 +13,7 @@ const EXTRA_HDRS = [ `\${PODS_CONFIGURATION_BUILD_DIR}/React-rendererconsistency/React_rendererconsistency.framework/Headers`, ]; -function buildPatch() { +function buildPatch(): string { return [ PATCH_START, " extra_hdrs = [", @@ -91,7 +91,7 @@ function buildPatch() { ].join("\n"); } -module.exports = function withRuntimeFrameworkHeaders(config) { +const withRuntimeFrameworkHeaders: ConfigPlugin = (config) => { return withPodfile(config, (config) => { let podfile = config.modResults.contents; @@ -125,3 +125,5 @@ end return config; }); }; + +export default withRuntimeFrameworkHeaders; diff --git a/plugins/withAndroidAlertColors.js b/plugins/withAndroidAlertColors.ts similarity index 60% rename from plugins/withAndroidAlertColors.js rename to plugins/withAndroidAlertColors.ts index c0570047..829b9a54 100644 --- a/plugins/withAndroidAlertColors.js +++ b/plugins/withAndroidAlertColors.ts @@ -1,10 +1,20 @@ -const { +import { + type ConfigPlugin, withAndroidColors, withAndroidColorsNight, -} = require("expo/config-plugins"); +} from "expo/config-plugins"; -const withAndroidAlertColors = (config) => { - const setColor = (colorsList, name, value) => { +interface ColorResourceItem { + $: { name: string }; + _: string; +} + +const withAndroidAlertColors: ConfigPlugin = (config) => { + const setColor = ( + colorsList: ColorResourceItem[], + name: string, + value: string, + ) => { const existingColor = colorsList.find( (item) => item.$ && item.$.name === name, ); @@ -20,7 +30,7 @@ const withAndroidAlertColors = (config) => { config = withAndroidColors(config, (config) => { const colors = config.modResults; - const colorsList = colors.resources.color || []; + const colorsList = (colors.resources.color ?? []) as ColorResourceItem[]; setColor(colorsList, "colorPrimary", "#000000"); colors.resources.color = colorsList; return config; @@ -28,7 +38,7 @@ const withAndroidAlertColors = (config) => { config = withAndroidColorsNight(config, (config) => { const colors = config.modResults; - const colorsList = colors.resources.color || []; + const colorsList = (colors.resources.color ?? []) as ColorResourceItem[]; setColor(colorsList, "colorPrimary", "#FFFFFF"); colors.resources.color = colorsList; return config; @@ -37,4 +47,4 @@ const withAndroidAlertColors = (config) => { return config; }; -module.exports = withAndroidAlertColors; +export default withAndroidAlertColors; diff --git a/plugins/withAndroidManifest.js b/plugins/withAndroidManifest.ts similarity index 77% rename from plugins/withAndroidManifest.js rename to plugins/withAndroidManifest.ts index 883869fb..7d5a9e62 100644 --- a/plugins/withAndroidManifest.js +++ b/plugins/withAndroidManifest.ts @@ -1,8 +1,12 @@ -const { withAndroidManifest } = require("expo/config-plugins"); +import { type ConfigPlugin, withAndroidManifest } from "expo/config-plugins"; -const _withGoogleCastAndroidManifest = (config) => +const withGoogleCastAndroidManifest: ConfigPlugin = (config) => withAndroidManifest(config, async (mod) => { - const mainApplication = mod.modResults.manifest.application[0]; + const mainApplication = mod.modResults.manifest.application?.[0]; + + if (!mainApplication) { + return mod; + } // Initialize activity array if it doesn't exist if (!mainApplication.activity) { @@ -39,4 +43,4 @@ const _withGoogleCastAndroidManifest = (config) => return mod; }); -module.exports = _withGoogleCastAndroidManifest; +export default withGoogleCastAndroidManifest; diff --git a/plugins/withChangeNativeAndroidTextToWhite.js b/plugins/withChangeNativeAndroidTextToWhite.ts similarity index 68% rename from plugins/withChangeNativeAndroidTextToWhite.js rename to plugins/withChangeNativeAndroidTextToWhite.ts index efdb782b..1ae4108a 100644 --- a/plugins/withChangeNativeAndroidTextToWhite.js +++ b/plugins/withChangeNativeAndroidTextToWhite.ts @@ -1,8 +1,8 @@ -const { readFileSync, writeFileSync } = require("node:fs"); -const { join } = require("node:path"); -const { withDangerousMod } = require("expo/config-plugins"); +import { readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { type ConfigPlugin, withDangerousMod } from "expo/config-plugins"; -const withChangeNativeAndroidTextToWhite = (expoConfig) => +const withChangeNativeAndroidTextToWhite: ConfigPlugin = (expoConfig) => withDangerousMod(expoConfig, [ "android", (modConfig) => { @@ -30,4 +30,4 @@ const withChangeNativeAndroidTextToWhite = (expoConfig) => }, ]); -module.exports = withChangeNativeAndroidTextToWhite; +export default withChangeNativeAndroidTextToWhite; diff --git a/plugins/withExcludeMedia3Dash.js b/plugins/withExcludeMedia3Dash.ts similarity index 85% rename from plugins/withExcludeMedia3Dash.js rename to plugins/withExcludeMedia3Dash.ts index 8884cccf..e6b93656 100644 --- a/plugins/withExcludeMedia3Dash.js +++ b/plugins/withExcludeMedia3Dash.ts @@ -1,6 +1,6 @@ -const { withAppBuildGradle } = require("expo/config-plugins"); +import { type ConfigPlugin, withAppBuildGradle } from "expo/config-plugins"; -module.exports = function withExcludeMedia3Dash(config) { +const withExcludeMedia3Dash: ConfigPlugin = (config) => { return withAppBuildGradle(config, (config) => { const contents = config.modResults.contents; @@ -32,3 +32,5 @@ configurations.all { return config; }); }; + +export default withExcludeMedia3Dash; diff --git a/plugins/withGitPod.js b/plugins/withGitPod.ts similarity index 63% rename from plugins/withGitPod.js rename to plugins/withGitPod.ts index dc046e8a..5d5aab73 100644 --- a/plugins/withGitPod.js +++ b/plugins/withGitPod.ts @@ -1,6 +1,14 @@ -const { withPodfile } = require("@expo/config-plugins"); +import { type ConfigPlugin, withPodfile } from "expo/config-plugins"; -const withGitPod = (config, { podName, podspecUrl }) => { +interface GitPodOptions { + podName: string; + podspecUrl: string; +} + +const withGitPod: ConfigPlugin = ( + config, + { podName, podspecUrl }, +) => { return withPodfile(config, (config) => { const podfile = config.modResults.contents; @@ -21,4 +29,4 @@ const withGitPod = (config, { podName, podspecUrl }) => { }); }; -module.exports = withGitPod; +export default withGitPod; diff --git a/plugins/withGradleProperties.js b/plugins/withGradleProperties.ts similarity index 67% rename from plugins/withGradleProperties.js rename to plugins/withGradleProperties.ts index 57c37be1..0cfa5a05 100644 --- a/plugins/withGradleProperties.js +++ b/plugins/withGradleProperties.ts @@ -1,12 +1,21 @@ -const { withGradleProperties } = require("expo/config-plugins"); +import type { ExpoConfig } from "expo/config"; +import { + AndroidConfig, + type ConfigPlugin, + withGradleProperties, +} from "expo/config-plugins"; -function setGradlePropertiesValue(config, key, value) { +function setGradlePropertiesValue( + config: ExpoConfig, + key: string, + value: string, +): ExpoConfig { return withGradleProperties(config, (exportedConfig) => { const props = exportedConfig.modResults; const keyIdx = props.findIndex( (item) => item.type === "property" && item.key === key, ); - const property = { + const property: AndroidConfig.Properties.PropertiesItem = { type: "property", key, value, @@ -22,7 +31,7 @@ function setGradlePropertiesValue(config, key, value) { }); } -module.exports = function withCustomPlugin(config) { +const withCustomGradleProperties: ConfigPlugin = (config) => { // Expo 52 is not setting this // https://github.com/expo/expo/issues/32558 config = setGradlePropertiesValue(config, "android.enableJetifier", "true"); @@ -38,3 +47,5 @@ module.exports = function withCustomPlugin(config) { ); return config; }; + +export default withCustomGradleProperties; diff --git a/plugins/withTVOSAppIcon.js b/plugins/withTVOSAppIcon.ts similarity index 81% rename from plugins/withTVOSAppIcon.js rename to plugins/withTVOSAppIcon.ts index 50114eb6..1fadbac2 100644 --- a/plugins/withTVOSAppIcon.js +++ b/plugins/withTVOSAppIcon.ts @@ -1,6 +1,6 @@ -const { withXcodeProject } = require("@expo/config-plugins"); +import { type ConfigPlugin, withXcodeProject } from "expo/config-plugins"; -const withTVOSAppIcon = (config) => { +const withTVOSAppIcon: ConfigPlugin = (config) => { // Only apply for TV builds if (process.env.EXPO_TV !== "1") { return config; @@ -28,4 +28,4 @@ const withTVOSAppIcon = (config) => { }); }; -module.exports = withTVOSAppIcon; +export default withTVOSAppIcon; diff --git a/plugins/withTVOSTopShelf.js b/plugins/withTVOSTopShelf.ts similarity index 80% rename from plugins/withTVOSTopShelf.js rename to plugins/withTVOSTopShelf.ts index 56610fcf..5fcd1374 100644 --- a/plugins/withTVOSTopShelf.js +++ b/plugins/withTVOSTopShelf.ts @@ -1,8 +1,10 @@ -const { +import type { ExpoConfig } from "expo/config"; +import { + type ConfigPlugin, withEntitlementsPlist, withInfoPlist, withXcodeProject, -} = require("@expo/config-plugins"); +} from "expo/config-plugins"; const EXTENSION_TARGET_NAME = "StreamyfinTopShelf"; const TARGET_SOURCE_DIR = "../targets/StreamyfinTopShelf"; @@ -10,19 +12,29 @@ const APP_GROUP_INFO_PLIST_KEY = "StreamyfinAppGroupIdentifier"; const KEYCHAIN_ACCESS_GROUP_INFO_PLIST_KEY = "StreamyfinKeychainAccessGroupIdentifier"; -function getBundleIdentifier(config) { +interface AppExtensionConfig { + targetName: string; + bundleIdentifier: string; + entitlements: { + "com.apple.security.application-groups": string[]; + "keychain-access-groups": string[]; + }; +} + +function getBundleIdentifier(config: ExpoConfig): string { return config.ios?.bundleIdentifier || "com.fredrikburmester.streamyfin"; } -function getAppGroupIdentifier(config) { +function getAppGroupIdentifier(config: ExpoConfig): string { return `group.${getBundleIdentifier(config)}.tvtopshelf`; } -function getKeychainAccessGroupIdentifier(config) { +function getKeychainAccessGroupIdentifier(config: ExpoConfig): string { return `$(AppIdentifierPrefix)${getBundleIdentifier(config)}`; } -function getBuildConfigurations(project, configurationListId) { +// The xcode project object has no usable typings — keep `any` here. +function getBuildConfigurations(project: any, configurationListId: string) { const configurationList = project.hash.project.objects.XCConfigurationList[configurationListId]; @@ -30,18 +42,21 @@ function getBuildConfigurations(project, configurationListId) { const buildConfigurations = project.pbxXCBuildConfigurationSection(); return configurationList.buildConfigurations - .map((config) => buildConfigurations[config.value]) + .map((config: { value: string }) => buildConfigurations[config.value]) .filter(Boolean); } -function ensureAppGroup(value, appGroupIdentifier) { +function ensureAppGroup(value: unknown, appGroupIdentifier: string): string[] { const groups = Array.isArray(value) ? value : []; return groups.includes(appGroupIdentifier) ? groups : [...groups, appGroupIdentifier]; } -function ensureKeychainAccessGroup(value, keychainAccessGroupIdentifier) { +function ensureKeychainAccessGroup( + value: unknown, + keychainAccessGroupIdentifier: string, +): string[] { const groups = Array.isArray(value) ? value : []; return groups.includes(keychainAccessGroupIdentifier) ? groups @@ -49,13 +64,13 @@ function ensureKeychainAccessGroup(value, keychainAccessGroupIdentifier) { } function ensureAppExtension( - appExtensions, - targetName, - bundleIdentifier, - appGroupIdentifier, - keychainAccessGroupIdentifier, -) { - const extensionConfig = { + appExtensions: unknown, + targetName: string, + bundleIdentifier: string, + appGroupIdentifier: string, + keychainAccessGroupIdentifier: string, +): AppExtensionConfig[] { + const extensionConfig: AppExtensionConfig = { targetName, bundleIdentifier, entitlements: { @@ -63,7 +78,9 @@ function ensureAppExtension( "keychain-access-groups": [keychainAccessGroupIdentifier], }, }; - const extensions = Array.isArray(appExtensions) ? appExtensions : []; + const extensions: AppExtensionConfig[] = Array.isArray(appExtensions) + ? appExtensions + : []; // Keep plugin runs idempotent and preserve unrelated app extension entries. const existingIndex = extensions.findIndex( (appExtension) => appExtension?.targetName === targetName, @@ -78,7 +95,7 @@ function ensureAppExtension( ); } -const withTVOSTopShelf = (config) => { +const withTVOSTopShelf: ConfigPlugin = (config) => { const appGroupIdentifier = getAppGroupIdentifier(config); const keychainAccessGroupIdentifier = getKeychainAccessGroupIdentifier(config); @@ -193,4 +210,4 @@ const withTVOSTopShelf = (config) => { }); }; -module.exports = withTVOSTopShelf; +export default withTVOSTopShelf; diff --git a/plugins/withTVUserManagement.js b/plugins/withTVUserManagement.ts similarity index 81% rename from plugins/withTVUserManagement.js rename to plugins/withTVUserManagement.ts index 651ee738..f69f47b1 100644 --- a/plugins/withTVUserManagement.js +++ b/plugins/withTVUserManagement.ts @@ -1,9 +1,9 @@ -const { withEntitlementsPlist } = require("expo/config-plugins"); +import { type ConfigPlugin, withEntitlementsPlist } from "expo/config-plugins"; /** * Expo config plugin to add User Management entitlement for tvOS profile linking */ -const withTVUserManagement = (config) => { +const withTVUserManagement: ConfigPlugin = (config) => { // Only add for tvOS builds. The entitlement is restricted by Apple and must // be present in the provisioning profile, so injecting it into mobile builds // breaks signing ("Entitlement ... not found and could not be included in @@ -24,4 +24,4 @@ const withTVUserManagement = (config) => { }); }; -module.exports = withTVUserManagement; +export default withTVUserManagement; diff --git a/plugins/withTVXcodeEnv.js b/plugins/withTVXcodeEnv.ts similarity index 91% rename from plugins/withTVXcodeEnv.js rename to plugins/withTVXcodeEnv.ts index 86f36755..dd26e8f0 100644 --- a/plugins/withTVXcodeEnv.js +++ b/plugins/withTVXcodeEnv.ts @@ -1,7 +1,7 @@ -const { withDangerousMod } = require("@expo/config-plugins"); -const { execSync } = require("node:child_process"); -const fs = require("node:fs"); -const path = require("node:path"); +import { execSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import { type ConfigPlugin, withDangerousMod } from "expo/config-plugins"; /** * Expo config plugin that adds EXPO_TV=1 and NODE_BINARY to .xcode.env.local for TV builds. @@ -12,7 +12,7 @@ const path = require("node:path"); * * It also sets NODE_BINARY for nvm users since Xcode can't resolve shell functions. */ -const withTVXcodeEnv = (config) => { +const withTVXcodeEnv: ConfigPlugin = (config) => { // Only apply for TV builds if (process.env.EXPO_TV !== "1") { return config; @@ -70,7 +70,7 @@ const withTVXcodeEnv = (config) => { /** * Get the actual node binary path, handling nvm installations. */ -function getNodeBinaryPath() { +function getNodeBinaryPath(): string | null { try { // First try to get node path directly (works for non-nvm installs) const directPath = execSync("which node 2>/dev/null", { @@ -114,4 +114,4 @@ function getNodeBinaryPath() { return null; } -module.exports = withTVXcodeEnv; +export default withTVXcodeEnv; diff --git a/plugins/withTrustLocalCerts.js b/plugins/withTrustLocalCerts.ts similarity index 70% rename from plugins/withTrustLocalCerts.js rename to plugins/withTrustLocalCerts.ts index 20e902e4..c9e69c38 100644 --- a/plugins/withTrustLocalCerts.js +++ b/plugins/withTrustLocalCerts.ts @@ -1,18 +1,29 @@ -const { AndroidConfig, withAndroidManifest } = require("expo/config-plugins"); -const path = require("node:path"); -const fs = require("node:fs"); +import fs from "node:fs"; +import path from "node:path"; +import { + AndroidConfig, + type ConfigPlugin, + type ExportedConfigWithProps, + withAndroidManifest, +} from "expo/config-plugins"; + const fsPromises = fs.promises; const { getMainApplicationOrThrow } = AndroidConfig.Manifest; -const withTrustLocalCerts = (config) => { +type AndroidManifest = AndroidConfig.Manifest.AndroidManifest; + +const withTrustLocalCerts: ConfigPlugin = (config) => { return withAndroidManifest(config, async (mod) => { mod.modResults = await setCustomConfigAsync(mod, mod.modResults); return mod; }); }; -async function setCustomConfigAsync(config, androidManifest) { +async function setCustomConfigAsync( + config: ExportedConfigWithProps, + androidManifest: AndroidManifest, +): Promise { const src_file_path = path.join(__dirname, "network_security_config.xml"); const res_file_path = path.join( await AndroidConfig.Paths.getResourceFolderAsync( @@ -45,4 +56,4 @@ async function setCustomConfigAsync(config, androidManifest) { return androidManifest; } -module.exports = withTrustLocalCerts; +export default withTrustLocalCerts; diff --git a/utils/version.ts b/utils/version.ts index 812665c4..ade6025a 100644 --- a/utils/version.ts +++ b/utils/version.ts @@ -5,7 +5,7 @@ import Constants from "expo-constants"; * clientInfo auto-tracks the app version instead of a hardcoded string. */ export const APP_VERSION = Application.nativeApplicationVersion ?? "unknown"; -/** Build metadata injected at build time by `app.config.js` into `extra.build`. */ +/** Build metadata injected at build time by `app.config.ts` into `extra.build`. */ export interface BuildMeta { commit?: string | null; branch?: string | null;