@hot-updater/expo 1.0.0-rc.0 → 1.0.0-rc.2
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/dist/index.cjs +30 -3
- package/dist/index.d.cts +5 -1
- package/dist/index.d.mts +5 -1
- package/dist/index.mjs +30 -5
- package/package.json +5 -5
- package/plugin/build/withHotUpdater.js +21 -57
package/dist/index.cjs
CHANGED
|
@@ -46,6 +46,7 @@ let node_fs = require("node:fs");
|
|
|
46
46
|
let node_stream_promises = require("node:stream/promises");
|
|
47
47
|
let node_stream = require("node:stream");
|
|
48
48
|
let node_buffer = require("node:buffer");
|
|
49
|
+
let expo_config_js = require("expo/config.js");
|
|
49
50
|
let uuidv7 = require("uuidv7");
|
|
50
51
|
//#region ../../node_modules/.pnpm/is-plain-obj@4.1.0/node_modules/is-plain-obj/index.js
|
|
51
52
|
function isPlainObject(value) {
|
|
@@ -6266,6 +6267,26 @@ const runExpoPrebuild = async ({ platform }) => {
|
|
|
6266
6267
|
};
|
|
6267
6268
|
//#endregion
|
|
6268
6269
|
//#region src/expo.ts
|
|
6270
|
+
const getHotUpdaterExpoPluginProps = (plugins) => {
|
|
6271
|
+
const entry = plugins?.find((plugin) => Array.isArray(plugin) && (plugin[0] === "@hot-updater/expo" || plugin[0] === "@hot-updater/expo/app.plugin.js"));
|
|
6272
|
+
if (!entry || !Array.isArray(entry)) return void 0;
|
|
6273
|
+
const props = entry[1];
|
|
6274
|
+
return typeof props === "object" && props !== null ? props : void 0;
|
|
6275
|
+
};
|
|
6276
|
+
const getExpoFingerprintExtraSources = (cwd) => {
|
|
6277
|
+
const { exp } = (0, expo_config_js.getConfig)(cwd, { skipSDKVersionRequirement: true });
|
|
6278
|
+
const publicKeyPath = getHotUpdaterExpoPluginProps(exp.plugins)?.publicKeyPath;
|
|
6279
|
+
if (publicKeyPath === void 0) return [];
|
|
6280
|
+
if (typeof publicKeyPath !== "string" || !publicKeyPath.trim()) throw new Error("@hot-updater/expo publicKeyPath must be a non-empty path.");
|
|
6281
|
+
return [publicKeyPath];
|
|
6282
|
+
};
|
|
6283
|
+
const getExpoBundleSigningPublicKey = async (cwd) => {
|
|
6284
|
+
const { exp } = (0, expo_config_js.getConfig)(cwd, { skipSDKVersionRequirement: true });
|
|
6285
|
+
const publicKeyPath = getHotUpdaterExpoPluginProps(exp.plugins)?.publicKeyPath;
|
|
6286
|
+
if (publicKeyPath === void 0) return null;
|
|
6287
|
+
if (typeof publicKeyPath !== "string" || !publicKeyPath.trim()) throw new Error("@hot-updater/expo publicKeyPath must be a non-empty path.");
|
|
6288
|
+
return { publicKey: await (0, _hot_updater_cli_tools.readBundleSigningPublicKeyFile)(publicKeyPath, { cwd }) };
|
|
6289
|
+
};
|
|
6269
6290
|
const isHermesEnabled = (cwd, platform) => {
|
|
6270
6291
|
try {
|
|
6271
6292
|
const appJsonPath = path.default.join(cwd, "app.json");
|
|
@@ -6333,9 +6354,13 @@ const expo = (config = {
|
|
|
6333
6354
|
}) => ({ cwd }) => {
|
|
6334
6355
|
const { outDir = "dist", sourcemap = false, resetCache = true } = config;
|
|
6335
6356
|
return {
|
|
6336
|
-
nativeBuild: {
|
|
6337
|
-
|
|
6338
|
-
|
|
6357
|
+
nativeBuild: {
|
|
6358
|
+
getBundleSigningPublicKey: () => getExpoBundleSigningPublicKey(cwd),
|
|
6359
|
+
getFingerprintExtraSources: async () => getExpoFingerprintExtraSources(cwd),
|
|
6360
|
+
prebuild: async ({ platform }) => {
|
|
6361
|
+
await runExpoPrebuild({ platform });
|
|
6362
|
+
}
|
|
6363
|
+
},
|
|
6339
6364
|
build: async ({ platform }) => {
|
|
6340
6365
|
const buildPath = path.default.join(cwd, outDir);
|
|
6341
6366
|
await fs.default.promises.rm(buildPath, {
|
|
@@ -6361,3 +6386,5 @@ const expo = (config = {
|
|
|
6361
6386
|
};
|
|
6362
6387
|
//#endregion
|
|
6363
6388
|
exports.expo = expo;
|
|
6389
|
+
exports.getExpoBundleSigningPublicKey = getExpoBundleSigningPublicKey;
|
|
6390
|
+
exports.getExpoFingerprintExtraSources = getExpoFingerprintExtraSources;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { BasePluginArgs, BuildPlugin, BuildPluginConfig } from "@hot-updater/plugin-core";
|
|
2
2
|
|
|
3
3
|
//#region src/expo.d.ts
|
|
4
|
+
declare const getExpoFingerprintExtraSources: (cwd: string) => string[];
|
|
5
|
+
declare const getExpoBundleSigningPublicKey: (cwd: string) => Promise<{
|
|
6
|
+
readonly publicKey: string;
|
|
7
|
+
} | null>;
|
|
4
8
|
interface ExpoPluginConfig extends BuildPluginConfig {
|
|
5
9
|
/**
|
|
6
10
|
* @default false
|
|
@@ -17,4 +21,4 @@ declare const expo: (config?: ExpoPluginConfig) => ({
|
|
|
17
21
|
cwd
|
|
18
22
|
}: BasePluginArgs) => BuildPlugin;
|
|
19
23
|
//#endregion
|
|
20
|
-
export { ExpoPluginConfig, expo };
|
|
24
|
+
export { ExpoPluginConfig, expo, getExpoBundleSigningPublicKey, getExpoFingerprintExtraSources };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { BasePluginArgs, BuildPlugin, BuildPluginConfig } from "@hot-updater/plugin-core";
|
|
2
2
|
|
|
3
3
|
//#region src/expo.d.ts
|
|
4
|
+
declare const getExpoFingerprintExtraSources: (cwd: string) => string[];
|
|
5
|
+
declare const getExpoBundleSigningPublicKey: (cwd: string) => Promise<{
|
|
6
|
+
readonly publicKey: string;
|
|
7
|
+
} | null>;
|
|
4
8
|
interface ExpoPluginConfig extends BuildPluginConfig {
|
|
5
9
|
/**
|
|
6
10
|
* @default false
|
|
@@ -17,4 +21,4 @@ declare const expo: (config?: ExpoPluginConfig) => ({
|
|
|
17
21
|
cwd
|
|
18
22
|
}: BasePluginArgs) => BuildPlugin;
|
|
19
23
|
//#endregion
|
|
20
|
-
export { ExpoPluginConfig, expo };
|
|
24
|
+
export { ExpoPluginConfig, expo, getExpoBundleSigningPublicKey, getExpoFingerprintExtraSources };
|
package/dist/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import fs from "fs";
|
|
|
4
4
|
import * as path$2 from "path";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { compileHermes } from "@hot-updater/bare";
|
|
7
|
-
import { log, p } from "@hot-updater/cli-tools";
|
|
7
|
+
import { log, p, readBundleSigningPublicKeyFile } from "@hot-updater/cli-tools";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
|
|
10
10
|
import { StringDecoder } from "node:string_decoder";
|
|
@@ -20,6 +20,7 @@ import { appendFileSync, createReadStream, createWriteStream, readFileSync, stat
|
|
|
20
20
|
import { finished } from "node:stream/promises";
|
|
21
21
|
import { Duplex, PassThrough, Readable, Transform, Writable, getDefaultHighWaterMark } from "node:stream";
|
|
22
22
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
23
|
+
import { getConfig } from "expo/config.js";
|
|
23
24
|
import { uuidv7 } from "uuidv7";
|
|
24
25
|
//#region \0rolldown/runtime.js
|
|
25
26
|
var __create = Object.create;
|
|
@@ -6264,6 +6265,26 @@ const runExpoPrebuild = async ({ platform }) => {
|
|
|
6264
6265
|
};
|
|
6265
6266
|
//#endregion
|
|
6266
6267
|
//#region src/expo.ts
|
|
6268
|
+
const getHotUpdaterExpoPluginProps = (plugins) => {
|
|
6269
|
+
const entry = plugins?.find((plugin) => Array.isArray(plugin) && (plugin[0] === "@hot-updater/expo" || plugin[0] === "@hot-updater/expo/app.plugin.js"));
|
|
6270
|
+
if (!entry || !Array.isArray(entry)) return void 0;
|
|
6271
|
+
const props = entry[1];
|
|
6272
|
+
return typeof props === "object" && props !== null ? props : void 0;
|
|
6273
|
+
};
|
|
6274
|
+
const getExpoFingerprintExtraSources = (cwd) => {
|
|
6275
|
+
const { exp } = getConfig(cwd, { skipSDKVersionRequirement: true });
|
|
6276
|
+
const publicKeyPath = getHotUpdaterExpoPluginProps(exp.plugins)?.publicKeyPath;
|
|
6277
|
+
if (publicKeyPath === void 0) return [];
|
|
6278
|
+
if (typeof publicKeyPath !== "string" || !publicKeyPath.trim()) throw new Error("@hot-updater/expo publicKeyPath must be a non-empty path.");
|
|
6279
|
+
return [publicKeyPath];
|
|
6280
|
+
};
|
|
6281
|
+
const getExpoBundleSigningPublicKey = async (cwd) => {
|
|
6282
|
+
const { exp } = getConfig(cwd, { skipSDKVersionRequirement: true });
|
|
6283
|
+
const publicKeyPath = getHotUpdaterExpoPluginProps(exp.plugins)?.publicKeyPath;
|
|
6284
|
+
if (publicKeyPath === void 0) return null;
|
|
6285
|
+
if (typeof publicKeyPath !== "string" || !publicKeyPath.trim()) throw new Error("@hot-updater/expo publicKeyPath must be a non-empty path.");
|
|
6286
|
+
return { publicKey: await readBundleSigningPublicKeyFile(publicKeyPath, { cwd }) };
|
|
6287
|
+
};
|
|
6267
6288
|
const isHermesEnabled = (cwd, platform) => {
|
|
6268
6289
|
try {
|
|
6269
6290
|
const appJsonPath = path.join(cwd, "app.json");
|
|
@@ -6331,9 +6352,13 @@ const expo = (config = {
|
|
|
6331
6352
|
}) => ({ cwd }) => {
|
|
6332
6353
|
const { outDir = "dist", sourcemap = false, resetCache = true } = config;
|
|
6333
6354
|
return {
|
|
6334
|
-
nativeBuild: {
|
|
6335
|
-
|
|
6336
|
-
|
|
6355
|
+
nativeBuild: {
|
|
6356
|
+
getBundleSigningPublicKey: () => getExpoBundleSigningPublicKey(cwd),
|
|
6357
|
+
getFingerprintExtraSources: async () => getExpoFingerprintExtraSources(cwd),
|
|
6358
|
+
prebuild: async ({ platform }) => {
|
|
6359
|
+
await runExpoPrebuild({ platform });
|
|
6360
|
+
}
|
|
6361
|
+
},
|
|
6337
6362
|
build: async ({ platform }) => {
|
|
6338
6363
|
const buildPath = path.join(cwd, outDir);
|
|
6339
6364
|
await fs.promises.rm(buildPath, {
|
|
@@ -6358,4 +6383,4 @@ const expo = (config = {
|
|
|
6358
6383
|
};
|
|
6359
6384
|
};
|
|
6360
6385
|
//#endregion
|
|
6361
|
-
export { expo };
|
|
6386
|
+
export { expo, getExpoBundleSigningPublicKey, getExpoFingerprintExtraSources };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/expo",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.2",
|
|
4
4
|
"description": "Hot Updater build and config plugin for Expo",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -28,16 +28,16 @@
|
|
|
28
28
|
"@babel/core": "7.26.0",
|
|
29
29
|
"@babel/types": "7.26.0",
|
|
30
30
|
"uuidv7": "^1.0.2",
|
|
31
|
-
"@hot-updater/
|
|
32
|
-
"@hot-updater/
|
|
33
|
-
"@hot-updater/plugin-core": "1.0.0-rc.
|
|
31
|
+
"@hot-updater/cli-tools": "1.0.0-rc.1",
|
|
32
|
+
"@hot-updater/bare": "1.0.0-rc.1",
|
|
33
|
+
"@hot-updater/plugin-core": "1.0.0-rc.1"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^20",
|
|
37
37
|
"expo": "^50.0.0",
|
|
38
38
|
"execa": "9.5.2",
|
|
39
39
|
"vitest": "4.1.4",
|
|
40
|
-
"hot-updater": "1.0.0-rc.
|
|
40
|
+
"hot-updater": "1.0.0-rc.2"
|
|
41
41
|
},
|
|
42
42
|
"exports": {
|
|
43
43
|
".": {
|
|
@@ -4,28 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getPublicKeyFromConfig = void 0;
|
|
7
|
-
const node_crypto_1 = require("node:crypto");
|
|
8
|
-
const promises_1 = require("node:fs/promises");
|
|
9
|
-
const path_1 = __importDefault(require("path"));
|
|
10
7
|
const config_plugins_1 = require("expo/config-plugins");
|
|
11
8
|
const package_json_1 = __importDefault(require("../../package.json"));
|
|
12
9
|
const transformers_1 = require("./transformers");
|
|
13
10
|
const loadCliTools = () => import("@hot-updater/cli-tools");
|
|
14
11
|
const loadHotUpdater = () => import("hot-updater");
|
|
15
|
-
const canonicalizeRsaSpkiPublicKey = (publicKeyPem) => {
|
|
16
|
-
const trimmed = publicKeyPem.trim();
|
|
17
|
-
if (!trimmed.startsWith("-----BEGIN PUBLIC KEY-----") ||
|
|
18
|
-
!trimmed.endsWith("-----END PUBLIC KEY-----") ||
|
|
19
|
-
trimmed.includes("PRIVATE KEY")) {
|
|
20
|
-
throw new Error("not spki");
|
|
21
|
-
}
|
|
22
|
-
const publicKey = (0, node_crypto_1.createPublicKey)(trimmed);
|
|
23
|
-
if (publicKey.asymmetricKeyType !== "rsa" ||
|
|
24
|
-
(publicKey.asymmetricKeyDetails?.modulusLength ?? 0) < 2048) {
|
|
25
|
-
throw new Error("not rsa");
|
|
26
|
-
}
|
|
27
|
-
return publicKey.export({ format: "pem", type: "spki" }).toString().trim();
|
|
28
|
-
};
|
|
29
12
|
const ANDROID_META_DATA_KEYS = {
|
|
30
13
|
channel: "com.hotupdater.CHANNEL",
|
|
31
14
|
fingerprintHash: "com.hotupdater.FINGERPRINT_HASH",
|
|
@@ -60,46 +43,27 @@ const upsertAndroidMetaData = (application, name, value) => {
|
|
|
60
43
|
application["meta-data"] = metaData;
|
|
61
44
|
};
|
|
62
45
|
let fingerprintCache = null;
|
|
63
|
-
const getFingerprint = async () => {
|
|
64
|
-
|
|
65
|
-
|
|
46
|
+
const getFingerprint = async (publicKeyPath) => {
|
|
47
|
+
const cached = fingerprintCache;
|
|
48
|
+
if (cached && cached.publicKeyPath === publicKeyPath) {
|
|
49
|
+
return cached.value;
|
|
66
50
|
}
|
|
67
51
|
const { createFingerprintJSON, generateFingerprints } = await loadHotUpdater();
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
52
|
+
const value = await generateFingerprints(publicKeyPath === undefined ? [] : [publicKeyPath]);
|
|
53
|
+
fingerprintCache = { publicKeyPath, value };
|
|
54
|
+
await createFingerprintJSON(value);
|
|
55
|
+
return value;
|
|
71
56
|
};
|
|
72
|
-
/**
|
|
73
|
-
const getPublicKeyFromConfig = async (
|
|
74
|
-
if (
|
|
75
|
-
("enabled" in signingConfig && !signingConfig.enabled)) {
|
|
57
|
+
/** Reads the native trust anchor configured by the Expo app. */
|
|
58
|
+
const getPublicKeyFromConfig = async (publicKeyPath, projectRoot = process.cwd()) => {
|
|
59
|
+
if (publicKeyPath === undefined)
|
|
76
60
|
return null;
|
|
77
|
-
}
|
|
78
|
-
// Retain the v0 EAS key source only for local configs without an explicit pin.
|
|
79
|
-
if ("enabled" in signingConfig && signingConfig.publicKeyPath === undefined) {
|
|
80
|
-
const envPrivateKey = process.env.HOT_UPDATER_PRIVATE_KEY;
|
|
81
|
-
if (envPrivateKey) {
|
|
82
|
-
try {
|
|
83
|
-
const pem = envPrivateKey.includes("-----BEGIN")
|
|
84
|
-
? envPrivateKey
|
|
85
|
-
: await (0, promises_1.readFile)(path_1.default.resolve(process.cwd(), envPrivateKey), "utf8");
|
|
86
|
-
return canonicalizeRsaSpkiPublicKey((0, node_crypto_1.createPublicKey)((0, node_crypto_1.createPrivateKey)(pem))
|
|
87
|
-
.export({ format: "pem", type: "spki" })
|
|
88
|
-
.toString());
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
// As in v0, try the configured local files if the environment source fails.
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
61
|
try {
|
|
96
|
-
const {
|
|
97
|
-
return (
|
|
62
|
+
const { readBundleSigningPublicKeyFile } = await loadCliTools();
|
|
63
|
+
return (await readBundleSigningPublicKeyFile(publicKeyPath, { cwd: projectRoot })).trim();
|
|
98
64
|
}
|
|
99
65
|
catch {
|
|
100
|
-
throw new Error(
|
|
101
|
-
? "[hot-updater] Failed to load publicKeyPath for bundle signing."
|
|
102
|
-
: "[hot-updater] Failed to load public key for bundle signing.");
|
|
66
|
+
throw new Error("[hot-updater] Failed to load publicKeyPath for bundle signing.");
|
|
103
67
|
}
|
|
104
68
|
};
|
|
105
69
|
exports.getPublicKeyFromConfig = getPublicKeyFromConfig;
|
|
@@ -138,9 +102,9 @@ const withHotUpdaterConfigAsync = (props) => (config) => {
|
|
|
138
102
|
return hotUpdaterConfigPromise;
|
|
139
103
|
};
|
|
140
104
|
let publicKeyPromise;
|
|
141
|
-
const getPublicKey = async () => {
|
|
105
|
+
const getPublicKey = async (projectRoot) => {
|
|
142
106
|
if (!publicKeyPromise) {
|
|
143
|
-
publicKeyPromise =
|
|
107
|
+
publicKeyPromise = (0, exports.getPublicKeyFromConfig)(props.publicKeyPath, projectRoot);
|
|
144
108
|
}
|
|
145
109
|
return publicKeyPromise;
|
|
146
110
|
};
|
|
@@ -150,11 +114,11 @@ const withHotUpdaterConfigAsync = (props) => (config) => {
|
|
|
150
114
|
let fingerprintHash = null;
|
|
151
115
|
const hotUpdaterConfig = await getHotUpdaterConfig();
|
|
152
116
|
if (hotUpdaterConfig.updateStrategy !== "appVersion") {
|
|
153
|
-
const fingerprint = await getFingerprint();
|
|
117
|
+
const fingerprint = await getFingerprint(props.publicKeyPath);
|
|
154
118
|
fingerprintHash = fingerprint.ios.hash;
|
|
155
119
|
}
|
|
156
120
|
// Load public key if signing is enabled
|
|
157
|
-
const publicKey = await getPublicKey();
|
|
121
|
+
const publicKey = await getPublicKey(cfg.modRequest.projectRoot);
|
|
158
122
|
cfg.modResults.HOT_UPDATER_CHANNEL = channel;
|
|
159
123
|
if (fingerprintHash) {
|
|
160
124
|
cfg.modResults.HOT_UPDATER_FINGERPRINT_HASH = fingerprintHash;
|
|
@@ -172,11 +136,11 @@ const withHotUpdaterConfigAsync = (props) => (config) => {
|
|
|
172
136
|
let fingerprintHash = null;
|
|
173
137
|
const hotUpdaterConfig = await getHotUpdaterConfig();
|
|
174
138
|
if (hotUpdaterConfig.updateStrategy !== "appVersion") {
|
|
175
|
-
const fingerprint = await getFingerprint();
|
|
139
|
+
const fingerprint = await getFingerprint(props.publicKeyPath);
|
|
176
140
|
fingerprintHash = fingerprint.android.hash;
|
|
177
141
|
}
|
|
178
142
|
// Load public key if signing is enabled
|
|
179
|
-
const publicKey = await getPublicKey();
|
|
143
|
+
const publicKey = await getPublicKey(cfg.modRequest.projectRoot);
|
|
180
144
|
const application = cfg.modResults.manifest.application?.[0];
|
|
181
145
|
if (!application) {
|
|
182
146
|
return cfg;
|
|
@@ -186,7 +150,7 @@ const withHotUpdaterConfigAsync = (props) => (config) => {
|
|
|
186
150
|
upsertAndroidMetaData(application, ANDROID_META_DATA_KEYS.fingerprintHash, fingerprintHash);
|
|
187
151
|
}
|
|
188
152
|
if (publicKey) {
|
|
189
|
-
upsertAndroidMetaData(application, ANDROID_META_DATA_KEYS.publicKey, publicKey);
|
|
153
|
+
upsertAndroidMetaData(application, ANDROID_META_DATA_KEYS.publicKey, config_plugins_1.XML.escapeAndroidString(publicKey));
|
|
190
154
|
}
|
|
191
155
|
else {
|
|
192
156
|
removeAndroidMetaData(application, ANDROID_META_DATA_KEYS.publicKey);
|