@bravemobile/react-native-code-push 13.1.0-beta.3 → 13.1.0-beta.5
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/CodePush.podspec +5 -0
- package/README.md +20 -145
- package/android/app/src/main/java/com/microsoft/codepush/react/ArchiveAttemptLog.java +181 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/{BinaryPatchResult.java → ArchiveRestoreResult.java} +17 -7
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushBinaryPatch.java +20 -20
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushConstants.java +20 -1
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushErrorCode.java +86 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushHttpException.java +29 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushIncompleteDownloadException.java +17 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +12 -7
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java +143 -43
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushBinaryPatchTest.java +31 -31
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushErrorCodeTest.java +90 -0
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushUpdateManagerBinaryPatchTest.java +159 -19
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushUpdateManagerDownloadTest.java +304 -15
- package/cli/commands/createHistoryCommand/createReleaseHistory.test.ts +133 -0
- package/cli/commands/createHistoryCommand/createReleaseHistory.ts +3 -11
- package/cli/commands/releaseCommand/addToReleaseHistory.ts +3 -11
- package/cli/commands/releaseCommand/release.test.ts +55 -13
- package/cli/commands/releaseCommand/release.ts +39 -6
- package/cli/commands/updateHistoryCommand/updateReleaseHistory.ts +3 -11
- package/cli/dist/commands/createHistoryCommand/createReleaseHistory.js +2 -8
- package/cli/dist/commands/createHistoryCommand/createReleaseHistory.test.js +95 -0
- package/cli/dist/commands/releaseCommand/addToReleaseHistory.js +2 -8
- package/cli/dist/commands/releaseCommand/release.js +23 -8
- package/cli/dist/commands/releaseCommand/release.test.js +37 -9
- package/cli/dist/commands/updateHistoryCommand/updateReleaseHistory.js +2 -8
- package/cli/dist/functions/resolveAssetDiffBases.js +7 -2
- package/cli/dist/functions/resolveAssetDiffBases.test.js +26 -12
- package/cli/dist/functions/stageReleaseHistoryFile.js +28 -0
- package/cli/functions/resolveAssetDiffBases.test.ts +28 -14
- package/cli/functions/resolveAssetDiffBases.ts +8 -1
- package/cli/functions/stageReleaseHistoryFile.ts +38 -0
- package/ios/CodePush/CodePush.h +6 -3
- package/ios/CodePush/CodePush.mm +10 -10
- package/ios/CodePush/CodePushBinaryPatch.h +18 -8
- package/ios/CodePush/CodePushBinaryPatch.m +23 -22
- package/ios/CodePush/CodePushDownloadHandler.m +2 -1
- package/ios/CodePush/CodePushErrorUtils.m +55 -0
- package/ios/CodePush/CodePushPackage.m +242 -71
- package/package.json +2 -1
- package/src/CodePush.js +28 -18
- package/src/CodePush.test.js +110 -55
- package/src/package-mixins.js +19 -10
- package/typings/react-native-code-push.d.ts +152 -41
- package/android/app/src/main/java/com/microsoft/codepush/react/BinaryPatchAttempt.java +0 -92
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
import { applyPatch } from "../../utils/binaryPatch.js";
|
|
18
18
|
import { generatePackageHashFromDirectory } from "../../utils/hash-utils.js";
|
|
19
19
|
import { unzip } from "../../utils/unzip.js";
|
|
20
|
-
import type { CliConfigInterface, ReleaseHistoryInterface } from "../../../typings/react-native-code-push.d.ts";
|
|
20
|
+
import type { BundleUploadArtifact, CliConfigInterface, ReleaseHistoryInterface } from "../../../typings/react-native-code-push.d.ts";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Covers the release flow around the binary patch option with `--skip-bundle`, which is
|
|
@@ -92,15 +92,23 @@ class ProcessExitError extends Error {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
type Uploads = {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
type Uploads = {
|
|
96
|
+
artifact: BundleUploadArtifact | undefined;
|
|
97
|
+
downloadUrl: string;
|
|
98
|
+
filePath: string;
|
|
99
|
+
logCountBeforeUpload: number;
|
|
100
|
+
}[];
|
|
101
|
+
|
|
102
|
+
function recordingUploader(
|
|
103
|
+
uploads: Uploads,
|
|
104
|
+
failOn?: (filePath: string) => boolean,
|
|
105
|
+
): CliConfigInterface['bundleUploader'] {
|
|
106
|
+
return async (filePath, _platform, _identifier, artifact) => {
|
|
99
107
|
if (failOn?.(filePath)) {
|
|
100
108
|
throw new Error(`upload rejected: ${path.basename(filePath)}`);
|
|
101
109
|
}
|
|
102
110
|
const downloadUrl = `https://cdn.example.com/${path.basename(filePath)}`;
|
|
103
|
-
uploads.push({ filePath, downloadUrl, logCountBeforeUpload: logs.length });
|
|
111
|
+
uploads.push({ artifact, filePath, downloadUrl, logCountBeforeUpload: logs.length });
|
|
104
112
|
return { downloadUrl };
|
|
105
113
|
};
|
|
106
114
|
}
|
|
@@ -198,12 +206,23 @@ beforeEach(() => {
|
|
|
198
206
|
|
|
199
207
|
afterEach(() => {
|
|
200
208
|
jest.restoreAllMocks();
|
|
201
|
-
// addToReleaseHistory writes its JSON
|
|
202
|
-
// when the history was stored successfully.
|
|
203
|
-
fs.rmSync(path.resolve(process.cwd(),
|
|
209
|
+
// addToReleaseHistory writes its JSON under the directory it was invoked in, and only
|
|
210
|
+
// removes it when the history was stored successfully.
|
|
211
|
+
fs.rmSync(path.resolve(process.cwd(), "codepush-release-history"), { recursive: true, force: true });
|
|
204
212
|
});
|
|
205
213
|
|
|
206
214
|
describe("release without --binary-bundle-path", () => {
|
|
215
|
+
it("keeps existing three-argument uploaders callable", async () => {
|
|
216
|
+
const legacyUploader = async (
|
|
217
|
+
source: string,
|
|
218
|
+
_platform: 'ios' | 'android',
|
|
219
|
+
_identifier?: string,
|
|
220
|
+
) => ({ downloadUrl: source });
|
|
221
|
+
const uploader: CliConfigInterface['bundleUploader'] = legacyUploader;
|
|
222
|
+
|
|
223
|
+
await expect(uploader('bundle.zip', 'ios')).resolves.toEqual({ downloadUrl: 'bundle.zip' });
|
|
224
|
+
});
|
|
225
|
+
|
|
207
226
|
it("uploads the full bundle only, exactly as before binary patches existed", async () => {
|
|
208
227
|
const staged = await stageBundleOutput("full-only");
|
|
209
228
|
|
|
@@ -211,6 +230,11 @@ describe("release without --binary-bundle-path", () => {
|
|
|
211
230
|
|
|
212
231
|
expect(uploads).toHaveLength(1);
|
|
213
232
|
expect(uploads[0].filePath).toBe(`${staged.bundleDirectory}/${staged.bundleFileName}`);
|
|
233
|
+
expect(uploads[0].artifact).toEqual({
|
|
234
|
+
type: 'full-bundle',
|
|
235
|
+
targetBinaryVersion: BINARY_VERSION,
|
|
236
|
+
packageHash: staged.bundleFileName,
|
|
237
|
+
});
|
|
214
238
|
expect(fs.readdirSync(staged.bundleDirectory)).toEqual([staged.bundleFileName]);
|
|
215
239
|
expect(releaseHistories[0][APP_VERSION]).toEqual({
|
|
216
240
|
enabled: true,
|
|
@@ -658,11 +682,11 @@ describe("release with asset diff bases", () => {
|
|
|
658
682
|
|
|
659
683
|
/** Serves the staged base archives, and records which releases were asked for. */
|
|
660
684
|
function recordingDownloader(bases: StagedBaseRelease[], downloads: string[]): CliConfigInterface['bundleDownloader'] {
|
|
661
|
-
return async (
|
|
662
|
-
downloads.push(downloadUrl);
|
|
663
|
-
const base = bases.find((candidate) => candidate.downloadUrl === downloadUrl);
|
|
685
|
+
return async (archive) => {
|
|
686
|
+
downloads.push(archive.downloadUrl);
|
|
687
|
+
const base = bases.find((candidate) => candidate.downloadUrl === archive.downloadUrl);
|
|
664
688
|
if (!base) {
|
|
665
|
-
throw new Error(`the downloader was called with an unexpected url: ${downloadUrl}`);
|
|
689
|
+
throw new Error(`the downloader was called with an unexpected url: ${archive.downloadUrl}`);
|
|
666
690
|
}
|
|
667
691
|
return { downloadedFilePath: base.archivePath };
|
|
668
692
|
};
|
|
@@ -687,6 +711,24 @@ describe("release with asset diff bases", () => {
|
|
|
687
711
|
`${staged.bundleFileName}${BINARY_PATCH_ARCHIVE_SUFFIX}`,
|
|
688
712
|
diffFileName,
|
|
689
713
|
]);
|
|
714
|
+
expect(uploads.map(({ artifact }) => artifact)).toEqual([
|
|
715
|
+
{
|
|
716
|
+
type: 'full-bundle',
|
|
717
|
+
targetBinaryVersion: BINARY_VERSION,
|
|
718
|
+
packageHash: staged.bundleFileName,
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
type: 'binary-patch',
|
|
722
|
+
targetBinaryVersion: BINARY_VERSION,
|
|
723
|
+
packageHash: staged.bundleFileName,
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
type: 'asset-diff',
|
|
727
|
+
targetBinaryVersion: BINARY_VERSION,
|
|
728
|
+
packageHash: staged.bundleFileName,
|
|
729
|
+
basePackageHash: base.packageHash,
|
|
730
|
+
},
|
|
731
|
+
]);
|
|
690
732
|
expect(downloads).toEqual([base.downloadUrl]);
|
|
691
733
|
// Every artifact is uploaded before the history points at any of them.
|
|
692
734
|
expect(uploadCountsWhenHistorySaved).toEqual([3]);
|
|
@@ -2,7 +2,7 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { bundleCodePush, resolveJsBundleName } from "../bundleCommand/bundleCodePush.js";
|
|
4
4
|
import { addToReleaseHistory } from "./addToReleaseHistory.js";
|
|
5
|
-
import type { CliConfigInterface, ReleaseHistoryInterface } from "../../../typings/react-native-code-push.d.ts";
|
|
5
|
+
import type { BundleUploadArtifact, CliConfigInterface, ReleaseHistoryInterface } from "../../../typings/react-native-code-push.d.ts";
|
|
6
6
|
import { generatePackageHashFromDirectory } from "../../utils/hash-utils.js";
|
|
7
7
|
import { unzip } from "../../utils/unzip.js";
|
|
8
8
|
import { ASSET_DIFF_ARCHIVE_INFIX, makeAssetDiffBundle } from "../../functions/makeAssetDiffBundle.js";
|
|
@@ -95,6 +95,7 @@ export async function release(
|
|
|
95
95
|
bundleDownloader,
|
|
96
96
|
bundleDirectory,
|
|
97
97
|
packageHash,
|
|
98
|
+
targetBinaryVersion: binaryVersion,
|
|
98
99
|
platform,
|
|
99
100
|
identifier,
|
|
100
101
|
})
|
|
@@ -102,10 +103,32 @@ export async function release(
|
|
|
102
103
|
|
|
103
104
|
// Every artifact is uploaded before the release history is touched, so a failed
|
|
104
105
|
// upload leaves the history describing only updates that can actually be downloaded.
|
|
105
|
-
const downloadUrl = await uploadArtifact(
|
|
106
|
+
const downloadUrl = await uploadArtifact(
|
|
107
|
+
bundleUploader,
|
|
108
|
+
bundleFilePath,
|
|
109
|
+
platform,
|
|
110
|
+
identifier,
|
|
111
|
+
{
|
|
112
|
+
type: 'full-bundle',
|
|
113
|
+
targetBinaryVersion: binaryVersion,
|
|
114
|
+
packageHash,
|
|
115
|
+
},
|
|
116
|
+
'bundle',
|
|
117
|
+
);
|
|
106
118
|
let patchDownloadUrl: string | undefined;
|
|
107
119
|
if (binaryPatch) {
|
|
108
|
-
patchDownloadUrl = await uploadArtifact(
|
|
120
|
+
patchDownloadUrl = await uploadArtifact(
|
|
121
|
+
bundleUploader,
|
|
122
|
+
binaryPatch.patchBundleFilePath,
|
|
123
|
+
platform,
|
|
124
|
+
identifier,
|
|
125
|
+
{
|
|
126
|
+
type: 'binary-patch',
|
|
127
|
+
targetBinaryVersion: binaryVersion,
|
|
128
|
+
packageHash,
|
|
129
|
+
},
|
|
130
|
+
'binary patch bundle',
|
|
131
|
+
);
|
|
109
132
|
console.log(`log: Binary patch archive uploaded (download url: ${patchDownloadUrl})`);
|
|
110
133
|
}
|
|
111
134
|
const diffPackages: Record<string, string> = {};
|
|
@@ -114,7 +137,12 @@ export async function release(
|
|
|
114
137
|
// one that cannot be uploaded is left out of the history instead of failing the
|
|
115
138
|
// release the other two artifacts are ready for.
|
|
116
139
|
try {
|
|
117
|
-
const diffDownloadUrl = await uploadFile(bundleUploader, filePath, platform, identifier
|
|
140
|
+
const diffDownloadUrl = await uploadFile(bundleUploader, filePath, platform, identifier, {
|
|
141
|
+
type: 'asset-diff',
|
|
142
|
+
targetBinaryVersion: binaryVersion,
|
|
143
|
+
packageHash,
|
|
144
|
+
basePackageHash,
|
|
145
|
+
});
|
|
118
146
|
diffPackages[basePackageHash] = diffDownloadUrl;
|
|
119
147
|
console.log(`log: Asset diff archive against ${basePackageHash} uploaded (download url: ${diffDownloadUrl})`);
|
|
120
148
|
} catch (error) {
|
|
@@ -289,6 +317,7 @@ async function makeAssetDiffArtifacts({
|
|
|
289
317
|
bundleDownloader,
|
|
290
318
|
bundleDirectory,
|
|
291
319
|
packageHash,
|
|
320
|
+
targetBinaryVersion,
|
|
292
321
|
platform,
|
|
293
322
|
identifier,
|
|
294
323
|
}: {
|
|
@@ -298,6 +327,7 @@ async function makeAssetDiffArtifacts({
|
|
|
298
327
|
bundleDownloader: NonNullable<CliConfigInterface['bundleDownloader']>;
|
|
299
328
|
bundleDirectory: string;
|
|
300
329
|
packageHash: string;
|
|
330
|
+
targetBinaryVersion: string;
|
|
301
331
|
platform: 'ios' | 'android';
|
|
302
332
|
identifier: string | undefined;
|
|
303
333
|
}): Promise<AssetDiffArtifact[]> {
|
|
@@ -307,6 +337,7 @@ async function makeAssetDiffArtifacts({
|
|
|
307
337
|
bundleDownloader,
|
|
308
338
|
platform,
|
|
309
339
|
identifier,
|
|
340
|
+
targetBinaryVersion,
|
|
310
341
|
});
|
|
311
342
|
|
|
312
343
|
const artifacts: AssetDiffArtifact[] = [];
|
|
@@ -421,10 +452,11 @@ async function uploadArtifact(
|
|
|
421
452
|
filePath: string,
|
|
422
453
|
platform: 'ios' | 'android',
|
|
423
454
|
identifier: string | undefined,
|
|
455
|
+
artifact: BundleUploadArtifact,
|
|
424
456
|
artifactName: string,
|
|
425
457
|
): Promise<string> {
|
|
426
458
|
try {
|
|
427
|
-
return await uploadFile(bundleUploader, filePath, platform, identifier);
|
|
459
|
+
return await uploadFile(bundleUploader, filePath, platform, identifier, artifact);
|
|
428
460
|
} catch (error) {
|
|
429
461
|
console.error(`Failed to upload the ${artifactName} file. Exiting the program.\n`, error)
|
|
430
462
|
process.exit(1)
|
|
@@ -436,7 +468,8 @@ async function uploadFile(
|
|
|
436
468
|
filePath: string,
|
|
437
469
|
platform: 'ios' | 'android',
|
|
438
470
|
identifier: string | undefined,
|
|
471
|
+
artifact: BundleUploadArtifact,
|
|
439
472
|
): Promise<string> {
|
|
440
|
-
const { downloadUrl } = await bundleUploader(filePath, platform, identifier);
|
|
473
|
+
const { downloadUrl } = await bundleUploader(filePath, platform, identifier, artifact);
|
|
441
474
|
return downloadUrl
|
|
442
475
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
1
|
import type { CliConfigInterface } from "../../../typings/react-native-code-push.d.ts";
|
|
2
|
+
import { stageReleaseHistoryFile } from "../../functions/stageReleaseHistoryFile.js";
|
|
4
3
|
|
|
5
4
|
export async function updateReleaseHistory(
|
|
6
5
|
appVersion: string,
|
|
@@ -23,15 +22,8 @@ export async function updateReleaseHistory(
|
|
|
23
22
|
if (typeof rollout === "number") updateInfo.rollout = rollout;
|
|
24
23
|
|
|
25
24
|
try {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
console.log(`log: creating JSON file... ("${JSON_FILE_NAME}")\n`, JSON.stringify(releaseHistory, null, 2));
|
|
30
|
-
fs.writeFileSync(JSON_FILE_PATH, JSON.stringify(releaseHistory));
|
|
31
|
-
|
|
32
|
-
await setReleaseHistory(binaryVersion, JSON_FILE_PATH, releaseHistory, platform, identifier)
|
|
33
|
-
|
|
34
|
-
fs.unlinkSync(JSON_FILE_PATH);
|
|
25
|
+
await stageReleaseHistoryFile(binaryVersion, releaseHistory, platform, (jsonFilePath) =>
|
|
26
|
+
setReleaseHistory(binaryVersion, jsonFilePath, releaseHistory, platform, identifier));
|
|
35
27
|
} catch (error) {
|
|
36
28
|
console.error('Error occurred while updating history:', error);
|
|
37
29
|
process.exit(1)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path from "path";
|
|
1
|
+
import { stageReleaseHistoryFile } from "../../functions/stageReleaseHistoryFile.js";
|
|
3
2
|
export async function createReleaseHistory(targetVersion, setReleaseHistory, platform, identifier) {
|
|
4
3
|
const BINARY_RELEASE = {
|
|
5
4
|
enabled: true,
|
|
@@ -11,12 +10,7 @@ export async function createReleaseHistory(targetVersion, setReleaseHistory, pla
|
|
|
11
10
|
[targetVersion]: BINARY_RELEASE
|
|
12
11
|
};
|
|
13
12
|
try {
|
|
14
|
-
|
|
15
|
-
const JSON_FILE_PATH = path.resolve(process.cwd(), JSON_FILE_NAME);
|
|
16
|
-
console.log(`log: creating JSON file... ("${JSON_FILE_NAME}")\n`, JSON.stringify(INITIAL_HISTORY, null, 2));
|
|
17
|
-
fs.writeFileSync(JSON_FILE_PATH, JSON.stringify(INITIAL_HISTORY));
|
|
18
|
-
await setReleaseHistory(targetVersion, JSON_FILE_PATH, INITIAL_HISTORY, platform, identifier);
|
|
19
|
-
fs.unlinkSync(JSON_FILE_PATH);
|
|
13
|
+
await stageReleaseHistoryFile(targetVersion, INITIAL_HISTORY, platform, (jsonFilePath) => setReleaseHistory(targetVersion, jsonFilePath, INITIAL_HISTORY, platform, identifier));
|
|
20
14
|
}
|
|
21
15
|
catch (error) {
|
|
22
16
|
console.error('Error occurred while creating new history:', error);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, jest } from "@jest/globals";
|
|
5
|
+
import { createReleaseHistory } from "./createReleaseHistory.js";
|
|
6
|
+
import { updateReleaseHistory } from "../updateHistoryCommand/updateReleaseHistory.js";
|
|
7
|
+
/**
|
|
8
|
+
* Covers where the release history is staged before the config is handed it.
|
|
9
|
+
*
|
|
10
|
+
* The config receives a path rather than the history itself, so the command has to put the
|
|
11
|
+
* file somewhere first. Two releases of one app for different platforms are the same binary
|
|
12
|
+
* version, so a staging path derived from that version alone is the same path for both, and
|
|
13
|
+
* they overwrite each other's contents before either has read its own.
|
|
14
|
+
*/
|
|
15
|
+
const BINARY_VERSION = "1.0.0";
|
|
16
|
+
let workingDir;
|
|
17
|
+
let previousCwd;
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
previousCwd = process.cwd();
|
|
20
|
+
workingDir = fs.mkdtempSync(path.join(os.tmpdir(), "codepush-history-staging-"));
|
|
21
|
+
process.chdir(workingDir);
|
|
22
|
+
});
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
process.chdir(previousCwd);
|
|
25
|
+
fs.rmSync(workingDir, { recursive: true, force: true });
|
|
26
|
+
jest.restoreAllMocks();
|
|
27
|
+
});
|
|
28
|
+
function stagedPath(platform) {
|
|
29
|
+
return path.join(workingDir, "codepush-release-history", platform, `${BINARY_VERSION}.json`);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Turns the command's own exit into a throw, so a staging failure fails the test instead
|
|
33
|
+
* of taking the worker down with it.
|
|
34
|
+
*/
|
|
35
|
+
function failOnExit() {
|
|
36
|
+
jest.spyOn(process, "exit").mockImplementation((code) => {
|
|
37
|
+
throw new Error(`the command exited with code ${String(code)}`);
|
|
38
|
+
});
|
|
39
|
+
jest.spyOn(console, "error").mockImplementation(() => undefined);
|
|
40
|
+
jest.spyOn(console, "log").mockImplementation(() => undefined);
|
|
41
|
+
}
|
|
42
|
+
describe("staging the release history a config is handed", () => {
|
|
43
|
+
it("gives each platform writing at the same time a file of its own", async () => {
|
|
44
|
+
failOnExit();
|
|
45
|
+
const staged = {};
|
|
46
|
+
const setReleaseHistory = async (_binaryVersion, jsonFilePath, _releaseInfo, platform) => {
|
|
47
|
+
// Yields once, which is all it takes for the other platform to reach its own
|
|
48
|
+
// write and its own clean-up while this one still holds only a path.
|
|
49
|
+
await Promise.resolve();
|
|
50
|
+
staged[platform] = { jsonFilePath, contents: fs.readFileSync(jsonFilePath, "utf8") };
|
|
51
|
+
};
|
|
52
|
+
await Promise.all([
|
|
53
|
+
createReleaseHistory(BINARY_VERSION, setReleaseHistory, "ios", "RN0840"),
|
|
54
|
+
createReleaseHistory(BINARY_VERSION, setReleaseHistory, "android", "RN0840"),
|
|
55
|
+
]);
|
|
56
|
+
expect(staged.ios.jsonFilePath).not.toBe(staged.android.jsonFilePath);
|
|
57
|
+
});
|
|
58
|
+
it("hands each platform the history that platform released", async () => {
|
|
59
|
+
failOnExit();
|
|
60
|
+
const released = {
|
|
61
|
+
ios: JSON.stringify({ [BINARY_VERSION]: { enabled: true, mandatory: false, downloadUrl: "", packageHash: "" }, "1.0.1": { enabled: true, mandatory: true, downloadUrl: "ios-url", packageHash: "ios-hash" } }),
|
|
62
|
+
android: JSON.stringify({ [BINARY_VERSION]: { enabled: true, mandatory: false, downloadUrl: "", packageHash: "" }, "1.0.1": { enabled: true, mandatory: true, downloadUrl: "android-url", packageHash: "android-hash" } }),
|
|
63
|
+
};
|
|
64
|
+
const staged = {};
|
|
65
|
+
const getReleaseHistory = async (_binaryVersion, platform) => JSON.parse(released[platform]);
|
|
66
|
+
const setReleaseHistory = async (_binaryVersion, jsonFilePath, _releaseInfo, platform) => {
|
|
67
|
+
await Promise.resolve();
|
|
68
|
+
staged[platform] = fs.readFileSync(jsonFilePath, "utf8");
|
|
69
|
+
};
|
|
70
|
+
await Promise.all([
|
|
71
|
+
updateReleaseHistory("1.0.1", BINARY_VERSION, getReleaseHistory, setReleaseHistory, "ios", "RN0840", undefined, false, undefined),
|
|
72
|
+
updateReleaseHistory("1.0.1", BINARY_VERSION, getReleaseHistory, setReleaseHistory, "android", "RN0840", undefined, false, undefined),
|
|
73
|
+
]);
|
|
74
|
+
expect(staged.ios).toContain("ios-url");
|
|
75
|
+
expect(staged.ios).not.toContain("android-url");
|
|
76
|
+
expect(staged.android).toContain("android-url");
|
|
77
|
+
expect(staged.android).not.toContain("ios-url");
|
|
78
|
+
});
|
|
79
|
+
it("takes the file away once the config has stored the history", async () => {
|
|
80
|
+
failOnExit();
|
|
81
|
+
const setReleaseHistory = async () => undefined;
|
|
82
|
+
await createReleaseHistory(BINARY_VERSION, setReleaseHistory, "ios", "RN0840");
|
|
83
|
+
expect(fs.existsSync(stagedPath("ios"))).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
it("leaves the file behind when the config could not store the history", async () => {
|
|
86
|
+
failOnExit();
|
|
87
|
+
const setReleaseHistory = async () => {
|
|
88
|
+
throw new Error("the storage backend rejected the history");
|
|
89
|
+
};
|
|
90
|
+
await expect(createReleaseHistory(BINARY_VERSION, setReleaseHistory, "ios", "RN0840")).rejects.toThrow("the command exited with code 1");
|
|
91
|
+
// What the release tried to store is worth reading back, so a failed release keeps
|
|
92
|
+
// its history at a path that is the same on every run.
|
|
93
|
+
expect(Object.keys(JSON.parse(fs.readFileSync(stagedPath("ios"), "utf8")))).toEqual([BINARY_VERSION]);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import fs from "fs";
|
|
1
|
+
import { stageReleaseHistoryFile } from "../../functions/stageReleaseHistoryFile.js";
|
|
3
2
|
export async function addToReleaseHistory(appVersion, binaryVersion, bundleDownloadUrl, binaryPatchDownloadUrl, packageHash, getReleaseHistory, setReleaseHistory, platform, identifier, mandatory, enable, rollout, diffPackages) {
|
|
4
3
|
const releaseHistory = await getReleaseHistory(binaryVersion, platform, identifier);
|
|
5
4
|
const updateInfo = releaseHistory[appVersion];
|
|
@@ -28,12 +27,7 @@ export async function addToReleaseHistory(appVersion, binaryVersion, bundleDownl
|
|
|
28
27
|
newReleaseHistory[appVersion].rollout = rollout;
|
|
29
28
|
}
|
|
30
29
|
try {
|
|
31
|
-
|
|
32
|
-
const JSON_FILE_PATH = path.resolve(process.cwd(), JSON_FILE_NAME);
|
|
33
|
-
console.log(`log: creating JSON file... ("${JSON_FILE_NAME}")\n`, JSON.stringify(newReleaseHistory, null, 2));
|
|
34
|
-
fs.writeFileSync(JSON_FILE_PATH, JSON.stringify(newReleaseHistory));
|
|
35
|
-
await setReleaseHistory(binaryVersion, JSON_FILE_PATH, newReleaseHistory, platform, identifier);
|
|
36
|
-
fs.unlinkSync(JSON_FILE_PATH);
|
|
30
|
+
await stageReleaseHistoryFile(binaryVersion, newReleaseHistory, platform, (jsonFilePath) => setReleaseHistory(binaryVersion, jsonFilePath, newReleaseHistory, platform, identifier));
|
|
37
31
|
}
|
|
38
32
|
catch (error) {
|
|
39
33
|
console.error('Error occurred while updating history:', error);
|
|
@@ -51,16 +51,25 @@ export async function release(bundleUploader, getReleaseHistory, setReleaseHisto
|
|
|
51
51
|
bundleDownloader,
|
|
52
52
|
bundleDirectory,
|
|
53
53
|
packageHash,
|
|
54
|
+
targetBinaryVersion: binaryVersion,
|
|
54
55
|
platform,
|
|
55
56
|
identifier,
|
|
56
57
|
})
|
|
57
58
|
: [];
|
|
58
59
|
// Every artifact is uploaded before the release history is touched, so a failed
|
|
59
60
|
// upload leaves the history describing only updates that can actually be downloaded.
|
|
60
|
-
const downloadUrl = await uploadArtifact(bundleUploader, bundleFilePath, platform, identifier,
|
|
61
|
+
const downloadUrl = await uploadArtifact(bundleUploader, bundleFilePath, platform, identifier, {
|
|
62
|
+
type: 'full-bundle',
|
|
63
|
+
targetBinaryVersion: binaryVersion,
|
|
64
|
+
packageHash,
|
|
65
|
+
}, 'bundle');
|
|
61
66
|
let patchDownloadUrl;
|
|
62
67
|
if (binaryPatch) {
|
|
63
|
-
patchDownloadUrl = await uploadArtifact(bundleUploader, binaryPatch.patchBundleFilePath, platform, identifier,
|
|
68
|
+
patchDownloadUrl = await uploadArtifact(bundleUploader, binaryPatch.patchBundleFilePath, platform, identifier, {
|
|
69
|
+
type: 'binary-patch',
|
|
70
|
+
targetBinaryVersion: binaryVersion,
|
|
71
|
+
packageHash,
|
|
72
|
+
}, 'binary patch bundle');
|
|
64
73
|
console.log(`log: Binary patch archive uploaded (download url: ${patchDownloadUrl})`);
|
|
65
74
|
}
|
|
66
75
|
const diffPackages = {};
|
|
@@ -69,7 +78,12 @@ export async function release(bundleUploader, getReleaseHistory, setReleaseHisto
|
|
|
69
78
|
// one that cannot be uploaded is left out of the history instead of failing the
|
|
70
79
|
// release the other two artifacts are ready for.
|
|
71
80
|
try {
|
|
72
|
-
const diffDownloadUrl = await uploadFile(bundleUploader, filePath, platform, identifier
|
|
81
|
+
const diffDownloadUrl = await uploadFile(bundleUploader, filePath, platform, identifier, {
|
|
82
|
+
type: 'asset-diff',
|
|
83
|
+
targetBinaryVersion: binaryVersion,
|
|
84
|
+
packageHash,
|
|
85
|
+
basePackageHash,
|
|
86
|
+
});
|
|
73
87
|
diffPackages[basePackageHash] = diffDownloadUrl;
|
|
74
88
|
console.log(`log: Asset diff archive against ${basePackageHash} uploaded (download url: ${diffDownloadUrl})`);
|
|
75
89
|
}
|
|
@@ -180,13 +194,14 @@ async function makeBinaryPatchArtifact({ baseBundlePath, contentsPath, bundleFil
|
|
|
180
194
|
*
|
|
181
195
|
* @return {Promise<AssetDiffArtifact[]>} The diff archives to upload, each naming the package it was built against.
|
|
182
196
|
*/
|
|
183
|
-
async function makeAssetDiffArtifacts({ patchBundleFilePath, releaseHistory, diffBaseCount, bundleDownloader, bundleDirectory, packageHash, platform, identifier, }) {
|
|
197
|
+
async function makeAssetDiffArtifacts({ patchBundleFilePath, releaseHistory, diffBaseCount, bundleDownloader, bundleDirectory, packageHash, targetBinaryVersion, platform, identifier, }) {
|
|
184
198
|
const bases = await resolveAssetDiffBases({
|
|
185
199
|
releaseHistory,
|
|
186
200
|
diffBaseCount,
|
|
187
201
|
bundleDownloader,
|
|
188
202
|
platform,
|
|
189
203
|
identifier,
|
|
204
|
+
targetBinaryVersion,
|
|
190
205
|
});
|
|
191
206
|
const artifacts = [];
|
|
192
207
|
const builtAgainst = new Set();
|
|
@@ -278,16 +293,16 @@ function warnOnBaseBundleMismatch(outputPath, baseBundlePath) {
|
|
|
278
293
|
}
|
|
279
294
|
}
|
|
280
295
|
/** Uploads an artifact the release cannot go out without, so a failure ends the program. */
|
|
281
|
-
async function uploadArtifact(bundleUploader, filePath, platform, identifier, artifactName) {
|
|
296
|
+
async function uploadArtifact(bundleUploader, filePath, platform, identifier, artifact, artifactName) {
|
|
282
297
|
try {
|
|
283
|
-
return await uploadFile(bundleUploader, filePath, platform, identifier);
|
|
298
|
+
return await uploadFile(bundleUploader, filePath, platform, identifier, artifact);
|
|
284
299
|
}
|
|
285
300
|
catch (error) {
|
|
286
301
|
console.error(`Failed to upload the ${artifactName} file. Exiting the program.\n`, error);
|
|
287
302
|
process.exit(1);
|
|
288
303
|
}
|
|
289
304
|
}
|
|
290
|
-
async function uploadFile(bundleUploader, filePath, platform, identifier) {
|
|
291
|
-
const { downloadUrl } = await bundleUploader(filePath, platform, identifier);
|
|
305
|
+
async function uploadFile(bundleUploader, filePath, platform, identifier, artifact) {
|
|
306
|
+
const { downloadUrl } = await bundleUploader(filePath, platform, identifier, artifact);
|
|
292
307
|
return downloadUrl;
|
|
293
308
|
}
|
|
@@ -66,12 +66,12 @@ class ProcessExitError extends Error {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
function recordingUploader(uploads, failOn) {
|
|
69
|
-
return async (filePath) => {
|
|
69
|
+
return async (filePath, _platform, _identifier, artifact) => {
|
|
70
70
|
if (failOn?.(filePath)) {
|
|
71
71
|
throw new Error(`upload rejected: ${path.basename(filePath)}`);
|
|
72
72
|
}
|
|
73
73
|
const downloadUrl = `https://cdn.example.com/${path.basename(filePath)}`;
|
|
74
|
-
uploads.push({ filePath, downloadUrl, logCountBeforeUpload: logs.length });
|
|
74
|
+
uploads.push({ artifact, filePath, downloadUrl, logCountBeforeUpload: logs.length });
|
|
75
75
|
return { downloadUrl };
|
|
76
76
|
};
|
|
77
77
|
}
|
|
@@ -119,16 +119,26 @@ beforeEach(() => {
|
|
|
119
119
|
});
|
|
120
120
|
afterEach(() => {
|
|
121
121
|
jest.restoreAllMocks();
|
|
122
|
-
// addToReleaseHistory writes its JSON
|
|
123
|
-
// when the history was stored successfully.
|
|
124
|
-
fs.rmSync(path.resolve(process.cwd(),
|
|
122
|
+
// addToReleaseHistory writes its JSON under the directory it was invoked in, and only
|
|
123
|
+
// removes it when the history was stored successfully.
|
|
124
|
+
fs.rmSync(path.resolve(process.cwd(), "codepush-release-history"), { recursive: true, force: true });
|
|
125
125
|
});
|
|
126
126
|
describe("release without --binary-bundle-path", () => {
|
|
127
|
+
it("keeps existing three-argument uploaders callable", async () => {
|
|
128
|
+
const legacyUploader = async (source, _platform, _identifier) => ({ downloadUrl: source });
|
|
129
|
+
const uploader = legacyUploader;
|
|
130
|
+
await expect(uploader('bundle.zip', 'ios')).resolves.toEqual({ downloadUrl: 'bundle.zip' });
|
|
131
|
+
});
|
|
127
132
|
it("uploads the full bundle only, exactly as before binary patches existed", async () => {
|
|
128
133
|
const staged = await stageBundleOutput("full-only");
|
|
129
134
|
const { uploads, releaseHistories } = await runRelease(staged);
|
|
130
135
|
expect(uploads).toHaveLength(1);
|
|
131
136
|
expect(uploads[0].filePath).toBe(`${staged.bundleDirectory}/${staged.bundleFileName}`);
|
|
137
|
+
expect(uploads[0].artifact).toEqual({
|
|
138
|
+
type: 'full-bundle',
|
|
139
|
+
targetBinaryVersion: BINARY_VERSION,
|
|
140
|
+
packageHash: staged.bundleFileName,
|
|
141
|
+
});
|
|
132
142
|
expect(fs.readdirSync(staged.bundleDirectory)).toEqual([staged.bundleFileName]);
|
|
133
143
|
expect(releaseHistories[0][APP_VERSION]).toEqual({
|
|
134
144
|
enabled: true,
|
|
@@ -426,11 +436,11 @@ describe("release with asset diff bases", () => {
|
|
|
426
436
|
}
|
|
427
437
|
/** Serves the staged base archives, and records which releases were asked for. */
|
|
428
438
|
function recordingDownloader(bases, downloads) {
|
|
429
|
-
return async (
|
|
430
|
-
downloads.push(downloadUrl);
|
|
431
|
-
const base = bases.find((candidate) => candidate.downloadUrl === downloadUrl);
|
|
439
|
+
return async (archive) => {
|
|
440
|
+
downloads.push(archive.downloadUrl);
|
|
441
|
+
const base = bases.find((candidate) => candidate.downloadUrl === archive.downloadUrl);
|
|
432
442
|
if (!base) {
|
|
433
|
-
throw new Error(`the downloader was called with an unexpected url: ${downloadUrl}`);
|
|
443
|
+
throw new Error(`the downloader was called with an unexpected url: ${archive.downloadUrl}`);
|
|
434
444
|
}
|
|
435
445
|
return { downloadedFilePath: base.archivePath };
|
|
436
446
|
};
|
|
@@ -452,6 +462,24 @@ describe("release with asset diff bases", () => {
|
|
|
452
462
|
`${staged.bundleFileName}${BINARY_PATCH_ARCHIVE_SUFFIX}`,
|
|
453
463
|
diffFileName,
|
|
454
464
|
]);
|
|
465
|
+
expect(uploads.map(({ artifact }) => artifact)).toEqual([
|
|
466
|
+
{
|
|
467
|
+
type: 'full-bundle',
|
|
468
|
+
targetBinaryVersion: BINARY_VERSION,
|
|
469
|
+
packageHash: staged.bundleFileName,
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
type: 'binary-patch',
|
|
473
|
+
targetBinaryVersion: BINARY_VERSION,
|
|
474
|
+
packageHash: staged.bundleFileName,
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
type: 'asset-diff',
|
|
478
|
+
targetBinaryVersion: BINARY_VERSION,
|
|
479
|
+
packageHash: staged.bundleFileName,
|
|
480
|
+
basePackageHash: base.packageHash,
|
|
481
|
+
},
|
|
482
|
+
]);
|
|
455
483
|
expect(downloads).toEqual([base.downloadUrl]);
|
|
456
484
|
// Every artifact is uploaded before the history points at any of them.
|
|
457
485
|
expect(uploadCountsWhenHistorySaved).toEqual([3]);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path from "path";
|
|
1
|
+
import { stageReleaseHistoryFile } from "../../functions/stageReleaseHistoryFile.js";
|
|
3
2
|
export async function updateReleaseHistory(appVersion, binaryVersion, getReleaseHistory, setReleaseHistory, platform, identifier, mandatory, enable, rollout) {
|
|
4
3
|
const releaseHistory = await getReleaseHistory(binaryVersion, platform, identifier);
|
|
5
4
|
const updateInfo = releaseHistory[appVersion];
|
|
@@ -12,12 +11,7 @@ export async function updateReleaseHistory(appVersion, binaryVersion, getRelease
|
|
|
12
11
|
if (typeof rollout === "number")
|
|
13
12
|
updateInfo.rollout = rollout;
|
|
14
13
|
try {
|
|
15
|
-
|
|
16
|
-
const JSON_FILE_PATH = path.resolve(process.cwd(), JSON_FILE_NAME);
|
|
17
|
-
console.log(`log: creating JSON file... ("${JSON_FILE_NAME}")\n`, JSON.stringify(releaseHistory, null, 2));
|
|
18
|
-
fs.writeFileSync(JSON_FILE_PATH, JSON.stringify(releaseHistory));
|
|
19
|
-
await setReleaseHistory(binaryVersion, JSON_FILE_PATH, releaseHistory, platform, identifier);
|
|
20
|
-
fs.unlinkSync(JSON_FILE_PATH);
|
|
14
|
+
await stageReleaseHistoryFile(binaryVersion, releaseHistory, platform, (jsonFilePath) => setReleaseHistory(binaryVersion, jsonFilePath, releaseHistory, platform, identifier));
|
|
21
15
|
}
|
|
22
16
|
catch (error) {
|
|
23
17
|
console.error('Error occurred while updating history:', error);
|
|
@@ -25,7 +25,7 @@ const TEMP_DIR_PREFIX = 'codepush-asset-diff-base-';
|
|
|
25
25
|
* @param bundleDownloader Consumer-provided downloader for a released archive
|
|
26
26
|
* @return The bases that downloaded and verified, newest release first.
|
|
27
27
|
*/
|
|
28
|
-
export async function resolveAssetDiffBases({ releaseHistory, diffBaseCount, bundleDownloader, platform, identifier, }) {
|
|
28
|
+
export async function resolveAssetDiffBases({ releaseHistory, diffBaseCount, bundleDownloader, platform, identifier, targetBinaryVersion, }) {
|
|
29
29
|
// The seeded entry for the update inside the app binary has neither a url nor a hash,
|
|
30
30
|
// and nothing can be diffed against a package that was never published. A key that is
|
|
31
31
|
// not a version cannot be ordered against the others either, and ordering is what picks
|
|
@@ -37,7 +37,12 @@ export async function resolveAssetDiffBases({ releaseHistory, diffBaseCount, bun
|
|
|
37
37
|
const bases = [];
|
|
38
38
|
for (const [version, releaseInfo] of candidates) {
|
|
39
39
|
try {
|
|
40
|
-
const { downloadedFilePath } = await bundleDownloader(
|
|
40
|
+
const { downloadedFilePath } = await bundleDownloader({
|
|
41
|
+
downloadUrl: releaseInfo.downloadUrl,
|
|
42
|
+
targetBinaryVersion,
|
|
43
|
+
releaseVersion: version,
|
|
44
|
+
packageHash: releaseInfo.packageHash,
|
|
45
|
+
}, platform, identifier);
|
|
41
46
|
if (!(await matchesPackageHash(downloadedFilePath, releaseInfo.packageHash))) {
|
|
42
47
|
console.warn(`warn: Skipping the asset diff base v${version}: the downloaded archive does not hash to the ` +
|
|
43
48
|
`package hash recorded for it (${releaseInfo.packageHash}).`);
|