@bravemobile/react-native-code-push 13.1.0-beta.2 → 13.1.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -1
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushBinaryPatch.java +22 -8
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java +5 -2
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushBinaryPatchTest.java +37 -0
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushUpdateManagerDownloadTest.java +189 -2
- package/cli/commands/releaseCommand/addToReleaseHistory.ts +7 -0
- package/cli/commands/releaseCommand/index.test.ts +32 -0
- package/cli/commands/releaseCommand/index.ts +16 -1
- package/cli/commands/releaseCommand/release.test.ts +259 -10
- package/cli/commands/releaseCommand/release.ts +163 -8
- package/cli/dist/commands/releaseCommand/addToReleaseHistory.js +6 -1
- package/cli/dist/commands/releaseCommand/index.js +12 -2
- package/cli/dist/commands/releaseCommand/index.test.js +25 -0
- package/cli/dist/commands/releaseCommand/release.js +108 -10
- package/cli/dist/commands/releaseCommand/release.test.js +205 -7
- package/cli/dist/functions/makeAssetDiffBundle.js +114 -0
- package/cli/dist/functions/makeAssetDiffBundle.test.js +215 -0
- package/cli/dist/functions/resolveAssetDiffBases.js +76 -0
- package/cli/dist/functions/resolveAssetDiffBases.test.js +188 -0
- package/cli/dist/utils/zip.js +12 -2
- package/cli/functions/makeAssetDiffBundle.test.ts +283 -0
- package/cli/functions/makeAssetDiffBundle.ts +166 -0
- package/cli/functions/resolveAssetDiffBases.test.ts +234 -0
- package/cli/functions/resolveAssetDiffBases.ts +108 -0
- package/cli/package.json +2 -0
- package/cli/utils/zip.ts +14 -3
- package/ios/CodePush/CodePushBinaryPatch.m +21 -8
- package/ios/CodePush/CodePushPackage.m +29 -2
- package/package.json +1 -1
- package/src/CodePush.js +7 -1
- package/src/CodePush.test.js +75 -5
- package/typings/react-native-code-push.d.ts +49 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picks the previously released packages a new release builds asset diffs against, and
|
|
3
|
+
* hands back a verified local copy of each.
|
|
4
|
+
*
|
|
5
|
+
* A diff is only installable by a client whose package is byte for byte the base it was
|
|
6
|
+
* built against, so the base has to be the exact archive the release history describes.
|
|
7
|
+
* Every download is therefore unpacked and hashed before it is accepted: a base that is
|
|
8
|
+
* not what the history recorded would produce a diff that sends every client holding
|
|
9
|
+
* that version into the full-download fallback.
|
|
10
|
+
*
|
|
11
|
+
* Bases are the most recent releases, because that is where the installed clients are.
|
|
12
|
+
* One base failing is never a reason to fail the release - the release still publishes
|
|
13
|
+
* its full and patch archives, and simply serves fewer diffs.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from "fs";
|
|
17
|
+
import os from "os";
|
|
18
|
+
import path from "path";
|
|
19
|
+
import semver from "semver";
|
|
20
|
+
import { generatePackageHashFromDirectory } from "../utils/hash-utils.js";
|
|
21
|
+
import { extractCodePushBundleContents } from "./makeBinaryPatchBundle.js";
|
|
22
|
+
import type { CliConfigInterface, ReleaseHistoryInterface } from "../../typings/react-native-code-push.d.ts";
|
|
23
|
+
|
|
24
|
+
const TEMP_DIR_PREFIX = 'codepush-asset-diff-base-';
|
|
25
|
+
|
|
26
|
+
export type AssetDiffBase = {
|
|
27
|
+
basePackageHash: string;
|
|
28
|
+
baseBundleFilePath: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param releaseHistory {ReleaseHistoryInterface} History of the binary version being released to
|
|
33
|
+
* @param diffBaseCount {number} How many of the most recent releases to build diffs against
|
|
34
|
+
* @param bundleDownloader Consumer-provided downloader for a released archive
|
|
35
|
+
* @return The bases that downloaded and verified, newest release first.
|
|
36
|
+
*/
|
|
37
|
+
export async function resolveAssetDiffBases({
|
|
38
|
+
releaseHistory,
|
|
39
|
+
diffBaseCount,
|
|
40
|
+
bundleDownloader,
|
|
41
|
+
platform,
|
|
42
|
+
identifier,
|
|
43
|
+
targetBinaryVersion,
|
|
44
|
+
}: {
|
|
45
|
+
releaseHistory: ReleaseHistoryInterface;
|
|
46
|
+
diffBaseCount: number;
|
|
47
|
+
bundleDownloader: NonNullable<CliConfigInterface['bundleDownloader']>;
|
|
48
|
+
platform: 'ios' | 'android';
|
|
49
|
+
identifier?: string;
|
|
50
|
+
targetBinaryVersion: string;
|
|
51
|
+
}): Promise<AssetDiffBase[]> {
|
|
52
|
+
// The seeded entry for the update inside the app binary has neither a url nor a hash,
|
|
53
|
+
// and nothing can be diffed against a package that was never published. A key that is
|
|
54
|
+
// not a version cannot be ordered against the others either, and ordering is what picks
|
|
55
|
+
// the recent releases, so it is not eligible.
|
|
56
|
+
const candidates = Object.entries(releaseHistory)
|
|
57
|
+
.filter(([version, releaseInfo]) =>
|
|
58
|
+
Boolean(semver.valid(version)) && Boolean(releaseInfo.downloadUrl) && Boolean(releaseInfo.packageHash))
|
|
59
|
+
.sort(([left], [right]) => semver.rcompare(left, right))
|
|
60
|
+
.slice(0, diffBaseCount);
|
|
61
|
+
|
|
62
|
+
const bases: AssetDiffBase[] = [];
|
|
63
|
+
for (const [version, releaseInfo] of candidates) {
|
|
64
|
+
try {
|
|
65
|
+
const { downloadedFilePath } = await bundleDownloader({
|
|
66
|
+
downloadUrl: releaseInfo.downloadUrl,
|
|
67
|
+
targetBinaryVersion,
|
|
68
|
+
releaseVersion: version,
|
|
69
|
+
packageHash: releaseInfo.packageHash,
|
|
70
|
+
}, platform, identifier);
|
|
71
|
+
|
|
72
|
+
if (!(await matchesPackageHash(downloadedFilePath, releaseInfo.packageHash))) {
|
|
73
|
+
console.warn(
|
|
74
|
+
`warn: Skipping the asset diff base v${version}: the downloaded archive does not hash to the ` +
|
|
75
|
+
`package hash recorded for it (${releaseInfo.packageHash}).`,
|
|
76
|
+
);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
bases.push({ basePackageHash: releaseInfo.packageHash, baseBundleFilePath: downloadedFilePath });
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.warn(
|
|
83
|
+
`warn: Skipping the asset diff base v${version}: ${error instanceof Error ? error.message : String(error)}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return bases;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Whether an archive holds the contents the release history recorded for it.
|
|
93
|
+
*
|
|
94
|
+
* Unpacked into a working directory of its own: the extraction reuses one directory name,
|
|
95
|
+
* so bases sharing a parent would have the second unpack wipe the first.
|
|
96
|
+
*/
|
|
97
|
+
async function matchesPackageHash(bundleFilePath: string, packageHash: string): Promise<boolean> {
|
|
98
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), TEMP_DIR_PREFIX));
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const { contentsPath } = await extractCodePushBundleContents(bundleFilePath, tempRoot);
|
|
102
|
+
// Hashed relative to the contents parent, the way the archive itself was hashed.
|
|
103
|
+
const actualHash = await generatePackageHashFromDirectory(contentsPath, path.join(contentsPath, '..'));
|
|
104
|
+
return actualHash === packageHash;
|
|
105
|
+
} finally {
|
|
106
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
}
|
package/cli/package.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"commander": "^12.1.0",
|
|
15
|
+
"semver": "^7.3.5",
|
|
15
16
|
"shelljs": "^0.10.0",
|
|
16
17
|
"xcode": "^3.0.1",
|
|
17
18
|
"yazl": "^3.3.1",
|
|
@@ -29,6 +30,7 @@
|
|
|
29
30
|
"node": ">=18"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
33
|
+
"@types/semver": "^7.5.8",
|
|
32
34
|
"@types/yazl": "^3.3.0",
|
|
33
35
|
"@types/yauzl": "^2.10.3"
|
|
34
36
|
}
|
package/cli/utils/zip.ts
CHANGED
|
@@ -11,6 +11,20 @@ import { walk } from "./promisfied-fs.js";
|
|
|
11
11
|
type ReleaseFile = { sourceLocation: string, targetLocation: string };
|
|
12
12
|
|
|
13
13
|
export function zip(updateContentsPath: string): Promise<string> {
|
|
14
|
+
// For legacy reasons, put the root directory in the zip
|
|
15
|
+
return zipRelativeTo(updateContentsPath, path.join(updateContentsPath, '..'));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Zips the contents of `rootPath` with entries relative to the root itself, so a file
|
|
20
|
+
* placed directly under the root lands at the archive root rather than inside a wrapper
|
|
21
|
+
* directory.
|
|
22
|
+
*/
|
|
23
|
+
export function zipDirectoryContents(rootPath: string): Promise<string> {
|
|
24
|
+
return zipRelativeTo(rootPath, rootPath);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function zipRelativeTo(updateContentsPath: string, baseDirectoryPath: string): Promise<string> {
|
|
14
28
|
|
|
15
29
|
// eslint-disable-next-line no-async-promise-executor
|
|
16
30
|
return new Promise(async (resolve, reject) => {
|
|
@@ -30,9 +44,6 @@ export function zip(updateContentsPath: string): Promise<string> {
|
|
|
30
44
|
reject(error);
|
|
31
45
|
}
|
|
32
46
|
|
|
33
|
-
const directoryPath = updateContentsPath;
|
|
34
|
-
const baseDirectoryPath = path.join(directoryPath, '..'); // For legacy reasons, put the root directory in the zip
|
|
35
|
-
|
|
36
47
|
const files: string[] = await walk(updateContentsPath);
|
|
37
48
|
|
|
38
49
|
files.forEach((filePath) => {
|
|
@@ -40,6 +40,10 @@ static NSString *const BinaryPatchTargetBundleHashKey = @"targetBundleHash";
|
|
|
40
40
|
static NSString *const BinaryPatchTargetBundleSizeKey = @"targetBundleSize";
|
|
41
41
|
static const NSInteger BinaryPatchFormatVersion = 1;
|
|
42
42
|
|
|
43
|
+
/* The manifest of the assets to delete that a diff archive carries at its root. Paired
|
|
44
|
+
* with CodePushPackage.m's DiffManifestFileName, which is file-local there as well. */
|
|
45
|
+
static NSString *const AssetDiffManifestFileName = @"hotcodepush.json";
|
|
46
|
+
|
|
43
47
|
/*
|
|
44
48
|
* The largest bundle a patch is allowed to promise. A manifest is untrusted input, and
|
|
45
49
|
* the size in it decides how much disk the restore asks for before a single byte of the
|
|
@@ -373,21 +377,30 @@ static CodePushBinaryPatchApplyResult CodePushApplyBinaryPatch(const unsigned ch
|
|
|
373
377
|
* An archive wraps its files in a single directory, and the manifest's paths are
|
|
374
378
|
* relative to that directory rather than to the archive. An archive whose files are at
|
|
375
379
|
* the top level is its own contents root, which is how the tooling that unpacks one
|
|
376
|
-
* reads it too.
|
|
380
|
+
* reads it too. A diff archive carries the manifest of the assets to delete beside that
|
|
381
|
+
* wrapper directory, at the root, so that file does not count against the shape.
|
|
377
382
|
*/
|
|
378
383
|
+ (NSString *)contentsFolderInUnzippedFolder:(NSString *)unzippedFolderPath
|
|
379
384
|
{
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
385
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
386
|
+
NSArray *entries = [fileManager contentsOfDirectoryAtPath:unzippedFolderPath error:nil];
|
|
387
|
+
NSString *wrapperPath = nil;
|
|
388
|
+
for (NSString *entry in entries) {
|
|
389
|
+
NSString *entryPath = [unzippedFolderPath stringByAppendingPathComponent:entry];
|
|
384
390
|
BOOL isDirectory = NO;
|
|
385
|
-
|
|
386
|
-
|
|
391
|
+
BOOL exists = [fileManager fileExistsAtPath:entryPath isDirectory:&isDirectory];
|
|
392
|
+
if (exists && !isDirectory && [entry isEqualToString:AssetDiffManifestFileName]) {
|
|
393
|
+
continue;
|
|
387
394
|
}
|
|
395
|
+
if (wrapperPath == nil && exists && isDirectory) {
|
|
396
|
+
wrapperPath = entryPath;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return unzippedFolderPath;
|
|
388
401
|
}
|
|
389
402
|
|
|
390
|
-
return unzippedFolderPath;
|
|
403
|
+
return wrapperPath != nil ? wrapperPath : unzippedFolderPath;
|
|
391
404
|
}
|
|
392
405
|
|
|
393
406
|
/**
|
|
@@ -329,8 +329,12 @@ static NSString *const UnzippedFolderName = @"unzipped";
|
|
|
329
329
|
error:&error];
|
|
330
330
|
NSArray *deletedFiles = manifestJSON[@"deletedFiles"];
|
|
331
331
|
for (NSString *deletedFileName in deletedFiles) {
|
|
332
|
-
|
|
333
|
-
|
|
332
|
+
// The manifest is untrusted input, so an entry that reaches outside the
|
|
333
|
+
// package folder is ignored rather than followed. Whether skipping it
|
|
334
|
+
// left the update whole is the folder hash check's answer to give.
|
|
335
|
+
NSString *absoluteDeletedFilePath = [self pathInsideFolder:newUpdateFolderPath
|
|
336
|
+
relativePath:deletedFileName];
|
|
337
|
+
if (absoluteDeletedFilePath && [[NSFileManager defaultManager] fileExistsAtPath:absoluteDeletedFilePath]) {
|
|
334
338
|
[[NSFileManager defaultManager] removeItemAtPath:absoluteDeletedFilePath
|
|
335
339
|
error:&error];
|
|
336
340
|
if (error) {
|
|
@@ -468,6 +472,29 @@ static NSString *const UnzippedFolderName = @"unzipped";
|
|
|
468
472
|
return [[self getCodePushPath] stringByAppendingPathComponent:BinaryPatchFolderName];
|
|
469
473
|
}
|
|
470
474
|
|
|
475
|
+
/*
|
|
476
|
+
* Resolves a path a manifest points at, refusing anything that would reach outside the
|
|
477
|
+
* folder it is relative to - an archive is untrusted input, and its manifest is no more
|
|
478
|
+
* trusted than its entries.
|
|
479
|
+
*
|
|
480
|
+
* @return the resolved path, or nil when the path is unusable
|
|
481
|
+
*/
|
|
482
|
+
+ (NSString *)pathInsideFolder:(NSString *)folderPath
|
|
483
|
+
relativePath:(NSString *)relativePath
|
|
484
|
+
{
|
|
485
|
+
if (relativePath.length == 0 || [relativePath isAbsolutePath]) {
|
|
486
|
+
return nil;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
NSString *resolvedFolderPath = [[folderPath stringByStandardizingPath] stringByAppendingString:@"/"];
|
|
490
|
+
NSString *resolvedPath = [[folderPath stringByAppendingPathComponent:relativePath] stringByStandardizingPath];
|
|
491
|
+
if (![resolvedPath hasPrefix:resolvedFolderPath]) {
|
|
492
|
+
return nil;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return resolvedPath;
|
|
496
|
+
}
|
|
497
|
+
|
|
471
498
|
+ (NSString *)getCodePushPath
|
|
472
499
|
{
|
|
473
500
|
NSString* codePushPath = [[CodePush getApplicationSupportDirectory] stringByAppendingPathComponent:@"CodePush"];
|
package/package.json
CHANGED
package/src/CodePush.js
CHANGED
|
@@ -174,6 +174,12 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) {
|
|
|
174
174
|
const [latestVersion, latestReleaseInfo] = versioning.findLatestRelease();
|
|
175
175
|
const isMandatory = versioning.checkIsMandatory(runtimeVersion);
|
|
176
176
|
|
|
177
|
+
// A diff archive is a patch archive computed against one specific earlier release,
|
|
178
|
+
// so it only applies when that exact release is the one installed right now.
|
|
179
|
+
const diffPackageDownloadUrl = localPackage && localPackage.packageHash
|
|
180
|
+
? latestReleaseInfo.diffPackages?.[localPackage.packageHash]
|
|
181
|
+
: undefined;
|
|
182
|
+
|
|
177
183
|
/**
|
|
178
184
|
* Convert the update information decided from `ReleaseHistoryInterface` to be passed to the library core (original CodePush library).
|
|
179
185
|
*
|
|
@@ -185,7 +191,7 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) {
|
|
|
185
191
|
* Only released updates that were published with a binary patch carry this.
|
|
186
192
|
* When it is missing, the update is downloaded in full from `download_url`.
|
|
187
193
|
*/
|
|
188
|
-
binary_patch_download_url: latestReleaseInfo.binaryPatchDownloadUrl,
|
|
194
|
+
binary_patch_download_url: diffPackageDownloadUrl || latestReleaseInfo.binaryPatchDownloadUrl,
|
|
189
195
|
// (`enabled` will always be true in the release information obtained from the previous process.)
|
|
190
196
|
is_available: latestReleaseInfo.enabled,
|
|
191
197
|
package_hash: latestReleaseInfo.packageHash,
|
package/src/CodePush.test.js
CHANGED
|
@@ -27,6 +27,8 @@ const LABEL = '1.0.1';
|
|
|
27
27
|
const PACKAGE_HASH = 'a'.repeat(64);
|
|
28
28
|
const DOWNLOAD_URL = 'https://cdn.example.com/full.zip';
|
|
29
29
|
const BINARY_PATCH_DOWNLOAD_URL = 'https://cdn.example.com/full.zip-patch.zip';
|
|
30
|
+
const DIFF_URL = 'https://cdn.example.com/diff-from-base.zip';
|
|
31
|
+
const INSTALLED_HASH = 'base-package-hash';
|
|
30
32
|
|
|
31
33
|
/** The release the CLI writes when only the full bundle was published. */
|
|
32
34
|
function fullOnlyRelease() {
|
|
@@ -50,19 +52,27 @@ function patchedRelease() {
|
|
|
50
52
|
};
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
/** The release the CLI writes when the update also has a diff archive against an earlier one. */
|
|
56
|
+
function diffRelease() {
|
|
57
|
+
const history = patchedRelease();
|
|
58
|
+
history[LABEL].diffPackages = { [INSTALLED_HASH]: DIFF_URL };
|
|
59
|
+
return history;
|
|
60
|
+
}
|
|
61
|
+
|
|
53
62
|
/**
|
|
54
63
|
* @param binaryPatchResult what the native side reports the patch attempt ended in, which
|
|
55
64
|
* only a download that had a patch to try comes back with.
|
|
65
|
+
* @param installedPackage the CodePush update the app is running, if any.
|
|
56
66
|
*/
|
|
57
|
-
function createNativeBridge({ binaryPatchResult } = {}) {
|
|
67
|
+
function createNativeBridge({ binaryPatchResult, installedPackage } = {}) {
|
|
58
68
|
return {
|
|
59
69
|
addDownloadProgressListener: jest.fn(() => ({ remove: jest.fn() })),
|
|
60
70
|
downloadUpdate: jest.fn(async (updatePackage) => ({
|
|
61
71
|
...updatePackage,
|
|
62
72
|
...(binaryPatchResult ? { binaryPatchResult } : {}),
|
|
63
73
|
})),
|
|
64
|
-
//
|
|
65
|
-
getUpdateMetadata: jest.fn(async () => null),
|
|
74
|
+
// Without an installed CodePush update, the app runs the bundle of its binary.
|
|
75
|
+
getUpdateMetadata: jest.fn(async () => installedPackage ?? null),
|
|
66
76
|
isFailedUpdate: jest.fn(async () => false),
|
|
67
77
|
isFirstRun: jest.fn(async () => false),
|
|
68
78
|
// The rest of what a full `sync()` reaches for.
|
|
@@ -82,10 +92,10 @@ function createNativeBridge({ binaryPatchResult } = {}) {
|
|
|
82
92
|
* @param onBinaryPatchResult a callback the app registers on the decorator, as opposed to
|
|
83
93
|
* one it passes to a single `sync()` call.
|
|
84
94
|
*/
|
|
85
|
-
function loadCodePush({ releaseHistory = {}, updateChecker, binaryPatchResult, onBinaryPatchResult } = {}) {
|
|
95
|
+
function loadCodePush({ releaseHistory = {}, updateChecker, binaryPatchResult, onBinaryPatchResult, installedPackage } = {}) {
|
|
86
96
|
jest.resetModules();
|
|
87
97
|
const CodePush = require('./CodePush');
|
|
88
|
-
const nativeBridge = createNativeBridge({ binaryPatchResult });
|
|
98
|
+
const nativeBridge = createNativeBridge({ binaryPatchResult, installedPackage });
|
|
89
99
|
|
|
90
100
|
CodePush.setUpTestDependencies(
|
|
91
101
|
null,
|
|
@@ -200,6 +210,66 @@ describe('checkForUpdate without a binary patch in the release history', () => {
|
|
|
200
210
|
});
|
|
201
211
|
});
|
|
202
212
|
|
|
213
|
+
/**
|
|
214
|
+
* A diff archive only applies to the one release it was computed against, so which of the
|
|
215
|
+
* two patch urls of a release is downloaded depends on what the app is running right now.
|
|
216
|
+
*/
|
|
217
|
+
describe('checkForUpdate with asset diff packages in the release history', () => {
|
|
218
|
+
it('downloads the diff archive when the installed update matches a diff base', async () => {
|
|
219
|
+
const { CodePush, nativeBridge } = loadCodePush({
|
|
220
|
+
releaseHistory: diffRelease(),
|
|
221
|
+
installedPackage: { packageHash: INSTALLED_HASH },
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const remotePackage = await CodePush.checkForUpdate();
|
|
225
|
+
await remotePackage.download();
|
|
226
|
+
|
|
227
|
+
expect(downloadedPackageMetadata(nativeBridge)).toMatchObject({
|
|
228
|
+
downloadUrl: DOWNLOAD_URL,
|
|
229
|
+
binaryPatchDownloadUrl: DIFF_URL,
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('downloads the plain patch archive when the installed update matches no diff base', async () => {
|
|
234
|
+
const { CodePush, nativeBridge } = loadCodePush({
|
|
235
|
+
releaseHistory: diffRelease(),
|
|
236
|
+
installedPackage: { packageHash: 'other-hash' },
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const remotePackage = await CodePush.checkForUpdate();
|
|
240
|
+
await remotePackage.download();
|
|
241
|
+
|
|
242
|
+
expect(downloadedPackageMetadata(nativeBridge)).toMatchObject({
|
|
243
|
+
binaryPatchDownloadUrl: BINARY_PATCH_DOWNLOAD_URL,
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('downloads the plain patch archive when no update is installed', async () => {
|
|
248
|
+
const { CodePush, nativeBridge } = loadCodePush({ releaseHistory: diffRelease() });
|
|
249
|
+
|
|
250
|
+
const remotePackage = await CodePush.checkForUpdate();
|
|
251
|
+
await remotePackage.download();
|
|
252
|
+
|
|
253
|
+
expect(downloadedPackageMetadata(nativeBridge)).toMatchObject({
|
|
254
|
+
binaryPatchDownloadUrl: BINARY_PATCH_DOWNLOAD_URL,
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('does not send the diff package map across the bridge', async () => {
|
|
259
|
+
const { CodePush, nativeBridge } = loadCodePush({
|
|
260
|
+
releaseHistory: diffRelease(),
|
|
261
|
+
installedPackage: { packageHash: INSTALLED_HASH },
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const remotePackage = await CodePush.checkForUpdate();
|
|
265
|
+
expect(remotePackage).not.toHaveProperty('diffPackages');
|
|
266
|
+
|
|
267
|
+
await remotePackage.download();
|
|
268
|
+
|
|
269
|
+
expect(downloadedPackageMetadata(nativeBridge)).not.toHaveProperty('diffPackages');
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
203
273
|
describe('checkForUpdate through the deprecated updateChecker', () => {
|
|
204
274
|
it('carries the patch url of the update check response', async () => {
|
|
205
275
|
const { CodePush } = loadCodePush({
|
|
@@ -34,6 +34,13 @@ export interface ReleaseInfo {
|
|
|
34
34
|
* that cannot use it downloads the full update from `downloadUrl` instead.
|
|
35
35
|
*/
|
|
36
36
|
binaryPatchDownloadUrl?: string;
|
|
37
|
+
/**
|
|
38
|
+
* URLs of patch archives that also carry an asset diff, keyed by the packageHash of the
|
|
39
|
+
* release each archive was diffed against. A client whose installed update matches one of
|
|
40
|
+
* these keys downloads that archive instead of `binaryPatchDownloadUrl`; every other client
|
|
41
|
+
* ignores this field. Only present when the release was published with asset diff archives.
|
|
42
|
+
*/
|
|
43
|
+
diffPackages?: Record<string, string>;
|
|
37
44
|
packageHash: string;
|
|
38
45
|
rollout?: number;
|
|
39
46
|
}
|
|
@@ -626,6 +633,31 @@ declare namespace CodePush {
|
|
|
626
633
|
|
|
627
634
|
export default CodePush;
|
|
628
635
|
|
|
636
|
+
/**
|
|
637
|
+
* Identifies the archive passed to `bundleUploader` during a release.
|
|
638
|
+
*
|
|
639
|
+
* `targetBinaryVersion` is the value of the release command's `--binary-version` option.
|
|
640
|
+
* A full bundle's `packageHash` identifies its contents across target binary versions, while
|
|
641
|
+
* binary patch and asset diff archives need the target binary version to distinguish their contents.
|
|
642
|
+
* Asset diff archives also identify the released package they were built against.
|
|
643
|
+
*/
|
|
644
|
+
export type BundleUploadArtifact = {
|
|
645
|
+
targetBinaryVersion: string;
|
|
646
|
+
packageHash: string;
|
|
647
|
+
} & (
|
|
648
|
+
| { type: 'full-bundle' }
|
|
649
|
+
| { type: 'binary-patch' }
|
|
650
|
+
| { type: 'asset-diff'; basePackageHash: string }
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
/** Identifies the previously released full archive passed to `bundleDownloader`. */
|
|
654
|
+
export interface BundleDownloadInfo {
|
|
655
|
+
downloadUrl: string;
|
|
656
|
+
targetBinaryVersion: string;
|
|
657
|
+
releaseVersion: ReleaseVersion;
|
|
658
|
+
packageHash: string;
|
|
659
|
+
}
|
|
660
|
+
|
|
629
661
|
/**
|
|
630
662
|
* Interface for the config file required for `npx code-push` CLI operation.
|
|
631
663
|
*
|
|
@@ -640,12 +672,14 @@ export interface CliConfigInterface {
|
|
|
640
672
|
*
|
|
641
673
|
* @param source The relative path of the generated bundle file. (e.g. build/bundleOutput/1087bc338fc45a961c...)
|
|
642
674
|
* @param platform The target platform of the bundle file. This is the string passed when executing the CLI command. ('ios'/'android')
|
|
675
|
+
* @param artifact Information that identifies the full bundle, binary patch, or asset diff archive. The release command always supplies it; it remains optional so existing callers and three-parameter implementations stay compatible.
|
|
643
676
|
* @return {Promise<{downloadUrl: string}>} An object containing the `downloadUrl` property, which is the URL from which the uploaded bundle file can be downloaded. This URL will be recorded in the release history.
|
|
644
677
|
*/
|
|
645
678
|
bundleUploader: (
|
|
646
679
|
source: string,
|
|
647
680
|
platform: "ios" | "android",
|
|
648
681
|
identifier?: string,
|
|
682
|
+
artifact?: BundleUploadArtifact,
|
|
649
683
|
) => Promise<{downloadUrl: string}>;
|
|
650
684
|
|
|
651
685
|
/**
|
|
@@ -683,4 +717,19 @@ export interface CliConfigInterface {
|
|
|
683
717
|
platform: "ios" | "android",
|
|
684
718
|
identifier?: string,
|
|
685
719
|
) => Promise<void>;
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Downloads a previously released update archive so the release command can compute
|
|
723
|
+
* asset diffs against it. Receives the `downloadUrl` recorded in the release history and
|
|
724
|
+
* must resolve with the local path of the downloaded file.
|
|
725
|
+
* Optional: when absent, releases are published without asset diff archives.
|
|
726
|
+
*
|
|
727
|
+
* @param archive Information that identifies the released full archive. The release command
|
|
728
|
+
* supplies its download URL, target binary version, release version, and package hash together.
|
|
729
|
+
*/
|
|
730
|
+
bundleDownloader?: (
|
|
731
|
+
archive: BundleDownloadInfo,
|
|
732
|
+
platform: "ios" | "android",
|
|
733
|
+
identifier?: string,
|
|
734
|
+
) => Promise<{ downloadedFilePath: string }>;
|
|
686
735
|
}
|