hot-updater 1.0.8 → 1.1.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.
Files changed (31) hide show
  1. package/dist/config.d.mts +23 -38
  2. package/dist/config.mjs +1 -1
  3. package/dist/fingerprint-D0bHaE2B.mjs +348 -0
  4. package/dist/index.mjs +30179 -9254
  5. package/dist/infra-templates/aws/lambda/index.cjs +1 -1
  6. package/dist/infra-templates/aws/template.json +10 -10
  7. package/dist/infra-templates/azure/azure/function/index.mjs +1 -1
  8. package/dist/infra-templates/azure/template.json +11 -11
  9. package/dist/infra-templates/cloudflare/template.json +10 -10
  10. package/dist/infra-templates/cloudflare/worker/dist/README.md +1 -1
  11. package/dist/infra-templates/cloudflare/worker/dist/index.js +1 -1
  12. package/dist/infra-templates/cloudflare/worker/dist/index.js.map +1 -1
  13. package/dist/infra-templates/firebase/firebase/functions/index.cjs +1 -1
  14. package/dist/infra-templates/firebase/template.json +11 -11
  15. package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-plugin-core/dist/index.d.cts +2 -2
  16. package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-plugin-core/dist/index.d.mts +2 -2
  17. package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-plugin-core/dist/types/index.d.cts +63 -2
  18. package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-plugin-core/dist/types/index.d.mts +63 -2
  19. package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-plugin-insights/dist/client/eventId.cjs +4 -2
  20. package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-plugin-insights/dist/client/eventId.mjs +4 -2
  21. package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-plugin-insights/dist/client/index.cjs +17 -11
  22. package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-plugin-insights/dist/client/index.mjs +17 -11
  23. package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-protocol/dist/index.d.cts +36 -0
  24. package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-protocol/dist/index.d.mts +36 -0
  25. package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-server/dist/package.cjs +1 -1
  26. package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-server/dist/package.mjs +1 -1
  27. package/dist/infra-templates/supabase/template.json +10 -10
  28. package/dist/infra-templates/vercel/template.json +11 -11
  29. package/dist/infra-templates/vercel/vercel/api/index.mjs +1 -1
  30. package/package.json +24 -29
  31. 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
- type FingerprintSource = {
5
- type: "file" | "dir";
6
- filePath: string;
7
- reasons: string[];
8
- overrideHashKey?: string;
9
- hash: string | null;
10
- debugInfo?: any;
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
- declare const generateFingerprint: (platform: "ios" | "android") => Promise<FingerprintResult>;
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 { i as generateFingerprints, n as createFingerprintJSON, o as readLocalFingerprint, r as generateFingerprint, t as createAndInjectFingerprintFiles } from "./fingerprint-DyKBEn51.mjs";
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 };