react-native-update 10.37.15 → 10.37.16

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 (68) hide show
  1. package/README-CN.md +72 -0
  2. package/README.md +61 -49
  3. package/bunfig.toml +2 -0
  4. package/harmony/har-wrapper/AppScope/app.json5 +8 -0
  5. package/harmony/har-wrapper/build-profile.json5 +35 -0
  6. package/harmony/har-wrapper/hvigor/hvigor-config.json5 +5 -0
  7. package/harmony/har-wrapper/hvigorfile.ts +6 -0
  8. package/harmony/har-wrapper/oh-package.json5 +4 -0
  9. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/.cmake/api/v1/reply/cache-v2-77b153ce45aba0ed28ef.json +1415 -0
  10. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/.cmake/api/v1/reply/cmakeFiles-v1-b65a07793384e0ce3e08.json +809 -0
  11. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/.cmake/api/v1/reply/codemodel-v2-ce0e89410afd8bf3a057.json +60 -0
  12. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/.cmake/api/v1/reply/directory-.-Release-f5ebdc15457944623624.json +14 -0
  13. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/.cmake/api/v1/reply/index-2026-03-16T14-00-08-0134.json +89 -0
  14. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/.cmake/api/v1/reply/target-rnupdate-Release-267153624504c9c3ffdd.json +222 -0
  15. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/.ninja_deps +0 -0
  16. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/.ninja_log +8 -0
  17. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeCache.txt +415 -0
  18. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CMakeCCompiler.cmake +74 -0
  19. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CMakeCXXCompiler.cmake +85 -0
  20. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CMakeDetermineCompilerABI_C.bin +0 -0
  21. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CMakeDetermineCompilerABI_CXX.bin +0 -0
  22. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CMakeSystem.cmake +15 -0
  23. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CompilerIdC/CMakeCCompilerId.c +880 -0
  24. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CompilerIdC/CMakeCCompilerId.o +0 -0
  25. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CompilerIdCXX/CMakeCXXCompilerId.cpp +869 -0
  26. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CompilerIdCXX/CMakeCXXCompilerId.o +0 -0
  27. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/CMakeConfigureLog.yaml +388 -0
  28. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/TargetDirectories.txt +3 -0
  29. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/cmake.check_cache +1 -0
  30. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/rnupdate.dir/__w/react-native-update/react-native-update/android/jni/HDiffPatch/file_for_patch.c.o +0 -0
  31. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/rnupdate.dir/__w/react-native-update/react-native-update/android/jni/HDiffPatch/libHDiffPatch/HPatch/patch.c.o +0 -0
  32. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/rnupdate.dir/__w/react-native-update/react-native-update/android/jni/hpatch.c.o +0 -0
  33. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/rnupdate.dir/__w/react-native-update/react-native-update/android/jni/lzma/C/Lzma2Dec.c.o +0 -0
  34. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/rnupdate.dir/__w/react-native-update/react-native-update/android/jni/lzma/C/LzmaDec.c.o +0 -0
  35. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/rnupdate.dir/pushy.c.o +0 -0
  36. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/rules.ninja +64 -0
  37. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/additional_project_files.txt +0 -0
  38. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/build.ninja +206 -0
  39. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/build_file_index.txt +1 -0
  40. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/cmake_install.cmake +54 -0
  41. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/compile_commands.json +38 -0
  42. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/configure_fingerprint.json +1 -0
  43. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/hvigor_native_config.json +1 -0
  44. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/metadata_generation_command.txt +17 -0
  45. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/native_work_dir.txt +1 -0
  46. package/harmony/pushy/.cxx/default/default/release/arm64-v8a/output.log +14 -0
  47. package/harmony/pushy/.cxx/default/default/release/hvigor/arm64-v8a/summary.cmake +0 -0
  48. package/harmony/pushy/BuildProfile.ets +3 -5
  49. package/harmony/pushy/build-profile.json5 +8 -1
  50. package/harmony/pushy/oh-package-lock.json5 +1 -1
  51. package/harmony/pushy/src/main/cpp/CMakeLists.txt +42 -30
  52. package/harmony/pushy/src/main/ets/DownloadTask.ts +145 -106
  53. package/harmony/pushy/src/main/ets/Logger.ts +24 -7
  54. package/harmony/pushy/src/main/ets/PushyFileJSBundleProvider.ets +1 -1
  55. package/harmony/pushy/src/main/ets/PushyTurboModule.ts +9 -73
  56. package/harmony/pushy/src/main/ets/UpdateContext.ts +206 -70
  57. package/harmony/pushy.har +0 -0
  58. package/package.json +2 -1
  59. package/scripts/build-harmony-har.js +427 -0
  60. package/scripts/prepublish.ts +66 -4
  61. package/src/__tests__/core.test.ts +103 -0
  62. package/src/__tests__/setup.ts +37 -0
  63. package/src/__tests__/utils.test.ts +36 -0
  64. package/src/client.ts +14 -8
  65. package/src/core.ts +5 -5
  66. package/src/locales/en.ts +1 -0
  67. package/src/locales/zh.ts +1 -0
  68. package/src/utils.ts +13 -1
@@ -0,0 +1,427 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { spawnSync } = require('child_process');
6
+
7
+ const projectRoot = path.resolve(__dirname, '..');
8
+ const androidJniDir = path.join(projectRoot, 'android', 'jni');
9
+ const harmonyModuleDir = path.join(projectRoot, 'harmony', 'pushy');
10
+ const harmonyBuildDir = path.join(harmonyModuleDir, 'build');
11
+ const harmonyNativeStageDir = path.join(
12
+ harmonyModuleDir,
13
+ 'src',
14
+ 'main',
15
+ 'cpp',
16
+ 'android-generated',
17
+ );
18
+ const harmonyNativeStageJniDir = path.join(harmonyNativeStageDir, 'jni');
19
+ const wrapperProjectDir = path.join(projectRoot, 'harmony', 'har-wrapper');
20
+ const defaultOutputPath = path.join(projectRoot, 'harmony', 'pushy.har');
21
+ const wrapperProjectFiles = [
22
+ 'hvigorfile.ts',
23
+ path.join('hvigor', 'hvigor-config.json5'),
24
+ 'oh-package.json5',
25
+ path.join('AppScope', 'app.json5'),
26
+ 'build-profile.json5',
27
+ ];
28
+
29
+ const args = parseArgs(process.argv.slice(2));
30
+ const buildMode = normalizeBuildMode(
31
+ args['build-mode'] || process.env.HARMONY_BUILD_MODE || 'debug',
32
+ );
33
+ const skipInstall =
34
+ args['skip-install'] || process.env.HARMONY_SKIP_INSTALL === '1';
35
+ const outputDir = args['out-dir']
36
+ ? path.resolve(projectRoot, args['out-dir'])
37
+ : process.env.HARMONY_HAR_OUTPUT_DIR
38
+ ? path.resolve(projectRoot, process.env.HARMONY_HAR_OUTPUT_DIR)
39
+ : null;
40
+ const outputPath = args['out-file']
41
+ ? path.resolve(projectRoot, args['out-file'])
42
+ : process.env.HARMONY_HAR_OUTPUT_PATH
43
+ ? path.resolve(projectRoot, process.env.HARMONY_HAR_OUTPUT_PATH)
44
+ : outputDir
45
+ ? null
46
+ : defaultOutputPath;
47
+
48
+ try {
49
+ main();
50
+ } catch (error) {
51
+ console.error(error instanceof Error ? error.message : String(error));
52
+ process.exit(1);
53
+ }
54
+
55
+ function main() {
56
+ syncHarmonyNativeSources();
57
+ let buildError = null;
58
+
59
+ try {
60
+ buildHar();
61
+ } catch (error) {
62
+ buildError = error;
63
+ }
64
+
65
+ try {
66
+ cleanupHarmonyNativeSources();
67
+ } catch (error) {
68
+ if (!buildError) {
69
+ buildError = error;
70
+ } else {
71
+ console.warn(
72
+ `Warning: failed to clean staged Harmony native sources: ${
73
+ error instanceof Error ? error.message : String(error)
74
+ }`,
75
+ );
76
+ }
77
+ }
78
+
79
+ if (buildError) {
80
+ throw buildError;
81
+ }
82
+ }
83
+
84
+ function buildHar() {
85
+ ensureWrapperProject();
86
+
87
+ const devecoRoots = getDevEcoRoots();
88
+ const hvigorwPath = resolveBinary('hvigorw', [
89
+ process.env.HVIGORW_PATH,
90
+ ...devecoRoots.map((root) =>
91
+ path.join(root, 'tools', 'hvigor', 'bin', 'hvigorw'),
92
+ ),
93
+ ]);
94
+ const ohpmPath = resolveBinary('ohpm', [
95
+ process.env.OHPM_PATH,
96
+ ...devecoRoots.map((root) =>
97
+ path.join(root, 'tools', 'ohpm', 'bin', 'ohpm'),
98
+ ),
99
+ ]);
100
+
101
+ if (!hvigorwPath) {
102
+ fail(
103
+ 'Cannot find hvigorw. Set HVIGORW_PATH or install DevEco Studio.',
104
+ );
105
+ }
106
+
107
+ if (!ohpmPath) {
108
+ fail('Cannot find ohpm. Set OHPM_PATH or install DevEco Studio.');
109
+ }
110
+
111
+ const env = {
112
+ ...process.env,
113
+ };
114
+
115
+ if (!env.DEVECO_SDK_HOME) {
116
+ const devecoSdkHome = findExistingPath(
117
+ devecoRoots.map((root) => path.join(root, 'sdk')),
118
+ );
119
+ if (devecoSdkHome) {
120
+ env.DEVECO_SDK_HOME = devecoSdkHome;
121
+ }
122
+ }
123
+
124
+ if (!env.DEVECO_STUDIO_HOME) {
125
+ const devecoStudioHome = findExistingPath(devecoRoots);
126
+ if (devecoStudioHome) {
127
+ env.DEVECO_STUDIO_HOME = devecoStudioHome;
128
+ }
129
+ }
130
+
131
+ if (!skipInstall) {
132
+ runCommand(ohpmPath, ['install'], {
133
+ cwd: harmonyModuleDir,
134
+ env,
135
+ label: 'Install Harmony dependencies',
136
+ });
137
+
138
+ runCommand(ohpmPath, ['install'], {
139
+ cwd: wrapperProjectDir,
140
+ env,
141
+ label: 'Install wrapper project dependencies',
142
+ });
143
+ }
144
+
145
+ const hvigorArgs = ['assembleHar'];
146
+ if (buildMode !== 'debug') {
147
+ hvigorArgs.push('-p', `buildMode=${buildMode}`);
148
+ }
149
+
150
+ runCommand(hvigorwPath, hvigorArgs, {
151
+ cwd: wrapperProjectDir,
152
+ env,
153
+ label: `Build Harmony HAR (${buildMode})`,
154
+ });
155
+
156
+ const harPath = findNewestHar(harmonyBuildDir);
157
+ if (!harPath) {
158
+ fail(
159
+ `Build finished but no .har artifact was found under ${relativeToProject(
160
+ harmonyBuildDir,
161
+ )}`,
162
+ );
163
+ }
164
+
165
+ let finalPath = harPath;
166
+ if (outputDir || outputPath) {
167
+ finalPath = outputPath || path.join(outputDir, path.basename(harPath));
168
+ fs.mkdirSync(path.dirname(finalPath), { recursive: true });
169
+ fs.copyFileSync(harPath, finalPath);
170
+ }
171
+
172
+ console.log(`HAR package ready: ${finalPath}`);
173
+ }
174
+
175
+ function syncHarmonyNativeSources() {
176
+ ensureFileExists(
177
+ path.join(androidJniDir, 'hpatch.c'),
178
+ `Missing Android native source: ${relativeToProject(
179
+ path.join(androidJniDir, 'hpatch.c'),
180
+ )}`,
181
+ );
182
+ ensureFileExists(
183
+ path.join(androidJniDir, 'hpatch.h'),
184
+ `Missing Android native source: ${relativeToProject(
185
+ path.join(androidJniDir, 'hpatch.h'),
186
+ )}`,
187
+ );
188
+ ensureFileExists(
189
+ path.join(androidJniDir, 'HDiffPatch'),
190
+ `Missing Android native source directory: ${relativeToProject(
191
+ path.join(androidJniDir, 'HDiffPatch'),
192
+ )}`,
193
+ );
194
+ ensureFileExists(
195
+ path.join(androidJniDir, 'lzma', 'C'),
196
+ `Missing Android native source directory: ${relativeToProject(
197
+ path.join(androidJniDir, 'lzma', 'C'),
198
+ )}`,
199
+ );
200
+
201
+ fs.rmSync(harmonyNativeStageDir, { recursive: true, force: true });
202
+ fs.mkdirSync(path.join(harmonyNativeStageJniDir, 'lzma'), {
203
+ recursive: true,
204
+ });
205
+
206
+ copyPath(
207
+ path.join(androidJniDir, 'hpatch.c'),
208
+ path.join(harmonyNativeStageJniDir, 'hpatch.c'),
209
+ );
210
+ copyPath(
211
+ path.join(androidJniDir, 'hpatch.h'),
212
+ path.join(harmonyNativeStageJniDir, 'hpatch.h'),
213
+ );
214
+ copyPath(
215
+ path.join(androidJniDir, 'HDiffPatch'),
216
+ path.join(harmonyNativeStageJniDir, 'HDiffPatch'),
217
+ );
218
+ copyPath(
219
+ path.join(androidJniDir, 'lzma', 'C'),
220
+ path.join(harmonyNativeStageJniDir, 'lzma', 'C'),
221
+ );
222
+ }
223
+
224
+ function cleanupHarmonyNativeSources() {
225
+ fs.rmSync(harmonyNativeStageDir, { recursive: true, force: true });
226
+ }
227
+
228
+ function copyPath(sourcePath, targetPath) {
229
+ const stats = fs.statSync(sourcePath);
230
+ if (stats.isDirectory()) {
231
+ fs.cpSync(sourcePath, targetPath, {
232
+ recursive: true,
233
+ force: true,
234
+ filter: (entry) => path.basename(entry) !== '.git',
235
+ });
236
+ return;
237
+ }
238
+
239
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
240
+ fs.copyFileSync(sourcePath, targetPath);
241
+ }
242
+
243
+ function ensureWrapperProject() {
244
+ wrapperProjectFiles.forEach((relativePath) => {
245
+ const fullPath = path.join(wrapperProjectDir, relativePath);
246
+ ensureFileExists(
247
+ fullPath,
248
+ `Missing Harmony wrapper file: ${relativeToProject(fullPath)}`,
249
+ );
250
+ });
251
+ }
252
+
253
+ function parseArgs(argv) {
254
+ const parsed = {};
255
+
256
+ for (let index = 0; index < argv.length; index += 1) {
257
+ const token = argv[index];
258
+ if (!token.startsWith('--')) {
259
+ fail(`Unsupported argument: ${token}`);
260
+ }
261
+
262
+ const keyValue = token.slice(2).split('=');
263
+ const key = keyValue[0];
264
+ const inlineValue = keyValue.length > 1 ? keyValue.slice(1).join('=') : '';
265
+
266
+ if (key === 'skip-install') {
267
+ parsed[key] = true;
268
+ continue;
269
+ }
270
+
271
+ const value = inlineValue || argv[index + 1];
272
+ if (!value || value.startsWith('--')) {
273
+ fail(`Missing value for --${key}`);
274
+ }
275
+
276
+ parsed[key] = value;
277
+ if (!inlineValue) {
278
+ index += 1;
279
+ }
280
+ }
281
+
282
+ return parsed;
283
+ }
284
+
285
+ function normalizeBuildMode(value) {
286
+ const mode = String(value).toLowerCase();
287
+ if (mode === 'debug' || mode === 'release') {
288
+ return mode;
289
+ }
290
+
291
+ fail(`Unsupported build mode: ${value}. Use debug or release.`);
292
+ }
293
+
294
+ function getDevEcoRoots() {
295
+ const roots = new Set();
296
+ const envCandidates = [
297
+ process.env.DEVECO_STUDIO_HOME,
298
+ process.env.DEVECO_SDK_HOME,
299
+ ].filter(Boolean);
300
+
301
+ envCandidates.forEach((candidate) => {
302
+ const normalized = normalizeDevEcoRoot(candidate);
303
+ if (normalized) {
304
+ roots.add(normalized);
305
+ }
306
+ });
307
+
308
+ roots.add('/Applications/DevEco-Studio.app/Contents');
309
+ return Array.from(roots);
310
+ }
311
+
312
+ function normalizeDevEcoRoot(value) {
313
+ const resolved = path.resolve(value);
314
+ const basename = path.basename(resolved);
315
+
316
+ if (basename === 'sdk') {
317
+ return path.dirname(resolved);
318
+ }
319
+
320
+ if (basename === 'Contents') {
321
+ return resolved;
322
+ }
323
+
324
+ if (resolved.endsWith('.app')) {
325
+ return path.join(resolved, 'Contents');
326
+ }
327
+
328
+ if (fs.existsSync(path.join(resolved, 'Contents', 'tools'))) {
329
+ return path.join(resolved, 'Contents');
330
+ }
331
+
332
+ return resolved;
333
+ }
334
+
335
+ function resolveBinary(name, candidates) {
336
+ const explicitPath = findExistingPath(candidates);
337
+ if (explicitPath) {
338
+ return explicitPath;
339
+ }
340
+
341
+ const whichResult = spawnSync('bash', ['-lc', `command -v ${name}`], {
342
+ encoding: 'utf8',
343
+ });
344
+ if (whichResult.status === 0) {
345
+ const resolved = whichResult.stdout.trim();
346
+ if (resolved) {
347
+ return resolved;
348
+ }
349
+ }
350
+
351
+ return null;
352
+ }
353
+
354
+ function findExistingPath(candidates) {
355
+ for (const candidate of candidates) {
356
+ if (candidate && fs.existsSync(candidate)) {
357
+ return candidate;
358
+ }
359
+ }
360
+
361
+ return null;
362
+ }
363
+
364
+ function runCommand(command, commandArgs, options) {
365
+ const { cwd, env, label } = options;
366
+ console.log(`> ${label}`);
367
+ console.log(` ${[command, ...commandArgs].join(' ')}`);
368
+
369
+ const result = spawnSync(command, commandArgs, {
370
+ cwd,
371
+ env,
372
+ stdio: 'inherit',
373
+ });
374
+
375
+ if (result.status !== 0) {
376
+ fail(`${label} failed with exit code ${result.status || 1}.`);
377
+ }
378
+ }
379
+
380
+ function findNewestHar(rootDir) {
381
+ if (!fs.existsSync(rootDir)) {
382
+ return null;
383
+ }
384
+
385
+ let latestFile = null;
386
+ let latestMtime = 0;
387
+ const queue = [rootDir];
388
+
389
+ while (queue.length > 0) {
390
+ const currentDir = queue.pop();
391
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
392
+
393
+ for (const entry of entries) {
394
+ const fullPath = path.join(currentDir, entry.name);
395
+ if (entry.isDirectory()) {
396
+ queue.push(fullPath);
397
+ continue;
398
+ }
399
+
400
+ if (!entry.isFile() || !entry.name.endsWith('.har')) {
401
+ continue;
402
+ }
403
+
404
+ const stat = fs.statSync(fullPath);
405
+ if (!latestFile || stat.mtimeMs > latestMtime) {
406
+ latestFile = fullPath;
407
+ latestMtime = stat.mtimeMs;
408
+ }
409
+ }
410
+ }
411
+
412
+ return latestFile;
413
+ }
414
+
415
+ function ensureFileExists(filePath, message) {
416
+ if (!fs.existsSync(filePath)) {
417
+ fail(message);
418
+ }
419
+ }
420
+
421
+ function relativeToProject(filePath) {
422
+ return path.relative(projectRoot, filePath) || '.';
423
+ }
424
+
425
+ function fail(message) {
426
+ throw new Error(message);
427
+ }
@@ -4,6 +4,70 @@ import { access, readFile, writeFile } from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import { $ } from 'bun';
6
6
 
7
+ function normalizeVersion(version: string): string {
8
+ return version.trim().replace(/^refs\/tags\//, '').replace(/^v/, '');
9
+ }
10
+
11
+ function getVersionFromEnvironment(): string | null {
12
+ const candidates = [
13
+ process.env.RELEASE_VERSION,
14
+ process.env.CI_COMMIT_TAG,
15
+ process.env.GITHUB_REF_TYPE === 'tag' ? process.env.GITHUB_REF_NAME : null,
16
+ process.env.GITHUB_REF?.startsWith('refs/tags/')
17
+ ? process.env.GITHUB_REF
18
+ : null,
19
+ ];
20
+
21
+ for (const candidate of candidates) {
22
+ if (candidate?.trim()) {
23
+ return normalizeVersion(candidate);
24
+ }
25
+ }
26
+
27
+ return null;
28
+ }
29
+
30
+ function getShellErrorMessage(error: unknown): string {
31
+ if (
32
+ typeof error === 'object' &&
33
+ error !== null &&
34
+ 'stderr' in error &&
35
+ typeof error.stderr === 'string' &&
36
+ error.stderr.trim()
37
+ ) {
38
+ return error.stderr.trim();
39
+ }
40
+
41
+ if (error instanceof Error && error.message.trim()) {
42
+ return error.message.trim();
43
+ }
44
+
45
+ return 'Unknown git error';
46
+ }
47
+
48
+ async function getVersionFromGit(): Promise<string> {
49
+ try {
50
+ return normalizeVersion(await $`git describe --tags --always`.text());
51
+ } catch (error) {
52
+ const message = getShellErrorMessage(error);
53
+
54
+ if (message.includes('detected dubious ownership')) {
55
+ throw new Error(
56
+ 'Git refused to read repository metadata because this checkout is not marked as safe. Configure safe.directory in CI or provide RELEASE_VERSION/GITHUB_REF_NAME.',
57
+ { cause: error },
58
+ );
59
+ }
60
+
61
+ throw new Error(`Unable to resolve publish version from git: ${message}`, {
62
+ cause: error,
63
+ });
64
+ }
65
+ }
66
+
67
+ async function resolveVersion(): Promise<string> {
68
+ return getVersionFromEnvironment() ?? (await getVersionFromGit());
69
+ }
70
+
7
71
  async function modifyPackageJson({
8
72
  version,
9
73
  }: {
@@ -35,17 +99,15 @@ async function modifyPackageJson({
35
99
  }
36
100
 
37
101
  async function main(): Promise<void> {
38
- const version = (await $`git describe --tags --always`.text())
39
- .replace('v', '')
40
- .trim();
41
102
  try {
103
+ const version = await resolveVersion();
104
+ console.log(`Using publish version ${version}`);
42
105
  await modifyPackageJson({ version });
43
106
  console.log('✅ Prepublish script completed successfully');
44
107
  } catch (error) {
45
108
  console.error('❌ Prepublish script failed:', error);
46
109
  process.exit(1);
47
110
  }
48
-
49
111
  }
50
112
 
51
113
  main();
@@ -0,0 +1,103 @@
1
+ import { describe, expect, test, mock } from 'bun:test';
2
+
3
+ // In Bun, top-level imports are cached.
4
+ // We can use mock.module to change the implementation of a module,
5
+ // but if a module has already been executed (like core.ts),
6
+ // re-importing it might not re-run the top-level code unless we use some tricks
7
+ // or run tests in isolation.
8
+ // Actually, bun test runs each file in its own environment usually,
9
+ // BUT if we run multiple test files in one process, they might share the cache.
10
+ const importFreshCore = (cacheKey: string) => import(`../core?${cacheKey}`);
11
+
12
+ describe('core info parsing', () => {
13
+ test('should call error when currentVersionInfo is invalid JSON', async () => {
14
+ const mockError = mock(() => {});
15
+
16
+ mock.module('react-native', () => ({
17
+ Platform: {
18
+ OS: 'ios',
19
+ Version: 13,
20
+ },
21
+ NativeModules: {
22
+ Pushy: {
23
+ currentVersionInfo: '{invalid}',
24
+ downloadRootDir: '/tmp',
25
+ packageVersion: '1.0.0',
26
+ currentVersion: 'hash1',
27
+ isFirstTime: false,
28
+ rolledBackVersion: '',
29
+ buildTime: '2023-01-01',
30
+ uuid: 'existing-uuid',
31
+ setLocalHashInfo: mock(() => {}),
32
+ getLocalHashInfo: mock(() => Promise.resolve('{}')),
33
+ setUuid: mock(() => {}),
34
+ },
35
+ },
36
+ NativeEventEmitter: class {
37
+ addListener = mock(() => ({ remove: mock(() => {}) }));
38
+ },
39
+ }));
40
+
41
+ mock.module('react-native/Libraries/Core/ReactNativeVersion', () => ({
42
+ version: { major: 0, minor: 73, patch: 0 },
43
+ }));
44
+
45
+ mock.module('nanoid/non-secure', () => ({
46
+ nanoid: () => 'mock-uuid',
47
+ }));
48
+
49
+ mock.module('../utils', () => ({
50
+ error: mockError,
51
+ log: mock(() => {}),
52
+ emptyModule: {},
53
+ }));
54
+
55
+ // Use a unique query parameter to bypass cache if supported, or just rely on fresh environment per file.
56
+ // In Bun, you can sometimes use a cache buster if it's dynamic import.
57
+ await importFreshCore('error');
58
+
59
+ expect(mockError).toHaveBeenCalledWith(
60
+ expect.stringContaining('error_parse_version_info')
61
+ );
62
+ });
63
+
64
+ test('should not call error when currentVersionInfo is valid JSON', async () => {
65
+ const mockError = mock(() => {});
66
+ const mockSetLocalHashInfo = mock(() => {});
67
+
68
+ mock.module('react-native', () => ({
69
+ Platform: {
70
+ OS: 'ios',
71
+ Version: 13,
72
+ },
73
+ NativeModules: {
74
+ Pushy: {
75
+ currentVersionInfo: JSON.stringify({ name: 'v1', debugChannel: true }),
76
+ downloadRootDir: '/tmp',
77
+ packageVersion: '1.0.0',
78
+ currentVersion: 'hash1',
79
+ isFirstTime: false,
80
+ rolledBackVersion: '',
81
+ buildTime: '2023-01-01',
82
+ uuid: 'existing-uuid',
83
+ setLocalHashInfo: mockSetLocalHashInfo,
84
+ getLocalHashInfo: mock(() => Promise.resolve('{}')),
85
+ setUuid: mock(() => {}),
86
+ },
87
+ },
88
+ NativeEventEmitter: class {
89
+ addListener = mock(() => ({ remove: mock(() => {}) }));
90
+ },
91
+ }));
92
+
93
+ mock.module('../utils', () => ({
94
+ error: mockError,
95
+ log: mock(() => {}),
96
+ emptyModule: {},
97
+ }));
98
+
99
+ await importFreshCore('success');
100
+
101
+ expect(mockError).not.toHaveBeenCalled();
102
+ });
103
+ });
@@ -0,0 +1,37 @@
1
+ import { mock } from 'bun:test';
2
+
3
+ mock.module('react-native', () => {
4
+ return {
5
+ Platform: {
6
+ OS: 'ios',
7
+ Version: 13,
8
+ },
9
+ NativeModules: {
10
+ Pushy: {
11
+ currentVersionInfo: '{}',
12
+ downloadRootDir: '/tmp',
13
+ packageVersion: '1.0.0',
14
+ currentVersion: 'hash',
15
+ isFirstTime: false,
16
+ rolledBackVersion: '',
17
+ buildTime: '2023-01-01',
18
+ uuid: 'uuid',
19
+ setLocalHashInfo: () => {},
20
+ getLocalHashInfo: () => Promise.resolve('{}'),
21
+ setUuid: () => {},
22
+ },
23
+ },
24
+ NativeEventEmitter: class {
25
+ addListener = () => ({ remove: () => {} });
26
+ removeAllListeners = () => {};
27
+ },
28
+ };
29
+ });
30
+
31
+ mock.module('../i18n', () => {
32
+ return {
33
+ default: {
34
+ t: (key: string, params?: any) => `${key}${params ? JSON.stringify(params) : ''}`,
35
+ },
36
+ };
37
+ });
@@ -0,0 +1,36 @@
1
+ import { describe, expect, test, mock } from 'bun:test';
2
+
3
+ mock.module('react-native', () => {
4
+ return {
5
+ Platform: {
6
+ OS: 'ios',
7
+ },
8
+ };
9
+ });
10
+
11
+ mock.module('../i18n', () => {
12
+ return {
13
+ default: {
14
+ t: (key: string) => key,
15
+ },
16
+ };
17
+ });
18
+
19
+ import { joinUrls } from '../utils';
20
+
21
+ describe('joinUrls', () => {
22
+ test('returns undefined when fileName is not provided', () => {
23
+ expect(joinUrls(['example.com'])).toBeUndefined();
24
+ });
25
+
26
+ test('returns an empty array when paths is empty', () => {
27
+ expect(joinUrls([], 'file.txt')).toEqual([]);
28
+ });
29
+
30
+ test('maps over paths and prepends https:// with fileName', () => {
31
+ expect(joinUrls(['example.com', 'test.org'], 'file.txt')).toEqual([
32
+ 'https://example.com/file.txt',
33
+ 'https://test.org/file.txt',
34
+ ]);
35
+ });
36
+ });