expo-constants 58.0.2 → 58.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,18 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 58.0.4 — 2026-09-21
14
+
15
+ ### 🎉 New features
16
+
17
+ - Embed the project fingerprint (`app.fingerprint`) in debug builds, next to the embedded app config. ([#49905](https://github.com/expo/expo/pull/49905) by [@vonovak](https://github.com/vonovak))
18
+
19
+ ## 58.0.3 — 2026-09-16
20
+
21
+ ### 💡 Others
22
+
23
+ - [iOS] Ship `PrivacyInfo.xcprivacy` from the `EXConstants` target so apps built with Swift Package Manager carry the privacy manifest, as CocoaPods builds already do. ([#49823](https://github.com/expo/expo/pull/49823) by [@chrfalch](https://github.com/chrfalch))
24
+
13
25
  ## 58.0.2 — 2026-09-15
14
26
 
15
27
  _This version does not introduce any user-facing changes._
package/Package.swift CHANGED
@@ -21,7 +21,11 @@ let package = Package(
21
21
  .target(
22
22
  name: "EXConstants",
23
23
  dependencies: [],
24
- path: "ios/EXConstants"
24
+ path: "ios/EXConstants",
25
+ // `.copy`, not `.process`: Apple reads a privacy manifest by its exact name, and
26
+ // .process may rename or transform what it puts in the bundle. The file stays in
27
+ // ios/ so the podspec keeps shipping it as the ExpoConstants_privacy bundle.
28
+ resources: [.copy("../PrivacyInfo.xcprivacy")]
25
29
  ),
26
30
  .target(
27
31
  name: "EXConstantsObjC",
@@ -6,7 +6,7 @@ plugins {
6
6
  apply from: "../scripts/get-app-config-android.gradle"
7
7
 
8
8
  group = 'host.exp.exponent'
9
- version = '58.0.2'
9
+ version = '58.0.4'
10
10
 
11
11
  expoModule {
12
12
  // We can't prebuild the module because we need to apply `get-app-config-android.gradle` script.
@@ -17,6 +17,6 @@ android {
17
17
  namespace "expo.modules.constants"
18
18
  defaultConfig {
19
19
  versionCode 33
20
- versionName "58.0.2"
20
+ versionName "58.0.4"
21
21
  }
22
22
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-constants",
3
- "version": "58.0.2",
3
+ "version": "58.0.4",
4
4
  "description": "Provides system information that remains constant throughout the lifetime of your app.",
5
5
  "keywords": [
6
6
  "constants",
@@ -47,19 +47,20 @@
47
47
  ]
48
48
  },
49
49
  "dependencies": {
50
- "@expo/env": "~2.5.0"
50
+ "@expo/env": "~2.5.0",
51
+ "@expo/require-utils": "^58.0.1"
51
52
  },
52
53
  "devDependencies": {
53
54
  "@types/node": "^22.14.0",
54
55
  "expo-manifests": "58.0.0",
55
56
  "expo-module-scripts": "56.0.3",
56
- "expo-updates": "58.0.4"
57
+ "expo-updates": "58.0.6"
57
58
  },
58
59
  "peerDependencies": {
59
60
  "expo": "*",
60
61
  "react-native": "*"
61
62
  },
62
- "gitHead": "313e67f5c9dfb16f9deca74a271cd1d4f33a6f82",
63
+ "gitHead": "ebb1fd7569c9c9e172cb9abd73dfede027c1d674",
63
64
  "scripts": {
64
65
  "build": "expo-build src",
65
66
  "clean": "expo-module clean",
@@ -0,0 +1,110 @@
1
+ // @ts-check
2
+ /// <reference types="node" />
3
+
4
+ const { resolveFrom } = require('@expo/require-utils');
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+
8
+ const { resolveProjectRoot } = require('./resolveProjectRoot');
9
+
10
+ const FINGERPRINT_FILE_NAME = 'app.fingerprint';
11
+
12
+ /**
13
+ * Write the project fingerprint to `app.fingerprint`, next to `app.config`.
14
+ *
15
+ * The options must match the reader's (`src/project/fingerprint.ts` in expo/expo-agent-cli).
16
+ * Never rejects: the fingerprint is optional metadata, so a failure is warned and the build goes on.
17
+ *
18
+ * @param {string} projectRoot
19
+ * @param {string} destinationDir
20
+ * @param {string | undefined} platform
21
+ * @param {boolean} enabled false for non-debug builds; the stale file is still removed
22
+ * @returns {Promise<string | null>} path of the written file, or null when skipped or failed
23
+ */
24
+ async function createFingerprintFileAsync(projectRoot, destinationDir, platform, enabled) {
25
+ try {
26
+ return await writeFingerprintFileAsync(projectRoot, destinationDir, platform, enabled);
27
+ } catch (error) {
28
+ warnFingerprintEmbedFailed(/** @type {Error} */ (error));
29
+ return null;
30
+ }
31
+ }
32
+
33
+ /** @type {typeof createFingerprintFileAsync} */
34
+ async function writeFingerprintFileAsync(projectRoot, destinationDir, platform, enabled) {
35
+ // No platform means the caller is not managing fingerprints — `@expo/cli`'s rebundle path asks
36
+ // for the app config alone — and clearing the file would strip it out of an app already built.
37
+ if (platform === undefined) {
38
+ return null;
39
+ }
40
+
41
+ // Cleared before the opt-out tests, not after: the iOS destination directory persists across
42
+ // builds, so a release build or an opted-out one has to remove what a debug build left.
43
+ const filePath = path.join(destinationDir, FINGERPRINT_FILE_NAME);
44
+ await fs.promises.rm(filePath, { force: true });
45
+
46
+ const skip =
47
+ !enabled || isFingerprintEmbeddingDisabled() || (platform !== 'ios' && platform !== 'android');
48
+ if (skip) {
49
+ return null;
50
+ }
51
+
52
+ // From the project root: a module-relative require could load another copy in a hoisted monorepo.
53
+ const fingerprintPath = resolveFrom(projectRoot, 'expo/fingerprint');
54
+ if (!fingerprintPath) {
55
+ return null;
56
+ }
57
+ const Fingerprint = require(fingerprintPath);
58
+
59
+ const fingerprint = await Fingerprint.createFingerprintAsync(projectRoot, {
60
+ platforms: [platform],
61
+ silent: true,
62
+ });
63
+ if (!fingerprint?.hash) {
64
+ return null;
65
+ }
66
+
67
+ const contents = { ...fingerprint, fingerprintVersion: readFingerprintVersion(fingerprintPath) };
68
+ await fs.promises.writeFile(filePath, JSON.stringify(contents));
69
+ return filePath;
70
+ }
71
+
72
+ /** @param {string} fingerprintPath */
73
+ function readFingerprintVersion(fingerprintPath) {
74
+ const packagePath = resolveFrom(path.dirname(fingerprintPath), '@expo/fingerprint/package.json');
75
+ if (!packagePath) {
76
+ return null;
77
+ }
78
+ try {
79
+ return require(packagePath).version ?? null;
80
+ } catch {
81
+ return null;
82
+ }
83
+ }
84
+
85
+ function isFingerprintEmbeddingDisabled() {
86
+ const value = process.env.EXPO_SKIP_FINGERPRINT_EMBED;
87
+ return value != null && !['0', 'false', ''].includes(value.toLowerCase());
88
+ }
89
+
90
+ /** @param {Error} error */
91
+ function warnFingerprintEmbedFailed(error) {
92
+ console.warn(
93
+ `Could not embed the project fingerprint (app.fingerprint): ${error.message}. ` +
94
+ `The build continues normally, but \`npx @expo/agent-cli status --explain\` cannot tell whether this build matches the project until a build with an embedded fingerprint succeeds.`
95
+ );
96
+ }
97
+
98
+ module.exports = {
99
+ createFingerprintFileAsync,
100
+ FINGERPRINT_FILE_NAME,
101
+ };
102
+
103
+ // Direct invocation from the Android build, whose task only exists for debuggable variants — hence
104
+ // the hardcoded `enabled`.
105
+ if (require.main === module) {
106
+ (async () => {
107
+ const projectRoot = resolveProjectRoot(process.argv[2] ?? process.cwd());
108
+ await createFingerprintFileAsync(projectRoot, process.argv[3], process.argv[4], true);
109
+ })();
110
+ }
@@ -37,13 +37,47 @@ afterEvaluate {
37
37
  // Switch to project root and generate the app config
38
38
  workingDir projectRoot
39
39
 
40
+ // The last argument turns the fingerprint embed off here: the task below owns it.
40
41
  if (Os.isFamily(Os.FAMILY_WINDOWS)) {
41
- commandLine("cmd", "/c", *nodeExecutableAndArgs, "$expoConstantsDir/scripts/getAppConfig.js", projectRoot, assetsDir)
42
+ commandLine("cmd", "/c", *nodeExecutableAndArgs, "$expoConstantsDir/scripts/getAppConfig.js", projectRoot, assetsDir, "android", "false")
42
43
  } else {
43
- commandLine(*nodeExecutableAndArgs, "$expoConstantsDir/scripts/getAppConfig.js", projectRoot, assetsDir)
44
+ commandLine(*nodeExecutableAndArgs, "$expoConstantsDir/scripts/getAppConfig.js", projectRoot, assetsDir, "android", "false")
44
45
  }
45
46
  }
46
47
 
47
48
  // Generate app.config at preBuild
48
49
  tasks.getByName('preBuild').dependsOn(currentCreateConfigTask)
50
+
51
+ def fingerprintAssetsDir = file("$buildDir/generated/assets/expo-constants-fingerprint")
52
+
53
+ // A second task because `createExpoConfig` publishes to `sourceSets.main` off the single
54
+ // `preBuild`, so its output reaches release too. The fingerprint must not.
55
+ def createFingerprintTask = tasks.register('createExpoFingerprint', Exec) {
56
+ description = 'expo-constants: Embed the project fingerprint (app.fingerprint) in debug builds.'
57
+
58
+ doFirst {
59
+ fingerprintAssetsDir.deleteDir()
60
+ fingerprintAssetsDir.mkdirs()
61
+ }
62
+
63
+ // The fingerprint hashes sources across the whole project, so always regenerate it
64
+ outputs.upToDateWhen { false }
65
+ outputs.dir fingerprintAssetsDir
66
+
67
+ workingDir projectRoot
68
+
69
+ if (Os.isFamily(Os.FAMILY_WINDOWS)) {
70
+ commandLine("cmd", "/c", *nodeExecutableAndArgs, "$expoConstantsDir/scripts/createFingerprintFile.js", projectRoot, fingerprintAssetsDir, "android")
71
+ } else {
72
+ commandLine(*nodeExecutableAndArgs, "$expoConstantsDir/scripts/createFingerprintFile.js", projectRoot, fingerprintAssetsDir, "android")
73
+ }
74
+ }
75
+
76
+ project.android.libraryVariants.all { variant ->
77
+ if (!variant.buildType.debuggable) {
78
+ return
79
+ }
80
+ variant.preBuildProvider.configure { it.dependsOn(createFingerprintTask) }
81
+ project.android.sourceSets.maybeCreate(variant.buildType.name).assets.srcDirs += fingerprintAssetsDir
82
+ }
49
83
  }
@@ -32,4 +32,8 @@ else
32
32
  exit 1
33
33
  fi
34
34
 
35
- "${EXPO_CONSTANTS_PACKAGE_DIR}/scripts/with-node.sh" "${EXPO_CONSTANTS_PACKAGE_DIR}/scripts/getAppConfig.js" "$PROJECT_ROOT" "$RESOURCE_DEST"
35
+ # Debug builds only, by the same configuration test expo-updates uses. The `|| echo` keeps a
36
+ # release build from tripping `set -e`.
37
+ EMBED_FINGERPRINT=$([[ "$CONFIGURATION" == *Debug* ]] && echo true || echo false)
38
+
39
+ "${EXPO_CONSTANTS_PACKAGE_DIR}/scripts/with-node.sh" "${EXPO_CONSTANTS_PACKAGE_DIR}/scripts/getAppConfig.js" "$PROJECT_ROOT" "$RESOURCE_DEST" "ios" "$EMBED_FINGERPRINT"
@@ -5,29 +5,29 @@ const { getConfig } = require('expo/config');
5
5
  const fs = require('node:fs');
6
6
  const path = require('node:path');
7
7
 
8
+ const { createFingerprintFileAsync } = require('./createFingerprintFile');
9
+ const { resolveProjectRoot } = require('./resolveProjectRoot');
10
+
8
11
  const cwd = process.cwd();
9
12
  const possibleProjectRoot = process.argv[2] ?? cwd;
10
13
  const destinationDir = process.argv[3] ?? cwd;
14
+ const platform = process.argv[4];
15
+ // The native build scripts pass 'true' for debug builds only.
16
+ const embedFingerprint = process.argv[5] === 'true';
17
+
18
+ (async () => {
19
+ const projectRoot = resolveProjectRoot(possibleProjectRoot);
20
+
21
+ require('@expo/env').load(projectRoot);
22
+ process.chdir(projectRoot);
23
+
24
+ const { exp } = getConfig(projectRoot, {
25
+ isPublicConfig: true,
26
+ skipSDKVersionRequirement: true,
27
+ });
28
+
29
+ fs.writeFileSync(path.join(destinationDir, 'app.config'), JSON.stringify(exp));
11
30
 
12
- // TODO: Verify we can remove projectRoot validation, now that we no longer
13
- // support React Native <= 62
14
- let projectRoot;
15
- if (fs.existsSync(path.join(possibleProjectRoot, 'package.json'))) {
16
- projectRoot = possibleProjectRoot;
17
- } else if (fs.existsSync(path.join(possibleProjectRoot, '..', 'package.json'))) {
18
- projectRoot = path.resolve(possibleProjectRoot, '..');
19
- } else {
20
- throw new Error(
21
- `Unable to locate project (no package.json found) at path: ${possibleProjectRoot}`
22
- );
23
- }
24
-
25
- require('@expo/env').load(projectRoot);
26
- process.chdir(projectRoot);
27
-
28
- const { exp } = getConfig(projectRoot, {
29
- isPublicConfig: true,
30
- skipSDKVersionRequirement: true,
31
- });
32
-
33
- fs.writeFileSync(path.join(destinationDir, 'app.config'), JSON.stringify(exp));
31
+ // Only the fingerprint is optional. A failure above this line must still fail the build.
32
+ await createFingerprintFileAsync(projectRoot, destinationDir, platform, embedFingerprint);
33
+ })();
@@ -0,0 +1,32 @@
1
+ // @ts-check
2
+ /// <reference types="node" />
3
+
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+
7
+ // TODO: Verify we can remove projectRoot validation, now that we no longer
8
+ // support React Native <= 62
9
+ /**
10
+ * Resolve the app root from the app directory or from `<project>/android`, which is what gradle
11
+ * passes as `rootProject.projectDir`.
12
+ *
13
+ * @param {string} possibleProjectRoot
14
+ * @returns {string}
15
+ */
16
+ function resolveProjectRoot(possibleProjectRoot) {
17
+ const resolvedProjectRoot = path.resolve(possibleProjectRoot);
18
+ if (fs.existsSync(path.join(resolvedProjectRoot, 'package.json'))) {
19
+ return resolvedProjectRoot;
20
+ }
21
+
22
+ const parentProjectRoot = path.dirname(resolvedProjectRoot);
23
+ if (fs.existsSync(path.join(parentProjectRoot, 'package.json'))) {
24
+ return parentProjectRoot;
25
+ }
26
+
27
+ throw new Error(
28
+ `Unable to locate project (no package.json found) at path: ${possibleProjectRoot}`
29
+ );
30
+ }
31
+
32
+ module.exports = { resolveProjectRoot };
package/.npmignore DELETED
@@ -1,24 +0,0 @@
1
- # @generated by expo-module-scripts
2
-
3
- # Exclude all top-level hidden directories by convention
4
- /.*/
5
-
6
- # Exclude tarballs generated by `npm pack`
7
- /*.tgz
8
-
9
- __mocks__
10
- __tests__
11
- __rsc_tests__
12
-
13
- /e2e
14
- /e2e-cli
15
- /babel.config.js
16
- /oxlint.config.mjs
17
- /android/src/androidTest/
18
- /android/src/test/
19
- /ios/Tests
20
-
21
- # Exclude native build output (pnpm pack ignores nested .gitignore files)
22
- .cxx/
23
- .gradle/
24
- /android/build/
package/.tsbuildinfo DELETED
@@ -1 +0,0 @@
1
- {"version":"6.0.3"}
@@ -1,261 +0,0 @@
1
-
2
- # Created by https://www.gitignore.io/api/java,maven,gradle,android,intellij,androidstudio
3
-
4
- ### Android ###
5
- # Built application files
6
- *.apk
7
- *.ap_
8
-
9
- # Files for the ART/Dalvik VM
10
- *.dex
11
-
12
- # Java class files
13
- *.class
14
-
15
- # Generated files
16
- bin/
17
- gen/
18
- out/
19
-
20
- # Gradle files
21
- .gradle/
22
- build/
23
-
24
- # Local configuration file (sdk path, etc)
25
- local.properties
26
-
27
- # Proguard folder generated by Eclipse
28
- proguard/
29
-
30
- # Log Files
31
- *.log
32
-
33
- # Android Studio Navigation editor temp files
34
- .navigation/
35
-
36
- # Android Studio captures folder
37
- captures/
38
-
39
- # Intellij
40
- *.iml
41
- .idea/workspace.xml
42
- .idea/tasks.xml
43
- .idea/gradle.xml
44
- .idea/dictionaries
45
- .idea/libraries
46
-
47
- # External native build folder generated in Android Studio 2.2 and later
48
- .externalNativeBuild
49
-
50
- # Freeline
51
- freeline.py
52
- freeline/
53
- freeline_project_description.json
54
-
55
- ### Android Patch ###
56
- gen-external-apklibs
57
-
58
- ### AndroidStudio ###
59
- # Covers files to be ignored for android development using Android Studio.
60
-
61
- # Built application files
62
-
63
- # Files for the ART/Dalvik VM
64
-
65
- # Java class files
66
-
67
- # Generated files
68
-
69
- # Gradle files
70
- .gradle
71
-
72
- # Signing files
73
- .signing/
74
-
75
- # Local configuration file (sdk path, etc)
76
-
77
- # Proguard folder generated by Eclipse
78
-
79
- # Log Files
80
-
81
- # Android Studio
82
- /*/build/
83
- /*/local.properties
84
- /*/out
85
- /*/*/build
86
- /*/*/production
87
- *.ipr
88
- *~
89
- *.swp
90
-
91
- # Android Patch
92
-
93
- # External native build folder generated in Android Studio 2.2 and later
94
-
95
- # NDK
96
- obj/
97
-
98
- # IntelliJ IDEA
99
- *.iws
100
- /out/
101
-
102
- # User-specific configurations
103
- .idea/libraries/
104
- .idea/.name
105
- .idea/compiler.xml
106
- .idea/copyright/profiles_settings.xml
107
- .idea/encodings.xml
108
- .idea/misc.xml
109
- .idea/modules.xml
110
- .idea/scopes/scope_settings.xml
111
- .idea/vcs.xml
112
- .idea/jsLibraryMappings.xml
113
- .idea/datasources.xml
114
- .idea/dataSources.ids
115
- .idea/sqlDataSources.xml
116
- .idea/dynamic.xml
117
- .idea/uiDesigner.xml
118
-
119
- # OS-specific files
120
- .DS_Store
121
- .DS_Store?
122
- ._*
123
- .Spotlight-V100
124
- .Trashes
125
- ehthumbs.db
126
- Thumbs.db
127
-
128
- # Legacy Eclipse project files
129
- .classpath
130
- .project
131
- .cproject
132
- .settings/
133
-
134
- # Mobile Tools for Java (J2ME)
135
- .mtj.tmp/
136
-
137
- # Package Files #
138
- *.war
139
- *.ear
140
-
141
- # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
142
- hs_err_pid*
143
-
144
- ## Plugin-specific files:
145
-
146
- # mpeltonen/sbt-idea plugin
147
- .idea_modules/
148
-
149
- # JIRA plugin
150
- atlassian-ide-plugin.xml
151
-
152
- # Mongo Explorer plugin
153
- .idea/mongoSettings.xml
154
-
155
- ### AndroidStudio Patch ###
156
-
157
- !/gradle/wrapper/gradle-wrapper.jar
158
-
159
- ### Intellij ###
160
- # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
161
- # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
162
-
163
- # User-specific stuff:
164
- .idea/**/workspace.xml
165
- .idea/**/tasks.xml
166
-
167
- # Sensitive or high-churn files:
168
- .idea/**/dataSources/
169
- .idea/**/dataSources.ids
170
- .idea/**/dataSources.xml
171
- .idea/**/dataSources.local.xml
172
- .idea/**/sqlDataSources.xml
173
- .idea/**/dynamic.xml
174
- .idea/**/uiDesigner.xml
175
-
176
- # Gradle:
177
- .idea/**/gradle.xml
178
- .idea/**/libraries
179
-
180
- # CMake
181
- cmake-build-debug/
182
-
183
- # Mongo Explorer plugin:
184
- .idea/**/mongoSettings.xml
185
-
186
- ## File-based project format:
187
-
188
- ## Plugin-specific files:
189
-
190
- # IntelliJ
191
-
192
- # mpeltonen/sbt-idea plugin
193
-
194
- # JIRA plugin
195
-
196
- # Cursive Clojure plugin
197
- .idea/replstate.xml
198
-
199
- # Ruby plugin and RubyMine
200
- /.rakeTasks
201
-
202
- ### Intellij Patch ###
203
- # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
204
-
205
- # *.iml
206
- # modules.xml
207
- # .idea/misc.xml
208
- # *.ipr
209
-
210
- # Sonarlint plugin
211
- .idea/sonarlint
212
-
213
- ### Java ###
214
- # Compiled class file
215
-
216
- # Log file
217
-
218
- # BlueJ files
219
- *.ctxt
220
-
221
- # Mobile Tools for Java (J2ME)
222
-
223
- # Package Files #
224
- *.jar
225
- *.zip
226
- *.tar.gz
227
- *.rar
228
-
229
- # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
230
-
231
- ### Maven ###
232
- target/
233
- pom.xml.tag
234
- pom.xml.releaseBackup
235
- pom.xml.versionsBackup
236
- pom.xml.next
237
- release.properties
238
- dependency-reduced-pom.xml
239
- buildNumber.properties
240
- .mvn/timing.properties
241
-
242
- # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored)
243
- !/.mvn/wrapper/maven-wrapper.jar
244
-
245
- ### Gradle ###
246
- **/build/
247
-
248
- # Ignore Gradle GUI config
249
- gradle-app.setting
250
-
251
- # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
252
- !gradle-wrapper.jar
253
-
254
- # Cache of project
255
- .gradletasknamecache
256
-
257
- # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
258
- # gradle/wrapper/gradle-wrapper.properties
259
-
260
-
261
- # End of https://www.gitignore.io/api/java,maven,gradle,android,intellij,androidstudio
package/jest.config.js DELETED
@@ -1,3 +0,0 @@
1
- module.exports = require('expo-module-scripts/createCompositeJestPreset')(__dirname, [], {
2
- rsc: true,
3
- });
@@ -1 +0,0 @@
1
- {"root":["./src/getappconfig.ts"],"version":"5.9.2"}
package/tsconfig.json DELETED
@@ -1,9 +0,0 @@
1
- // @generated by expo-module-scripts
2
- {
3
- "extends": "expo-module-scripts/tsconfig.base",
4
- "compilerOptions": {
5
- "rootDir": "./src",
6
- "noEmit": true
7
- },
8
- "include": ["./src"]
9
- }