@forthtilliath/expo-config-plugins 0.2.0 → 0.3.0
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/README.md +17 -1
- package/dist/transforms.d.ts +19 -0
- package/dist/transforms.d.ts.map +1 -1
- package/dist/transforms.js +33 -0
- package/dist/withReleaseRemovedPermissions.d.ts +17 -0
- package/dist/withReleaseRemovedPermissions.d.ts.map +1 -0
- package/dist/withReleaseRemovedPermissions.js +43 -0
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -68,9 +68,25 @@ Some native modules (e.g. `expo-dynamic-app-icon`) compile their Kotlin code for
|
|
|
68
68
|
| ----------- | -------- | ------- |
|
|
69
69
|
| `jvmTarget` | `string` | `"17"` |
|
|
70
70
|
|
|
71
|
+
## `withReleaseRemovedPermissions` — a release APK that can't go online
|
|
72
|
+
|
|
73
|
+
For an app that works fully offline (personal, health or intimate data), removing the `INTERNET` permission from the **release** APK makes Android itself guarantee that nothing can leave the phone — whatever a dependency (an SDK, a library's telemetry) tries. Debug builds keep the permission: Metro needs the network.
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
"@forthtilliath/expo-config-plugins/withReleaseRemovedPermissions"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The plugin writes `android/app/src/release/AndroidManifest.xml`, merged by Gradle over the main manifest and every library's, with `tools:node="remove"` on each permission: it's dropped whoever declared it. It refuses to overwrite a release manifest it didn't generate, and fails the prebuild on anything but permission names (they end up in XML). Check the result with `aapt dump permissions app-release.apk`.
|
|
80
|
+
|
|
81
|
+
| Option | Type | Default | Notes |
|
|
82
|
+
| ------------- | ---------- | -------------- | ----------------------------------------------------------------------------------------- |
|
|
83
|
+
| `permissions` | `string[]` | `["INTERNET"]` | Short (`ACCESS_NETWORK_STATE`) or full (`com.google.android.gms.permission.AD_ID`) names. |
|
|
84
|
+
|
|
85
|
+
Remove it the day a feature really needs the network (update check, Drive backup…): without `INTERNET`, every request fails.
|
|
86
|
+
|
|
71
87
|
## Testing your own setup
|
|
72
88
|
|
|
73
|
-
The transforms behind the plugins are exported as pure functions from `@forthtilliath/expo-config-plugins/transforms` (`applyReleaseSigning`, `applyAndroidAbis`, `applyShortCxxPath`, `applyKotlinJvmTarget`).
|
|
89
|
+
The transforms behind the plugins are exported as pure functions from `@forthtilliath/expo-config-plugins/transforms` (`applyReleaseSigning`, `applyAndroidAbis`, `applyShortCxxPath`, `applyKotlinJvmTarget`, `buildReleaseRemovedPermissionsManifest`).
|
|
74
90
|
|
|
75
91
|
## Scripts
|
|
76
92
|
|
package/dist/transforms.d.ts
CHANGED
|
@@ -71,4 +71,23 @@ export interface ShortCxxPathOptions {
|
|
|
71
71
|
*/
|
|
72
72
|
stagingDir?: string;
|
|
73
73
|
}
|
|
74
|
+
export interface ReleaseRemovedPermissionsOptions {
|
|
75
|
+
/**
|
|
76
|
+
* Android permissions to strip from the release APK only, short (`INTERNET`)
|
|
77
|
+
* or full (`android.permission.INTERNET`) names.
|
|
78
|
+
*
|
|
79
|
+
* @defaultValue `["INTERNET"]`
|
|
80
|
+
*/
|
|
81
|
+
permissions?: string[];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The `android/app/src/release/AndroidManifest.xml` removing `permissions`
|
|
85
|
+
* from the release build: merged over the main manifest and every library's,
|
|
86
|
+
* `tools:node="remove"` drops the permission whoever declared it. Debug
|
|
87
|
+
* builds keep it (Metro needs `INTERNET`).
|
|
88
|
+
*
|
|
89
|
+
* @throws When the list is empty or holds anything but permission names —
|
|
90
|
+
* the names end up in XML, nothing else is let through.
|
|
91
|
+
*/
|
|
92
|
+
export declare function buildReleaseRemovedPermissionsManifest(permissions?: readonly string[]): string;
|
|
74
93
|
//# sourceMappingURL=transforms.d.ts.map
|
package/dist/transforms.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transforms.d.ts","sourceRoot":"","sources":["../src/transforms.ts"],"names":[],"mappings":"AAoBA;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,MAAM,CAmBR;AAED,qFAAqF;AACrF,MAAM,MAAM,oBAAoB,GAC5B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB,4CAA4C;AAC5C,eAAO,MAAM,QAAQ,EAAE,SAAS,MAAM,EAAiC,CAAC;AAExE;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,oBAAoB,EAAE,EAClC,IAAI,GAAE,SAAS,MAAM,EAAa,GACjC,oBAAoB,EAAE,CAcxB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,MAAM,CAiBR;AAID;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,SAAS,SAAO,GACf,MAAM,CAkBR;AAED,MAAM,WAAW,sBAAsB;IACrC,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
|
|
1
|
+
{"version":3,"file":"transforms.d.ts","sourceRoot":"","sources":["../src/transforms.ts"],"names":[],"mappings":"AAoBA;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,MAAM,CAmBR;AAED,qFAAqF;AACrF,MAAM,MAAM,oBAAoB,GAC5B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB,4CAA4C;AAC5C,eAAO,MAAM,QAAQ,EAAE,SAAS,MAAM,EAAiC,CAAC;AAExE;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,oBAAoB,EAAE,EAClC,IAAI,GAAE,SAAS,MAAM,EAAa,GACjC,oBAAoB,EAAE,CAcxB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,MAAM,CAiBR;AAID;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,SAAS,SAAO,GACf,MAAM,CAkBR;AAED,MAAM,WAAW,sBAAsB;IACrC,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gCAAgC;IAC/C;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAID;;;;;;;;GAQG;AACH,wBAAgB,sCAAsC,CACpD,WAAW,GAAE,SAAS,MAAM,EAAiB,GAC5C,MAAM,CA0BR"}
|
package/dist/transforms.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.applyReleaseSigning = applyReleaseSigning;
|
|
|
7
7
|
exports.applyAndroidAbis = applyAndroidAbis;
|
|
8
8
|
exports.applyShortCxxPath = applyShortCxxPath;
|
|
9
9
|
exports.applyKotlinJvmTarget = applyKotlinJvmTarget;
|
|
10
|
+
exports.buildReleaseRemovedPermissionsManifest = buildReleaseRemovedPermissionsManifest;
|
|
10
11
|
/** The `signingConfig signingConfigs.debug` of the `release` build type (not the `debug` one's). */
|
|
11
12
|
const RELEASE_USES_DEBUG_KEY = /(buildTypes\s*\{[\s\S]*?release\s*\{[^}]*?)signingConfig signingConfigs\.debug/;
|
|
12
13
|
function releaseSigningConfig(prefix) {
|
|
@@ -116,3 +117,35 @@ subprojects {
|
|
|
116
117
|
}
|
|
117
118
|
`;
|
|
118
119
|
}
|
|
120
|
+
const PERMISSION_NAME = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)*$/;
|
|
121
|
+
/**
|
|
122
|
+
* The `android/app/src/release/AndroidManifest.xml` removing `permissions`
|
|
123
|
+
* from the release build: merged over the main manifest and every library's,
|
|
124
|
+
* `tools:node="remove"` drops the permission whoever declared it. Debug
|
|
125
|
+
* builds keep it (Metro needs `INTERNET`).
|
|
126
|
+
*
|
|
127
|
+
* @throws When the list is empty or holds anything but permission names —
|
|
128
|
+
* the names end up in XML, nothing else is let through.
|
|
129
|
+
*/
|
|
130
|
+
function buildReleaseRemovedPermissionsManifest(permissions = ["INTERNET"]) {
|
|
131
|
+
if (permissions.length === 0) {
|
|
132
|
+
throw new Error("withReleaseRemovedPermissions: empty permissions list");
|
|
133
|
+
}
|
|
134
|
+
const names = permissions.map((permission) => {
|
|
135
|
+
if (!PERMISSION_NAME.test(permission)) {
|
|
136
|
+
throw new Error(`withReleaseRemovedPermissions: invalid permission name "${permission}"`);
|
|
137
|
+
}
|
|
138
|
+
return permission.includes(".")
|
|
139
|
+
? permission
|
|
140
|
+
: `android.permission.${permission}`;
|
|
141
|
+
});
|
|
142
|
+
const lines = [...new Set(names)].map((name) => ` <uses-permission android:name="${name}" tools:node="remove"/>`);
|
|
143
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
144
|
+
<!-- Generated by @forthtilliath/expo-config-plugins/withReleaseRemovedPermissions -->
|
|
145
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
146
|
+
xmlns:tools="http://schemas.android.com/tools">
|
|
147
|
+
|
|
148
|
+
${lines.join("\n")}
|
|
149
|
+
</manifest>
|
|
150
|
+
`;
|
|
151
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ConfigPlugin } from "expo/config-plugins";
|
|
2
|
+
import { type ReleaseRemovedPermissionsOptions } from "./transforms.js";
|
|
3
|
+
/**
|
|
4
|
+
* Strips permissions from the **release** APK only — `INTERNET` by default:
|
|
5
|
+
* for an app that works fully offline, Android itself then guarantees that
|
|
6
|
+
* no data can leave the phone, whatever a dependency tries. Debug builds
|
|
7
|
+
* keep them (Metro needs the network).
|
|
8
|
+
*
|
|
9
|
+
* Writes `android/app/src/release/AndroidManifest.xml`; refuses to overwrite
|
|
10
|
+
* one it didn't generate.
|
|
11
|
+
*
|
|
12
|
+
* `"@forthtilliath/expo-config-plugins/withReleaseRemovedPermissions"`, or
|
|
13
|
+
* `["@forthtilliath/expo-config-plugins/withReleaseRemovedPermissions", { "permissions": ["INTERNET", "ACCESS_NETWORK_STATE"] }]`
|
|
14
|
+
*/
|
|
15
|
+
declare const withReleaseRemovedPermissions: ConfigPlugin<ReleaseRemovedPermissionsOptions | undefined>;
|
|
16
|
+
export = withReleaseRemovedPermissions;
|
|
17
|
+
//# sourceMappingURL=withReleaseRemovedPermissions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withReleaseRemovedPermissions.d.ts","sourceRoot":"","sources":["../src/withReleaseRemovedPermissions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAoB,MAAM,qBAAqB,CAAC;AAI1E,OAAO,EAEL,KAAK,gCAAgC,EACtC,MAAM,iBAAiB,CAAC;AAYzB;;;;;;;;;;;GAWG;AACH,QAAA,MAAM,6BAA6B,EAAE,YAAY,CAC/C,gCAAgC,GAAG,SAAS,CAqB1C,CAAC;AAEL,SAAS,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const config_plugins_1 = require("expo/config-plugins");
|
|
3
|
+
const promises_1 = require("node:fs/promises");
|
|
4
|
+
const node_path_1 = require("node:path");
|
|
5
|
+
const transforms_js_1 = require("./transforms.js");
|
|
6
|
+
const GENERATED_MARK = "withReleaseRemovedPermissions";
|
|
7
|
+
async function readIfExists(path) {
|
|
8
|
+
try {
|
|
9
|
+
return await (0, promises_1.readFile)(path, "utf8");
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Strips permissions from the **release** APK only — `INTERNET` by default:
|
|
17
|
+
* for an app that works fully offline, Android itself then guarantees that
|
|
18
|
+
* no data can leave the phone, whatever a dependency tries. Debug builds
|
|
19
|
+
* keep them (Metro needs the network).
|
|
20
|
+
*
|
|
21
|
+
* Writes `android/app/src/release/AndroidManifest.xml`; refuses to overwrite
|
|
22
|
+
* one it didn't generate.
|
|
23
|
+
*
|
|
24
|
+
* `"@forthtilliath/expo-config-plugins/withReleaseRemovedPermissions"`, or
|
|
25
|
+
* `["@forthtilliath/expo-config-plugins/withReleaseRemovedPermissions", { "permissions": ["INTERNET", "ACCESS_NETWORK_STATE"] }]`
|
|
26
|
+
*/
|
|
27
|
+
const withReleaseRemovedPermissions = (config, options) => (0, config_plugins_1.withDangerousMod)(config, [
|
|
28
|
+
"android",
|
|
29
|
+
async (mod) => {
|
|
30
|
+
// Built first: invalid options fail the prebuild before any write.
|
|
31
|
+
const manifest = (0, transforms_js_1.buildReleaseRemovedPermissionsManifest)(options?.permissions);
|
|
32
|
+
const dir = (0, node_path_1.join)(mod.modRequest.platformProjectRoot, "app/src/release");
|
|
33
|
+
const path = (0, node_path_1.join)(dir, "AndroidManifest.xml");
|
|
34
|
+
const existing = await readIfExists(path);
|
|
35
|
+
if (existing !== null && !existing.includes(GENERATED_MARK)) {
|
|
36
|
+
throw new Error(`withReleaseRemovedPermissions: ${path} already exists and wasn't generated by this plugin`);
|
|
37
|
+
}
|
|
38
|
+
await (0, promises_1.mkdir)(dir, { recursive: true });
|
|
39
|
+
await (0, promises_1.writeFile)(path, manifest);
|
|
40
|
+
return mod;
|
|
41
|
+
},
|
|
42
|
+
]);
|
|
43
|
+
module.exports = withReleaseRemovedPermissions;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forthtilliath/expo-config-plugins",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Expo config plugins for sideloaded Android apps: release signing with your own key, ARM-only native code, short CMake paths on Windows, one JVM target for Kotlin.",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Expo config plugins for sideloaded Android apps: release signing with your own key, ARM-only native code, short CMake paths on Windows, one JVM target for Kotlin, permissions removed from release builds.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"./withAndroidAbis": "./dist/withAndroidAbis.js",
|
|
22
22
|
"./withShortCxxPath": "./dist/withShortCxxPath.js",
|
|
23
23
|
"./withKotlinJvmTarget": "./dist/withKotlinJvmTarget.js",
|
|
24
|
+
"./withReleaseRemovedPermissions": "./dist/withReleaseRemovedPermissions.js",
|
|
24
25
|
"./transforms": "./dist/transforms.js",
|
|
25
26
|
"./package.json": "./package.json"
|
|
26
27
|
},
|
|
@@ -28,14 +29,14 @@
|
|
|
28
29
|
"expo": ">=54.0.0"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
|
-
"@types/node": "^26.
|
|
32
|
+
"@types/node": "^26.6.6",
|
|
32
33
|
"@vitest/coverage-v8": "5.0.5",
|
|
33
|
-
"eslint": "10.
|
|
34
|
-
"expo": "~57.0.
|
|
34
|
+
"eslint": "10.14.0",
|
|
35
|
+
"expo": "~57.0.31",
|
|
35
36
|
"typescript": "^5.9.3",
|
|
36
37
|
"vitest": "^5.0.5",
|
|
37
|
-
"@forthtilliath/
|
|
38
|
-
"@forthtilliath/
|
|
38
|
+
"@forthtilliath/typescript-config": "0.1.1",
|
|
39
|
+
"@forthtilliath/eslint-config": "0.10.1"
|
|
39
40
|
},
|
|
40
41
|
"scripts": {
|
|
41
42
|
"lint": "eslint . --max-warnings 0",
|