hot-updater 1.0.8 → 1.1.1
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/config.d.mts +23 -38
- package/dist/config.mjs +1 -1
- package/dist/fingerprint-D0bHaE2B.mjs +348 -0
- package/dist/index.mjs +30179 -9254
- package/dist/infra-templates/aws/lambda/index.cjs +1 -1
- package/dist/infra-templates/aws/template.json +10 -10
- package/dist/infra-templates/azure/azure/function/index.mjs +1 -1
- package/dist/infra-templates/azure/template.json +11 -11
- package/dist/infra-templates/cloudflare/template.json +10 -10
- package/dist/infra-templates/cloudflare/worker/dist/README.md +1 -1
- package/dist/infra-templates/cloudflare/worker/dist/index.js +1 -1
- package/dist/infra-templates/cloudflare/worker/dist/index.js.map +1 -1
- package/dist/infra-templates/firebase/firebase/functions/index.cjs +1 -1
- package/dist/infra-templates/firebase/template.json +11 -11
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-plugin-core/dist/index.d.cts +2 -2
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-plugin-core/dist/index.d.mts +2 -2
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-plugin-core/dist/types/index.d.cts +63 -2
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-plugin-core/dist/types/index.d.mts +63 -2
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-plugin-insights/dist/client/eventId.cjs +4 -2
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-plugin-insights/dist/client/eventId.mjs +4 -2
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-plugin-insights/dist/client/index.cjs +17 -11
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-plugin-insights/dist/client/index.mjs +17 -11
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-protocol/dist/index.d.cts +36 -0
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-protocol/dist/index.d.mts +36 -0
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-server/dist/package.cjs +1 -1
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-server/dist/package.mjs +1 -1
- package/dist/infra-templates/supabase/template.json +10 -10
- package/dist/infra-templates/vercel/template.json +11 -11
- package/dist/infra-templates/vercel/vercel/api/index.mjs +1 -1
- package/package.json +24 -29
- package/dist/fingerprint-DyKBEn51.mjs +0 -21191
package/dist/config.d.mts
CHANGED
|
@@ -1,52 +1,37 @@
|
|
|
1
1
|
import { HotUpdaterConfigOptions } from "@hot-updater/cli-tools";
|
|
2
|
-
import { ConfigInput, Platform } from "@hot-updater/plugin-core";
|
|
2
|
+
import { ConfigInput, FingerprintExtraSources, NativeFingerprint, Platform } from "@hot-updater/plugin-core";
|
|
3
3
|
//#region src/utils/fingerprint/common.d.ts
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type: "contents";
|
|
13
|
-
id: string;
|
|
14
|
-
contents: string | Buffer;
|
|
15
|
-
reasons: string[];
|
|
16
|
-
hash: string | null;
|
|
17
|
-
debugInfo?: any;
|
|
18
|
-
};
|
|
19
|
-
type FingerprintResult = {
|
|
20
|
-
hash: string;
|
|
21
|
-
sources: FingerprintSource[];
|
|
22
|
-
};
|
|
23
|
-
//#endregion
|
|
24
|
-
//#region src/utils/fingerprint/index.d.ts
|
|
25
|
-
declare const generateFingerprints: (additionalExtraSources?: readonly string[]) => Promise<{
|
|
4
|
+
interface FingerprintInputs {
|
|
5
|
+
adapter: string;
|
|
6
|
+
extraSources?: FingerprintExtraSources;
|
|
7
|
+
ignorePaths?: string[];
|
|
8
|
+
nativeVersion?: string | number;
|
|
9
|
+
}
|
|
10
|
+
/** fingerprint.json: each platform's fingerprint and the inputs it came from. */
|
|
11
|
+
interface FingerprintFile {
|
|
26
12
|
ios: FingerprintResult;
|
|
27
13
|
android: FingerprintResult;
|
|
28
|
-
|
|
29
|
-
|
|
14
|
+
inputs?: FingerprintInputs;
|
|
15
|
+
}
|
|
16
|
+
type FingerprintResult = NativeFingerprint;
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/utils/fingerprint/index.d.ts
|
|
19
|
+
declare const generateFingerprints: (additionalExtraSources?: readonly string[]) => Promise<FingerprintFile>;
|
|
20
|
+
declare const generateFingerprint: (platform: "ios" | "android") => Promise<import("@hot-updater/plugin-core").NativeFingerprint>;
|
|
21
|
+
/**
|
|
22
|
+
* Writes fingerprint.json, which native builds read to embed each platform's
|
|
23
|
+
* hash. With `platform`, only that platform's fingerprint is replaced.
|
|
24
|
+
*/
|
|
30
25
|
declare const createAndInjectFingerprintFiles: ({ platform }?: {
|
|
31
26
|
platform?: Platform;
|
|
32
27
|
}) => Promise<{
|
|
33
|
-
fingerprint:
|
|
34
|
-
ios: FingerprintResult;
|
|
35
|
-
android: FingerprintResult;
|
|
36
|
-
};
|
|
37
|
-
androidPaths: string[];
|
|
38
|
-
iosPaths: string[];
|
|
39
|
-
}>;
|
|
40
|
-
declare const createFingerprintJSON: (fingerprint: {
|
|
41
|
-
ios: FingerprintResult;
|
|
42
|
-
android: FingerprintResult;
|
|
43
|
-
}) => Promise<{
|
|
44
|
-
ios: FingerprintResult;
|
|
45
|
-
android: FingerprintResult;
|
|
28
|
+
fingerprint: FingerprintFile;
|
|
46
29
|
}>;
|
|
30
|
+
declare const createFingerprintJSON: (fingerprint: FingerprintFile) => Promise<FingerprintFile>;
|
|
47
31
|
declare const readLocalFingerprint: () => Promise<{
|
|
48
32
|
ios: FingerprintResult | null;
|
|
49
33
|
android: FingerprintResult | null;
|
|
34
|
+
inputs?: FingerprintInputs;
|
|
50
35
|
} | null>;
|
|
51
36
|
//#endregion
|
|
52
37
|
//#region src/config.d.ts
|
package/dist/config.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as readLocalFingerprint, i as generateFingerprints, n as createFingerprintJSON, r as generateFingerprint, t as createAndInjectFingerprintFiles } from "./fingerprint-D0bHaE2B.mjs";
|
|
2
2
|
//#region src/config.ts
|
|
3
3
|
const defineConfig = (config) => {
|
|
4
4
|
return config;
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { colors, getCwd, loadConfig, p, reactNativeCliFingerprint } from "@hot-updater/cli-tools";
|
|
5
|
+
import fs$1 from "node:fs";
|
|
6
|
+
import path$1 from "node:path";
|
|
7
|
+
import fs$2 from "node:fs/promises";
|
|
8
|
+
import { pathToFileURL } from "node:url";
|
|
9
|
+
//#region src/utils/fingerprint/common.ts
|
|
10
|
+
const appendFingerprintExtraSources = (extraSources, additions) => {
|
|
11
|
+
if (additions.length === 0) return extraSources;
|
|
12
|
+
const append = (sources = []) => [.../* @__PURE__ */ new Set([...sources, ...additions])];
|
|
13
|
+
if (!extraSources || Array.isArray(extraSources)) return append(extraSources);
|
|
14
|
+
return {
|
|
15
|
+
ios: append(extraSources.ios),
|
|
16
|
+
android: append(extraSources.android)
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
const ensureFingerprintConfig = async (additionalExtraSources) => {
|
|
20
|
+
const config = await loadConfig(null);
|
|
21
|
+
if (config.updateStrategy === "appVersion") {
|
|
22
|
+
p.log.error("The updateStrategy in hot-updater.config.ts is set to 'appVersion'. This command only works with 'fingerprint' strategy.");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const buildAdapter = await config.build({ cwd: getCwd() });
|
|
26
|
+
const nativeExtraSources = additionalExtraSources ?? await buildAdapter.nativeBuild?.getFingerprintExtraSources?.() ?? [];
|
|
27
|
+
return {
|
|
28
|
+
...config.fingerprint,
|
|
29
|
+
extraSources: appendFingerprintExtraSources(config.fingerprint.extraSources, nativeExtraSources),
|
|
30
|
+
adapter: buildAdapter.name,
|
|
31
|
+
provider: buildAdapter.nativeBuild?.fingerprint
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* What fingerprint.json records so a native build can compute the fingerprint
|
|
36
|
+
* without loading hot-updater.config.ts, whose storage and database need
|
|
37
|
+
* deploy secrets a build machine may not have: the build adapter and the
|
|
38
|
+
* fingerprint settings it receives.
|
|
39
|
+
*/
|
|
40
|
+
/** The inputs' adapter for a build adapter without its own fingerprint. */
|
|
41
|
+
const DEFAULT_FINGERPRINT_ADAPTER = "react-native";
|
|
42
|
+
const getFingerprintInputs = (options) => ({
|
|
43
|
+
adapter: options.provider ? options.adapter ?? "unknown" : DEFAULT_FINGERPRINT_ADAPTER,
|
|
44
|
+
...options.extraSources === void 0 ? {} : { extraSources: options.extraSources },
|
|
45
|
+
...options.ignorePaths === void 0 ? {} : { ignorePaths: options.ignorePaths },
|
|
46
|
+
...options.nativeVersion === void 0 ? {} : { nativeVersion: options.nativeVersion }
|
|
47
|
+
});
|
|
48
|
+
const EXPO_FINGERPRINT = "@expo/fingerprint";
|
|
49
|
+
/** An engine id without its version: `@expo/fingerprint@0.20.14` → `@expo/fingerprint`. */
|
|
50
|
+
const engineName = (engine) => {
|
|
51
|
+
const at = engine.lastIndexOf("@");
|
|
52
|
+
return at > 0 ? engine.slice(0, at) : engine;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* The engines of two fingerprints when they differ. The hash then changed with
|
|
56
|
+
* the engine, whether or not a native input changed. A fingerprint without a
|
|
57
|
+
* recorded engine counts as `@expo/fingerprint` against another engine.
|
|
58
|
+
*/
|
|
59
|
+
const getFingerprintEngineChange = (before, after) => {
|
|
60
|
+
if (!before || !after?.engine) return null;
|
|
61
|
+
const beforeEngine = before.engine ?? (engineName(after.engine) === EXPO_FINGERPRINT ? null : EXPO_FINGERPRINT);
|
|
62
|
+
return beforeEngine && beforeEngine !== after.engine ? {
|
|
63
|
+
before: beforeEngine,
|
|
64
|
+
after: after.engine
|
|
65
|
+
} : null;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Explains a hash that changed with its engine, for deploy, doctor, and
|
|
69
|
+
* `fingerprint create`.
|
|
70
|
+
*/
|
|
71
|
+
const STILL_DIFFERS = "If the fingerprint still differs with it, native inputs changed too, and builds you shipped need a rebuild.";
|
|
72
|
+
const describeFingerprintEngineChange = (change, label, recorded = false) => ({
|
|
73
|
+
message: `The ${label} fingerprint changed because fingerprint.json was created with ${change.before} and the project now uses ${change.after}. Native inputs may be unchanged.`,
|
|
74
|
+
resolution: engineName(change.before) !== engineName(change.after) ? `${recorded ? `fingerprint.json now records the ${change.after} hash. Rebuild` : "Run `npx hot-updater fingerprint create`, then rebuild"} the ${label} app. Builds you already shipped keep the old fingerprint and receive no updates until users install the new build.` : recorded ? `fingerprint.json now records the ${change.after} hash. To keep updating ${label} builds you already shipped, go back to ${change.before} and run \`npx hot-updater fingerprint create\` again, or restore the previous fingerprint.json. ${STILL_DIFFERS} Otherwise rebuild the ${label} app.` : `To keep updating ${label} builds you already shipped, go back to ${change.before}. ${STILL_DIFFERS} Otherwise run \`npx hot-updater fingerprint create\`, then rebuild the ${label} app.`
|
|
75
|
+
});
|
|
76
|
+
function isFingerprintEquals(lhs, rhs) {
|
|
77
|
+
if (!lhs || !rhs) return false;
|
|
78
|
+
if (isFingerprintResultsObject(lhs) && isFingerprintResultsObject(rhs)) return lhs.android.hash === rhs.android.hash && lhs.ios.hash === rhs.ios.hash;
|
|
79
|
+
if (!isFingerprintResultsObject(lhs) && !isFingerprintResultsObject(rhs)) return lhs["hash"] === rhs["hash"];
|
|
80
|
+
return false;
|
|
81
|
+
function isFingerprintResultsObject(result) {
|
|
82
|
+
return typeof result["android"] === "object" && typeof result["ios"] === "object" && !!result["android"]?.hash && !!result["ios"]?.hash;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/utils/fingerprint/nativeFingerprint.ts
|
|
87
|
+
/**
|
|
88
|
+
* Calculates the project's native fingerprint with the build adapter's
|
|
89
|
+
* fingerprint, or with React Native CLI's for an adapter without one.
|
|
90
|
+
*/
|
|
91
|
+
async function nativeFingerprint(path, options) {
|
|
92
|
+
const { provider, ...providerOptions } = options;
|
|
93
|
+
return provider ? provider(providerOptions) : reactNativeCliFingerprint(path, providerOptions);
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/utils/fingerprint/nativeSdk.ts
|
|
97
|
+
const SDK = "@hot-updater/react-native";
|
|
98
|
+
const ERROR_NAME = "NativeSdkWithoutBuildFingerprintError";
|
|
99
|
+
/**
|
|
100
|
+
* The installed React Native SDK predates build-time fingerprints: it reads
|
|
101
|
+
* the fingerprint from Info.plist and AndroidManifest.xml, which hot-updater
|
|
102
|
+
* no longer writes, so its builds would carry a stale one.
|
|
103
|
+
*/
|
|
104
|
+
var NativeSdkWithoutBuildFingerprintError = class extends Error {
|
|
105
|
+
constructor(version) {
|
|
106
|
+
super(`${SDK}@${version} reads the fingerprint from Info.plist and AndroidManifest.xml, which this hot-updater no longer writes, so its builds would carry a stale fingerprint. Update ${SDK} to the version of hot-updater, then rebuild the app.`);
|
|
107
|
+
this.name = ERROR_NAME;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const isNativeSdkWithoutBuildFingerprintError = (error) => error instanceof Error && error.name === ERROR_NAME;
|
|
111
|
+
/**
|
|
112
|
+
* Throws when the project's React Native SDK can't embed the fingerprint at
|
|
113
|
+
* build time, which it shows by shipping the build step that does.
|
|
114
|
+
*/
|
|
115
|
+
const assertNativeSdkEmbedsFingerprint = (cwd) => {
|
|
116
|
+
let root = null;
|
|
117
|
+
for (let dir = path$1.resolve(cwd);; dir = path$1.dirname(dir)) {
|
|
118
|
+
const candidate = path$1.join(dir, "node_modules", SDK);
|
|
119
|
+
if (fs$1.existsSync(path$1.join(candidate, "package.json"))) {
|
|
120
|
+
root = candidate;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
if (path$1.dirname(dir) === dir) break;
|
|
124
|
+
}
|
|
125
|
+
if (root === null) return;
|
|
126
|
+
const manifest = path$1.join(root, "package.json");
|
|
127
|
+
if (fs$1.existsSync(path$1.join(root, "android/hot-updater-fingerprint.gradle"))) return;
|
|
128
|
+
const { version } = JSON.parse(fs$1.readFileSync(manifest, "utf8"));
|
|
129
|
+
throw new NativeSdkWithoutBuildFingerprintError(version ?? "unknown");
|
|
130
|
+
};
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/utils/fingerprint/diff.ts
|
|
133
|
+
const sourceKey = (source) => source.type === "contents" ? `contents:${source.id}` : `${source.type}:${source.filePath}`;
|
|
134
|
+
/**
|
|
135
|
+
* Compares a saved fingerprint with the project's fingerprint now, source by
|
|
136
|
+
* source, whichever engine computes it.
|
|
137
|
+
*/
|
|
138
|
+
async function getFingerprintDiff(oldFingerprint, options) {
|
|
139
|
+
const current = await nativeFingerprint(getCwd(), options);
|
|
140
|
+
const before = new Map(oldFingerprint.sources.map((source) => [sourceKey(source), source]));
|
|
141
|
+
const after = new Map(current.sources.map((source) => [sourceKey(source), source]));
|
|
142
|
+
const diff = [];
|
|
143
|
+
for (const [key, beforeSource] of before) {
|
|
144
|
+
const afterSource = after.get(key);
|
|
145
|
+
if (!afterSource) diff.push({
|
|
146
|
+
op: "removed",
|
|
147
|
+
removedSource: beforeSource
|
|
148
|
+
});
|
|
149
|
+
else if (afterSource.hash !== beforeSource.hash) diff.push({
|
|
150
|
+
op: "changed",
|
|
151
|
+
beforeSource,
|
|
152
|
+
afterSource
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
for (const [key, afterSource] of after) if (!before.has(key)) diff.push({
|
|
156
|
+
op: "added",
|
|
157
|
+
addedSource: afterSource
|
|
158
|
+
});
|
|
159
|
+
return diff;
|
|
160
|
+
}
|
|
161
|
+
/** Contents short enough to show, such as `react-native-screens@4.25.2`. */
|
|
162
|
+
const readableContents = (source) => source.type === "contents" && typeof source.contents === "string" && source.contents.length <= 120 && !source.contents.includes("\n") ? source.contents : null;
|
|
163
|
+
/**
|
|
164
|
+
* A source as people read it: a file path, a value such as
|
|
165
|
+
* `react-native-screens@4.25.2` or `android:hermesEnabled=true`, or the
|
|
166
|
+
* source's id when its contents are long.
|
|
167
|
+
*/
|
|
168
|
+
function getSourcePath(source) {
|
|
169
|
+
if (!("id" in source)) return source.filePath;
|
|
170
|
+
const contents = readableContents(source);
|
|
171
|
+
if (contents === null) return source.id;
|
|
172
|
+
return source.id.startsWith("setting:") ? `${source.id.slice(8)}=${contents}` : source.id === "nativeVersion" ? `nativeVersion=${contents}` : contents;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* A changed source with its value before and after, such as
|
|
176
|
+
* `react-native-screens 4.25.2 → 4.26.0`, when both are short.
|
|
177
|
+
*/
|
|
178
|
+
function describeChange(before, after) {
|
|
179
|
+
const from = readableContents(before);
|
|
180
|
+
const to = readableContents(after);
|
|
181
|
+
if (from === null || to === null || !("id" in before)) return getSourcePath(before);
|
|
182
|
+
const at = from.lastIndexOf("@");
|
|
183
|
+
if (at > 0 && to.startsWith(from.slice(0, at + 1))) return `${from.slice(0, at)} ${from.slice(at + 1)} → ${to.slice(at + 1)}`;
|
|
184
|
+
return `${before.id.startsWith("setting:") ? before.id.slice(8) : before.id} ${from} → ${to}`;
|
|
185
|
+
}
|
|
186
|
+
function summarizeFingerprintDiff(diff) {
|
|
187
|
+
return {
|
|
188
|
+
added: diff.flatMap((item) => item.op === "added" ? [getSourcePath(item.addedSource)] : []),
|
|
189
|
+
removed: diff.flatMap((item) => item.op === "removed" ? [getSourcePath(item.removedSource)] : []),
|
|
190
|
+
changed: diff.flatMap((item) => item.op === "changed" ? [describeChange(item.beforeSource, item.afterSource)] : [])
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function showFingerprintChanges({ added, removed, changed }, platform) {
|
|
194
|
+
if (added.length + removed.length + changed.length === 0) return;
|
|
195
|
+
p.log.info(`${colors.bold(`${platform} Fingerprint Changes:`)}`);
|
|
196
|
+
if (added.length > 0) p.log.info(` ${colors.green("Added:")} ${added.join(", ")}`);
|
|
197
|
+
if (removed.length > 0) p.log.info(` ${colors.red("Removed:")} ${removed.join(", ")}`);
|
|
198
|
+
if (changed.length > 0) p.log.info(` ${colors.yellow("Changed:")} ${changed.join(", ")}`);
|
|
199
|
+
}
|
|
200
|
+
function showFingerprintDiff(diff, platform) {
|
|
201
|
+
showFingerprintChanges(summarizeFingerprintDiff(diff), platform);
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/utils/fingerprint/recorded.ts
|
|
205
|
+
/**
|
|
206
|
+
* The official build adapters whose fingerprint a native build computes from
|
|
207
|
+
* fingerprint.json alone: the package and the export that computes it.
|
|
208
|
+
*/
|
|
209
|
+
const ADAPTER_FINGERPRINTS = {
|
|
210
|
+
bare: {
|
|
211
|
+
packageName: "@hot-updater/bare",
|
|
212
|
+
exportName: "bareFingerprint"
|
|
213
|
+
},
|
|
214
|
+
rock: {
|
|
215
|
+
packageName: "@hot-updater/rock",
|
|
216
|
+
exportName: "rockFingerprint"
|
|
217
|
+
},
|
|
218
|
+
expo: {
|
|
219
|
+
packageName: "@hot-updater/expo",
|
|
220
|
+
exportName: "expoFingerprint"
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const loadAdapterFingerprint = async (cwd, { packageName, exportName }) => {
|
|
224
|
+
let entry;
|
|
225
|
+
try {
|
|
226
|
+
entry = createRequire(path$1.join(cwd, "package.json")).resolve(packageName);
|
|
227
|
+
} catch {
|
|
228
|
+
return "missing";
|
|
229
|
+
}
|
|
230
|
+
const module = await import(pathToFileURL(entry).href);
|
|
231
|
+
const fingerprint = module[exportName] ?? module.default?.[exportName];
|
|
232
|
+
return typeof fingerprint === "function" ? fingerprint : null;
|
|
233
|
+
};
|
|
234
|
+
/**
|
|
235
|
+
* The fingerprint a native build of `platform` embeds, computed from the
|
|
236
|
+
* inputs fingerprint.json records instead of hot-updater.config.ts, whose
|
|
237
|
+
* storage and database need deploy secrets a build machine may not have.
|
|
238
|
+
* `recorded` is the hash fingerprint.json holds for the platform.
|
|
239
|
+
*/
|
|
240
|
+
const computeRecordedFingerprintHash = async (platform) => {
|
|
241
|
+
const cwd = getCwd();
|
|
242
|
+
let file;
|
|
243
|
+
try {
|
|
244
|
+
file = JSON.parse(await fs$2.readFile(path$1.join(cwd, "fingerprint.json"), "utf8"));
|
|
245
|
+
} catch {
|
|
246
|
+
throw new Error("fingerprint.json was not found. Run `npx hot-updater fingerprint create`.");
|
|
247
|
+
}
|
|
248
|
+
const recorded = file[platform]?.hash ?? null;
|
|
249
|
+
const embedRecorded = (reason) => {
|
|
250
|
+
if (recorded === null) throw new Error(`fingerprint.json has no ${platform} fingerprint. Run \`npx hot-updater fingerprint create\`.`);
|
|
251
|
+
console.error(`warning: ${reason} The build embeds ${recorded}.`);
|
|
252
|
+
return {
|
|
253
|
+
hash: recorded,
|
|
254
|
+
recorded
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
const inputs = file.inputs;
|
|
258
|
+
if (!inputs) return embedRecorded("fingerprint.json records no inputs to compute the fingerprint from. Run `npx hot-updater fingerprint create`.");
|
|
259
|
+
const options = {
|
|
260
|
+
platform,
|
|
261
|
+
...inputs.extraSources === void 0 ? {} : { extraSources: inputs.extraSources },
|
|
262
|
+
...inputs.ignorePaths === void 0 ? {} : { ignorePaths: inputs.ignorePaths },
|
|
263
|
+
...inputs.nativeVersion === void 0 ? {} : { nativeVersion: inputs.nativeVersion }
|
|
264
|
+
};
|
|
265
|
+
const adapter = ADAPTER_FINGERPRINTS[inputs.adapter];
|
|
266
|
+
if (adapter) {
|
|
267
|
+
const fingerprint = await loadAdapterFingerprint(cwd, adapter);
|
|
268
|
+
if (fingerprint === "missing") return embedRecorded(`fingerprint.json records the ${inputs.adapter} build adapter, but ${adapter.packageName} is not installed. Run \`npx hot-updater fingerprint create\`.`);
|
|
269
|
+
if (!fingerprint) return embedRecorded(`The installed ${adapter.packageName} does not export ${adapter.exportName}; update it to compute the fingerprint at build time.`);
|
|
270
|
+
return {
|
|
271
|
+
hash: (await fingerprint(cwd, options)).hash,
|
|
272
|
+
recorded
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
if (inputs.adapter === "react-native") return {
|
|
276
|
+
hash: (await reactNativeCliFingerprint(cwd, options)).hash,
|
|
277
|
+
recorded
|
|
278
|
+
};
|
|
279
|
+
return embedRecorded(`The "${inputs.adapter}" build adapter's fingerprint needs hot-updater.config.ts.`);
|
|
280
|
+
};
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/utils/fingerprint/index.ts
|
|
283
|
+
const generateFingerprints = async (additionalExtraSources) => {
|
|
284
|
+
const fingerprintConfig = await ensureFingerprintConfig(additionalExtraSources);
|
|
285
|
+
const projectPath = getCwd();
|
|
286
|
+
assertNativeSdkEmbedsFingerprint(projectPath);
|
|
287
|
+
const [ios, android] = await Promise.all([nativeFingerprint(projectPath, {
|
|
288
|
+
platform: "ios",
|
|
289
|
+
...fingerprintConfig
|
|
290
|
+
}), nativeFingerprint(projectPath, {
|
|
291
|
+
platform: "android",
|
|
292
|
+
...fingerprintConfig
|
|
293
|
+
})]);
|
|
294
|
+
return {
|
|
295
|
+
ios,
|
|
296
|
+
android,
|
|
297
|
+
inputs: getFingerprintInputs(fingerprintConfig)
|
|
298
|
+
};
|
|
299
|
+
};
|
|
300
|
+
const generateFingerprint = async (platform) => {
|
|
301
|
+
const fingerprintConfig = await ensureFingerprintConfig();
|
|
302
|
+
return nativeFingerprint(getCwd(), {
|
|
303
|
+
platform,
|
|
304
|
+
...fingerprintConfig
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
/**
|
|
308
|
+
* Writes fingerprint.json, which native builds read to embed each platform's
|
|
309
|
+
* hash. With `platform`, only that platform's fingerprint is replaced.
|
|
310
|
+
*/
|
|
311
|
+
const createAndInjectFingerprintFiles = async ({ platform } = {}) => {
|
|
312
|
+
const localFingerprint = await readLocalFingerprint();
|
|
313
|
+
const newFingerprint = await generateFingerprints();
|
|
314
|
+
await createFingerprintJSON(!localFingerprint || !platform ? newFingerprint : {
|
|
315
|
+
android: localFingerprint.android || newFingerprint.android,
|
|
316
|
+
ios: localFingerprint.ios || newFingerprint.ios,
|
|
317
|
+
[platform]: newFingerprint[platform],
|
|
318
|
+
inputs: newFingerprint.inputs
|
|
319
|
+
});
|
|
320
|
+
return { fingerprint: newFingerprint };
|
|
321
|
+
};
|
|
322
|
+
/**
|
|
323
|
+
* Brings fingerprint.json to `fingerprint`, writing it only when a hash or the
|
|
324
|
+
* recorded inputs differ: what `fingerprint create` writes, without rewriting
|
|
325
|
+
* a file that already matches. Returns whether it wrote.
|
|
326
|
+
*/
|
|
327
|
+
const syncFingerprintFiles = async (fingerprint) => {
|
|
328
|
+
const local = await readLocalFingerprint();
|
|
329
|
+
const fingerprintJson = !isFingerprintEquals(local, fingerprint) || JSON.stringify(local?.inputs) !== JSON.stringify(fingerprint.inputs) || ["ios", "android"].some((platform) => local?.[platform]?.engine !== fingerprint[platform].engine);
|
|
330
|
+
if (fingerprintJson) await createFingerprintJSON(fingerprint);
|
|
331
|
+
return { fingerprintJson };
|
|
332
|
+
};
|
|
333
|
+
const createFingerprintJSON = async (fingerprint) => {
|
|
334
|
+
const FINGERPRINT_FILE_PATH = path.join(getCwd(), "fingerprint.json");
|
|
335
|
+
await fs.promises.writeFile(FINGERPRINT_FILE_PATH, JSON.stringify(fingerprint, null, 2));
|
|
336
|
+
return fingerprint;
|
|
337
|
+
};
|
|
338
|
+
const readLocalFingerprint = async () => {
|
|
339
|
+
const FINGERPRINT_FILE_PATH = path.join(getCwd(), "fingerprint.json");
|
|
340
|
+
try {
|
|
341
|
+
const content = await fs.promises.readFile(FINGERPRINT_FILE_PATH, "utf-8");
|
|
342
|
+
return JSON.parse(content);
|
|
343
|
+
} catch {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
//#endregion
|
|
348
|
+
export { ensureFingerprintConfig as _, readLocalFingerprint as a, getFingerprintDiff as c, summarizeFingerprintDiff as d, assertNativeSdkEmbedsFingerprint as f, describeFingerprintEngineChange as g, appendFingerprintExtraSources as h, generateFingerprints as i, showFingerprintChanges as l, nativeFingerprint as m, createFingerprintJSON as n, syncFingerprintFiles as o, isNativeSdkWithoutBuildFingerprintError as p, generateFingerprint as r, computeRecordedFingerprintHash as s, createAndInjectFingerprintFiles as t, showFingerprintDiff as u, getFingerprintEngineChange as v, isFingerprintEquals as y };
|