code-push-itspar 1.0.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.
- package/LICENSE.txt +22 -0
- package/README.md +174 -0
- package/bin/script/acquisition-sdk.js +178 -0
- package/bin/script/cli.js +23 -0
- package/bin/script/command-executor.js +1392 -0
- package/bin/script/command-parser.js +1225 -0
- package/bin/script/commands/debug.js +125 -0
- package/bin/script/hash-utils.js +203 -0
- package/bin/script/index.js +5 -0
- package/bin/script/management-sdk.js +531 -0
- package/bin/script/patch-scripts/apply-patch.sh +111 -0
- package/bin/script/patch-scripts/create-patch.sh +53 -0
- package/bin/script/react-native-utils.js +249 -0
- package/bin/script/sign.js +69 -0
- package/bin/script/types/cli.js +50 -0
- package/bin/script/types/rest-definitions.js +19 -0
- package/bin/script/types.js +4 -0
- package/bin/script/utils/config.constants.js +13 -0
- package/bin/script/utils/file-utils.js +50 -0
- package/bin/test/acquisition-rest-mock.js +108 -0
- package/bin/test/acquisition-sdk.js +188 -0
- package/bin/test/cli.js +1342 -0
- package/bin/test/hash-utils.js +149 -0
- package/bin/test/management-sdk.js +338 -0
- package/bsdiff/README.md +114 -0
- package/bsdiff/bsdiff43 +0 -0
- package/package.json +107 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) Microsoft Corporation.
|
|
3
|
+
// Licensed under the MIT License.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
const assert = require("assert");
|
|
6
|
+
const crypto = require("crypto");
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const hashUtils = require("../script/hash-utils");
|
|
9
|
+
var mkdirp = require("mkdirp");
|
|
10
|
+
const os = require("os");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const q = require("q");
|
|
13
|
+
var yauzl = require("yauzl");
|
|
14
|
+
function randomString() {
|
|
15
|
+
var stringLength = 10;
|
|
16
|
+
return crypto
|
|
17
|
+
.randomBytes(Math.ceil(stringLength / 2))
|
|
18
|
+
.toString("hex") // convert to hexadecimal format
|
|
19
|
+
.slice(0, stringLength); // return required number of characters
|
|
20
|
+
}
|
|
21
|
+
function unzipToDirectory(zipPath, directoryPath) {
|
|
22
|
+
var deferred = q.defer();
|
|
23
|
+
var originalCwd = process.cwd();
|
|
24
|
+
mkdirp(directoryPath, (err) => {
|
|
25
|
+
if (err)
|
|
26
|
+
throw err;
|
|
27
|
+
process.chdir(directoryPath);
|
|
28
|
+
yauzl.open(zipPath, { lazyEntries: true }, function (err, zipfile) {
|
|
29
|
+
if (err)
|
|
30
|
+
throw err;
|
|
31
|
+
zipfile.readEntry();
|
|
32
|
+
zipfile.on("entry", function (entry) {
|
|
33
|
+
if (/\/$/.test(entry.fileName)) {
|
|
34
|
+
// directory file names end with '/'
|
|
35
|
+
mkdirp(entry.fileName, function (err) {
|
|
36
|
+
if (err)
|
|
37
|
+
throw err;
|
|
38
|
+
zipfile.readEntry();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// file entry
|
|
43
|
+
zipfile.openReadStream(entry, function (err, readStream) {
|
|
44
|
+
if (err)
|
|
45
|
+
throw err;
|
|
46
|
+
// ensure parent directory exists
|
|
47
|
+
mkdirp(path.dirname(entry.fileName), function (err) {
|
|
48
|
+
if (err)
|
|
49
|
+
throw err;
|
|
50
|
+
readStream.pipe(fs.createWriteStream(entry.fileName));
|
|
51
|
+
readStream.on("end", function () {
|
|
52
|
+
zipfile.readEntry();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
zipfile.on("end", function (err) {
|
|
59
|
+
if (err)
|
|
60
|
+
deferred.reject(err);
|
|
61
|
+
else
|
|
62
|
+
deferred.resolve(null);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
return deferred.promise.finally(() => {
|
|
67
|
+
process.chdir(originalCwd);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
describe("Hashing utility", () => {
|
|
71
|
+
const TEST_DIRECTORY = path.join(os.tmpdir(), "codepushtests", randomString());
|
|
72
|
+
const TEST_ARCHIVE_FILE_PATH = path.join(__dirname, "resources", "test.zip");
|
|
73
|
+
const TEST_ZIP_HASH = "540fed8df3553079e81d1353c5cc4e3cac7db9aea647a85d550f646e8620c317";
|
|
74
|
+
const TEST_ZIP_MANIFEST_HASH = "9e0499ce7df5c04cb304c9deed684dc137fc603cb484a5b027478143c595d80b";
|
|
75
|
+
const HASH_B = "3e23e8160039594a33894f6564e1b1348bbd7a0088d42c4acb73eeaed59c009d";
|
|
76
|
+
const HASH_C = "2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6";
|
|
77
|
+
const HASH_D = "18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8e4";
|
|
78
|
+
const IGNORED_METADATA_ARCHIVE_FILE_PATH = path.join(__dirname, "resources", "ignoredMetadata.zip");
|
|
79
|
+
const INDEX_HASH = "b0693dc92f76e08bf1485b3dd9b514a2e31dfd6f39422a6b60edb722671dc98f";
|
|
80
|
+
it("generates a package hash from file", (done) => {
|
|
81
|
+
hashUtils.hashFile(TEST_ARCHIVE_FILE_PATH).done((packageHash) => {
|
|
82
|
+
assert.equal(packageHash, TEST_ZIP_HASH);
|
|
83
|
+
done();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
it("generates a package manifest for an archive", (done) => {
|
|
87
|
+
hashUtils.generatePackageManifestFromZip(TEST_ARCHIVE_FILE_PATH).done((manifest) => {
|
|
88
|
+
var fileHashesMap = manifest.toMap();
|
|
89
|
+
assert.equal(fileHashesMap.size, 3);
|
|
90
|
+
var hash = fileHashesMap.get("b.txt");
|
|
91
|
+
assert.equal(hash, HASH_B);
|
|
92
|
+
hash = fileHashesMap.get("c.txt");
|
|
93
|
+
assert.equal(hash, HASH_C);
|
|
94
|
+
hash = fileHashesMap.get("d.txt");
|
|
95
|
+
assert.equal(hash, HASH_D);
|
|
96
|
+
done();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
it("generates a package manifest for a directory", (done) => {
|
|
100
|
+
var directory = path.join(TEST_DIRECTORY, "testZip");
|
|
101
|
+
unzipToDirectory(TEST_ARCHIVE_FILE_PATH, directory)
|
|
102
|
+
.then(() => {
|
|
103
|
+
return hashUtils.generatePackageManifestFromDirectory(/*directoryPath*/ directory, /*basePath*/ directory);
|
|
104
|
+
})
|
|
105
|
+
.done((manifest) => {
|
|
106
|
+
var fileHashesMap = manifest.toMap();
|
|
107
|
+
assert.equal(fileHashesMap.size, 3);
|
|
108
|
+
var hash = fileHashesMap.get("b.txt");
|
|
109
|
+
assert.equal(hash, HASH_B);
|
|
110
|
+
hash = fileHashesMap.get("c.txt");
|
|
111
|
+
assert.equal(hash, HASH_C);
|
|
112
|
+
hash = fileHashesMap.get("d.txt");
|
|
113
|
+
assert.equal(hash, HASH_D);
|
|
114
|
+
done();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
it("generates a package hash from manifest", (done) => {
|
|
118
|
+
hashUtils
|
|
119
|
+
.generatePackageManifestFromZip(TEST_ARCHIVE_FILE_PATH)
|
|
120
|
+
.then((manifest) => {
|
|
121
|
+
return manifest.computePackageHash();
|
|
122
|
+
})
|
|
123
|
+
.done((packageHash) => {
|
|
124
|
+
assert.equal(packageHash, TEST_ZIP_MANIFEST_HASH);
|
|
125
|
+
done();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
it("generates a package manifest for an archive with ignorable metadata", (done) => {
|
|
129
|
+
hashUtils.generatePackageManifestFromZip(IGNORED_METADATA_ARCHIVE_FILE_PATH).done((manifest) => {
|
|
130
|
+
assert.equal(manifest.toMap().size, 1);
|
|
131
|
+
var hash = manifest.toMap().get("www/index.html");
|
|
132
|
+
assert.equal(hash, INDEX_HASH);
|
|
133
|
+
done();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
it("generates a package manifest for a directory with ignorable metadata", (done) => {
|
|
137
|
+
var directory = path.join(TEST_DIRECTORY, "ignorableMetadata");
|
|
138
|
+
unzipToDirectory(IGNORED_METADATA_ARCHIVE_FILE_PATH, directory)
|
|
139
|
+
.then(() => {
|
|
140
|
+
return hashUtils.generatePackageManifestFromDirectory(/*directoryPath*/ directory, /*basePath*/ directory);
|
|
141
|
+
})
|
|
142
|
+
.done((manifest) => {
|
|
143
|
+
assert.equal(manifest.toMap().size, 1);
|
|
144
|
+
var hash = manifest.toMap().get("www/index.html");
|
|
145
|
+
assert.equal(hash, INDEX_HASH);
|
|
146
|
+
done();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) Microsoft Corporation.
|
|
3
|
+
// Licensed under the MIT License.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
const assert = require("assert");
|
|
6
|
+
const Q = require("q");
|
|
7
|
+
const AccountManager = require("../script/management-sdk");
|
|
8
|
+
var request = require("superagent");
|
|
9
|
+
var manager;
|
|
10
|
+
describe("Management SDK", () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
manager = new AccountManager(/*accessKey=*/ "dummyAccessKey", /*customHeaders=*/ null, /*serverUrl=*/ "http://localhost");
|
|
13
|
+
});
|
|
14
|
+
after(() => {
|
|
15
|
+
// Prevent an exception that occurs due to how superagent-mock overwrites methods
|
|
16
|
+
request.Request.prototype._callback = function () { };
|
|
17
|
+
});
|
|
18
|
+
it("methods reject the promise with status code info when an error occurs", (done) => {
|
|
19
|
+
mockReturn("Text", 404);
|
|
20
|
+
var methodsWithErrorHandling = [
|
|
21
|
+
manager.addApp.bind(manager, "appName"),
|
|
22
|
+
manager.getApp.bind(manager, "appName"),
|
|
23
|
+
manager.renameApp.bind(manager, "appName", {}),
|
|
24
|
+
manager.removeApp.bind(manager, "appName"),
|
|
25
|
+
manager.transferApp.bind(manager, "appName", "email1"),
|
|
26
|
+
manager.addDeployment.bind(manager, "appName", "deploymentName"),
|
|
27
|
+
manager.getDeployment.bind(manager, "appName", "deploymentName"),
|
|
28
|
+
manager.getDeployments.bind(manager, "appName"),
|
|
29
|
+
manager.renameDeployment.bind(manager, "appName", "deploymentName", {
|
|
30
|
+
name: "newDeploymentName",
|
|
31
|
+
}),
|
|
32
|
+
manager.removeDeployment.bind(manager, "appName", "deploymentName"),
|
|
33
|
+
manager.addCollaborator.bind(manager, "appName", "email1"),
|
|
34
|
+
manager.getCollaborators.bind(manager, "appName"),
|
|
35
|
+
manager.removeCollaborator.bind(manager, "appName", "email1"),
|
|
36
|
+
manager.patchRelease.bind(manager, "appName", "deploymentName", "label", {
|
|
37
|
+
description: "newDescription",
|
|
38
|
+
}),
|
|
39
|
+
manager.promote.bind(manager, "appName", "deploymentName", "newDeploymentName", { description: "newDescription" }),
|
|
40
|
+
manager.rollback.bind(manager, "appName", "deploymentName", "targetReleaseLabel"),
|
|
41
|
+
];
|
|
42
|
+
var result = Q(null);
|
|
43
|
+
methodsWithErrorHandling.forEach(function (f) {
|
|
44
|
+
result = result.then(() => {
|
|
45
|
+
return testErrors(f);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
result.done(() => {
|
|
49
|
+
done();
|
|
50
|
+
});
|
|
51
|
+
// Test that the proper error code and text is passed through on a server error
|
|
52
|
+
function testErrors(method) {
|
|
53
|
+
return Q.Promise((resolve, reject, notify) => {
|
|
54
|
+
method().done(() => {
|
|
55
|
+
assert.fail("Should have thrown an error");
|
|
56
|
+
reject();
|
|
57
|
+
}, (error) => {
|
|
58
|
+
assert.equal(error.message, "Text");
|
|
59
|
+
assert(error.statusCode);
|
|
60
|
+
resolve();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
it("isAuthenticated handles successful auth", (done) => {
|
|
66
|
+
mockReturn(JSON.stringify({ authenticated: true }), 200, {});
|
|
67
|
+
manager.isAuthenticated().done((authenticated) => {
|
|
68
|
+
assert(authenticated, "Should be authenticated");
|
|
69
|
+
done();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
it("isAuthenticated handles unsuccessful auth", (done) => {
|
|
73
|
+
mockReturn("Unauthorized", 401, {});
|
|
74
|
+
manager.isAuthenticated().done((authenticated) => {
|
|
75
|
+
assert(!authenticated, "Should not be authenticated");
|
|
76
|
+
done();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
it("isAuthenticated handles unsuccessful auth with promise rejection", (done) => {
|
|
80
|
+
mockReturn("Unauthorized", 401, {});
|
|
81
|
+
// use optional parameter to ask for rejection of the promise if not authenticated
|
|
82
|
+
manager.isAuthenticated(true).done((authenticated) => {
|
|
83
|
+
assert.fail("isAuthenticated should have rejected the promise");
|
|
84
|
+
done();
|
|
85
|
+
}, (err) => {
|
|
86
|
+
assert.equal(err.message, "Unauthorized", "Error message should be 'Unauthorized'");
|
|
87
|
+
done();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
it("isAuthenticated handles unexpected status codes", (done) => {
|
|
91
|
+
mockReturn("Not Found", 404, {});
|
|
92
|
+
manager.isAuthenticated().done((authenticated) => {
|
|
93
|
+
assert.fail("isAuthenticated should have rejected the promise");
|
|
94
|
+
done();
|
|
95
|
+
}, (err) => {
|
|
96
|
+
assert.equal(err.message, "Not Found", "Error message should be 'Not Found'");
|
|
97
|
+
done();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
it("addApp handles successful response", (done) => {
|
|
101
|
+
mockReturn(JSON.stringify({ success: true }), 201, {
|
|
102
|
+
location: "/appName",
|
|
103
|
+
});
|
|
104
|
+
manager.addApp("appName").done((obj) => {
|
|
105
|
+
assert.ok(obj);
|
|
106
|
+
done();
|
|
107
|
+
}, rejectHandler);
|
|
108
|
+
});
|
|
109
|
+
it("addApp handles error response", (done) => {
|
|
110
|
+
mockReturn(JSON.stringify({ success: false }), 404, {});
|
|
111
|
+
manager.addApp("appName").done((obj) => {
|
|
112
|
+
throw new Error("Call should not complete successfully");
|
|
113
|
+
}, (error) => done());
|
|
114
|
+
});
|
|
115
|
+
it("getApp handles JSON response", (done) => {
|
|
116
|
+
mockReturn(JSON.stringify({ app: {} }), 200, {});
|
|
117
|
+
manager.getApp("appName").done((obj) => {
|
|
118
|
+
assert.ok(obj);
|
|
119
|
+
done();
|
|
120
|
+
}, rejectHandler);
|
|
121
|
+
});
|
|
122
|
+
it("updateApp handles success response", (done) => {
|
|
123
|
+
mockReturn(JSON.stringify({ apps: [] }), 200, {});
|
|
124
|
+
manager.renameApp("appName", "newAppName").done((obj) => {
|
|
125
|
+
assert.ok(!obj);
|
|
126
|
+
done();
|
|
127
|
+
}, rejectHandler);
|
|
128
|
+
});
|
|
129
|
+
it("removeApp handles success response", (done) => {
|
|
130
|
+
mockReturn("", 200, {});
|
|
131
|
+
manager.removeApp("appName").done((obj) => {
|
|
132
|
+
assert.ok(!obj);
|
|
133
|
+
done();
|
|
134
|
+
}, rejectHandler);
|
|
135
|
+
});
|
|
136
|
+
it("transferApp handles successful response", (done) => {
|
|
137
|
+
mockReturn("", 201);
|
|
138
|
+
manager.transferApp("appName", "email1").done((obj) => {
|
|
139
|
+
assert.ok(!obj);
|
|
140
|
+
done();
|
|
141
|
+
}, rejectHandler);
|
|
142
|
+
});
|
|
143
|
+
it("addDeployment handles success response", (done) => {
|
|
144
|
+
mockReturn(JSON.stringify({ deployment: { name: "name", key: "key" } }), 201, { location: "/deploymentName" });
|
|
145
|
+
manager.addDeployment("appName", "deploymentName").done((obj) => {
|
|
146
|
+
assert.ok(obj);
|
|
147
|
+
done();
|
|
148
|
+
}, rejectHandler);
|
|
149
|
+
});
|
|
150
|
+
it("getDeployment handles JSON response", (done) => {
|
|
151
|
+
mockReturn(JSON.stringify({ deployment: {} }), 200, {});
|
|
152
|
+
manager.getDeployment("appName", "deploymentName").done((obj) => {
|
|
153
|
+
assert.ok(obj);
|
|
154
|
+
done();
|
|
155
|
+
}, rejectHandler);
|
|
156
|
+
});
|
|
157
|
+
it("getDeployments handles JSON response", (done) => {
|
|
158
|
+
mockReturn(JSON.stringify({ deployments: [] }), 200, {});
|
|
159
|
+
manager.getDeployments("appName").done((obj) => {
|
|
160
|
+
assert.ok(obj);
|
|
161
|
+
done();
|
|
162
|
+
}, rejectHandler);
|
|
163
|
+
});
|
|
164
|
+
it("renameDeployment handles success response", (done) => {
|
|
165
|
+
mockReturn(JSON.stringify({ apps: [] }), 200, {});
|
|
166
|
+
manager.renameDeployment("appName", "deploymentName", "newDeploymentName").done((obj) => {
|
|
167
|
+
assert.ok(!obj);
|
|
168
|
+
done();
|
|
169
|
+
}, rejectHandler);
|
|
170
|
+
});
|
|
171
|
+
it("removeDeployment handles success response", (done) => {
|
|
172
|
+
mockReturn("", 200, {});
|
|
173
|
+
manager.removeDeployment("appName", "deploymentName").done((obj) => {
|
|
174
|
+
assert.ok(!obj);
|
|
175
|
+
done();
|
|
176
|
+
}, rejectHandler);
|
|
177
|
+
});
|
|
178
|
+
it("getDeploymentHistory handles success response with no packages", (done) => {
|
|
179
|
+
mockReturn(JSON.stringify({ history: [] }), 200);
|
|
180
|
+
manager.getDeploymentHistory("appName", "deploymentName").done((obj) => {
|
|
181
|
+
assert.ok(obj);
|
|
182
|
+
assert.equal(obj.length, 0);
|
|
183
|
+
done();
|
|
184
|
+
}, rejectHandler);
|
|
185
|
+
});
|
|
186
|
+
it("getDeploymentHistory handles success response with two packages", (done) => {
|
|
187
|
+
mockReturn(JSON.stringify({ history: [{ label: "v1" }, { label: "v2" }] }), 200);
|
|
188
|
+
manager.getDeploymentHistory("appName", "deploymentName").done((obj) => {
|
|
189
|
+
assert.ok(obj);
|
|
190
|
+
assert.equal(obj.length, 2);
|
|
191
|
+
assert.equal(obj[0].label, "v1");
|
|
192
|
+
assert.equal(obj[1].label, "v2");
|
|
193
|
+
done();
|
|
194
|
+
}, rejectHandler);
|
|
195
|
+
});
|
|
196
|
+
it("getDeploymentHistory handles error response", (done) => {
|
|
197
|
+
mockReturn("", 404);
|
|
198
|
+
manager.getDeploymentHistory("appName", "deploymentName").done((obj) => {
|
|
199
|
+
throw new Error("Call should not complete successfully");
|
|
200
|
+
}, (error) => done());
|
|
201
|
+
});
|
|
202
|
+
it("clearDeploymentHistory handles success response", (done) => {
|
|
203
|
+
mockReturn("", 204);
|
|
204
|
+
manager.clearDeploymentHistory("appName", "deploymentName").done((obj) => {
|
|
205
|
+
assert.ok(!obj);
|
|
206
|
+
done();
|
|
207
|
+
}, rejectHandler);
|
|
208
|
+
});
|
|
209
|
+
it("clearDeploymentHistory handles error response", (done) => {
|
|
210
|
+
mockReturn("", 404);
|
|
211
|
+
manager.clearDeploymentHistory("appName", "deploymentName").done((obj) => {
|
|
212
|
+
throw new Error("Call should not complete successfully");
|
|
213
|
+
}, (error) => done());
|
|
214
|
+
});
|
|
215
|
+
it("addCollaborator handles successful response", (done) => {
|
|
216
|
+
mockReturn("", 201, { location: "/collaborators" });
|
|
217
|
+
manager.addCollaborator("appName", "email1").done((obj) => {
|
|
218
|
+
assert.ok(!obj);
|
|
219
|
+
done();
|
|
220
|
+
}, rejectHandler);
|
|
221
|
+
});
|
|
222
|
+
it("addCollaborator handles error response", (done) => {
|
|
223
|
+
mockReturn("", 404, {});
|
|
224
|
+
manager.addCollaborator("appName", "email1").done((obj) => {
|
|
225
|
+
throw new Error("Call should not complete successfully");
|
|
226
|
+
}, (error) => done());
|
|
227
|
+
});
|
|
228
|
+
it("getCollaborators handles success response with no collaborators", (done) => {
|
|
229
|
+
mockReturn(JSON.stringify({ collaborators: {} }), 200);
|
|
230
|
+
manager.getCollaborators("appName").done((obj) => {
|
|
231
|
+
assert.ok(obj);
|
|
232
|
+
assert.equal(Object.keys(obj).length, 0);
|
|
233
|
+
done();
|
|
234
|
+
}, rejectHandler);
|
|
235
|
+
});
|
|
236
|
+
it("getCollaborators handles success response with multiple collaborators", (done) => {
|
|
237
|
+
mockReturn(JSON.stringify({
|
|
238
|
+
collaborators: {
|
|
239
|
+
email1: { permission: "Owner", isCurrentAccount: true },
|
|
240
|
+
email2: { permission: "Collaborator", isCurrentAccount: false },
|
|
241
|
+
},
|
|
242
|
+
}), 200);
|
|
243
|
+
manager.getCollaborators("appName").done((obj) => {
|
|
244
|
+
assert.ok(obj);
|
|
245
|
+
assert.equal(obj["email1"].permission, "Owner");
|
|
246
|
+
assert.equal(obj["email2"].permission, "Collaborator");
|
|
247
|
+
done();
|
|
248
|
+
}, rejectHandler);
|
|
249
|
+
});
|
|
250
|
+
it("removeCollaborator handles success response", (done) => {
|
|
251
|
+
mockReturn("", 200, {});
|
|
252
|
+
manager.removeCollaborator("appName", "email1").done((obj) => {
|
|
253
|
+
assert.ok(!obj);
|
|
254
|
+
done();
|
|
255
|
+
}, rejectHandler);
|
|
256
|
+
});
|
|
257
|
+
it("patchRelease handles success response", (done) => {
|
|
258
|
+
mockReturn(JSON.stringify({ package: { description: "newDescription" } }), 200);
|
|
259
|
+
manager
|
|
260
|
+
.patchRelease("appName", "deploymentName", "label", {
|
|
261
|
+
description: "newDescription",
|
|
262
|
+
})
|
|
263
|
+
.done((obj) => {
|
|
264
|
+
assert.ok(!obj);
|
|
265
|
+
done();
|
|
266
|
+
}, rejectHandler);
|
|
267
|
+
});
|
|
268
|
+
it("patchRelease handles error response", (done) => {
|
|
269
|
+
mockReturn("", 400);
|
|
270
|
+
manager.patchRelease("appName", "deploymentName", "label", {}).done((obj) => {
|
|
271
|
+
throw new Error("Call should not complete successfully");
|
|
272
|
+
}, (error) => done());
|
|
273
|
+
});
|
|
274
|
+
it("promote handles success response", (done) => {
|
|
275
|
+
mockReturn(JSON.stringify({ package: { description: "newDescription" } }), 200);
|
|
276
|
+
manager
|
|
277
|
+
.promote("appName", "deploymentName", "newDeploymentName", {
|
|
278
|
+
description: "newDescription",
|
|
279
|
+
})
|
|
280
|
+
.done((obj) => {
|
|
281
|
+
assert.ok(!obj);
|
|
282
|
+
done();
|
|
283
|
+
}, rejectHandler);
|
|
284
|
+
});
|
|
285
|
+
it("promote handles error response", (done) => {
|
|
286
|
+
mockReturn("", 400);
|
|
287
|
+
manager
|
|
288
|
+
.promote("appName", "deploymentName", "newDeploymentName", {
|
|
289
|
+
rollout: 123,
|
|
290
|
+
})
|
|
291
|
+
.done((obj) => {
|
|
292
|
+
throw new Error("Call should not complete successfully");
|
|
293
|
+
}, (error) => done());
|
|
294
|
+
});
|
|
295
|
+
it("rollback handles success response", (done) => {
|
|
296
|
+
mockReturn(JSON.stringify({ package: { label: "v1" } }), 200);
|
|
297
|
+
manager.rollback("appName", "deploymentName", "v1").done((obj) => {
|
|
298
|
+
assert.ok(!obj);
|
|
299
|
+
done();
|
|
300
|
+
}, rejectHandler);
|
|
301
|
+
});
|
|
302
|
+
it("rollback handles error response", (done) => {
|
|
303
|
+
mockReturn("", 400);
|
|
304
|
+
manager.rollback("appName", "deploymentName", "v1").done((obj) => {
|
|
305
|
+
throw new Error("Call should not complete successfully");
|
|
306
|
+
}, (error) => done());
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
// Helper method that is used everywhere that an assert.fail() is needed in a promise handler
|
|
310
|
+
function rejectHandler(val) {
|
|
311
|
+
assert.fail();
|
|
312
|
+
}
|
|
313
|
+
// Wrapper for superagent-mock that abstracts away information not needed for SDK tests
|
|
314
|
+
function mockReturn(bodyText, statusCode, header = {}) {
|
|
315
|
+
require("superagent-mock")(request, [
|
|
316
|
+
{
|
|
317
|
+
pattern: "http://localhost/(\\w+)/?",
|
|
318
|
+
fixtures: function (match, params) {
|
|
319
|
+
var isOk = statusCode >= 200 && statusCode < 300;
|
|
320
|
+
if (!isOk) {
|
|
321
|
+
var err = new Error(bodyText);
|
|
322
|
+
err.status = statusCode;
|
|
323
|
+
throw err;
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
text: bodyText,
|
|
327
|
+
status: statusCode,
|
|
328
|
+
ok: isOk,
|
|
329
|
+
header: header,
|
|
330
|
+
headers: {},
|
|
331
|
+
};
|
|
332
|
+
},
|
|
333
|
+
callback: function (match, data) {
|
|
334
|
+
return data;
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
]);
|
|
338
|
+
}
|
package/bsdiff/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# ENDSLEY/BSDIFF43 Utility
|
|
2
|
+
|
|
3
|
+
A cross-platform implementation of the ENDSLEY/BSDIFF43 binary diff and patch algorithm, optimized for CodePush deployments. While the standard `bsdiff` available through package managers supports BSDIFF40 format, this tool implements the enhanced ENDSLEY/BSDIFF43 format with additional optimizations for mobile app updates.
|
|
4
|
+
|
|
5
|
+
## Why ENDSLEY/BSDIFF43?
|
|
6
|
+
|
|
7
|
+
- **Optimized for Mobile**: Specifically tuned for React Native and Cordova app updates
|
|
8
|
+
- **Streaming-First**: No seeking operations during patch application, perfect for mobile downloads
|
|
9
|
+
- **Minimal Resource Usage**: Efficient streaming with minimal disk I/O and memory footprint
|
|
10
|
+
- **Cross-Platform**: Works reliably on iOS, Android, and Windows platforms
|
|
11
|
+
- **Integration-Ready**: Designed for easy integration with build and deployment systems
|
|
12
|
+
- **Smaller Updates**: Generates optimized patch sizes for faster downloads
|
|
13
|
+
- **Compression Options**: Flexible compression support (raw, bzip2, Brotli) for different scenarios
|
|
14
|
+
|
|
15
|
+
## Credits
|
|
16
|
+
|
|
17
|
+
Built using the bsdiff/bspatch library:
|
|
18
|
+
- Original bsdiff algorithm by Colin Percival (2003-2005)
|
|
19
|
+
- Enhanced ENDSLEY/BSDIFF43 version by Matthew Endsley (2012)
|
|
20
|
+
- Source: https://github.com/mendsley/bsdiff
|
|
21
|
+
|
|
22
|
+
## Prerequisites
|
|
23
|
+
|
|
24
|
+
### Dependencies
|
|
25
|
+
- bzip2 library (for compression support)
|
|
26
|
+
- **macOS**: Pre-installed
|
|
27
|
+
- **Ubuntu/Debian**: `sudo apt-get install libbz2-dev`
|
|
28
|
+
- **CentOS/RHEL**: `sudo yum install bzip2-devel`
|
|
29
|
+
- **Windows**: Available through WSL package manager
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
### Building from Source
|
|
34
|
+
To use bsdiff/bspatch directly without code-push-itspar, follow these steps:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd cli/bsdiff
|
|
38
|
+
make clean
|
|
39
|
+
make
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Creating a Patch
|
|
43
|
+
```bash
|
|
44
|
+
./bsdiff43 diff <old_file> <new_file> <patch_file> <compression>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Parameters:
|
|
48
|
+
- `old_file`: Path to the original bundle file
|
|
49
|
+
- `new_file`: Path to the new bundle file
|
|
50
|
+
- `patch_file`: Path where the patch file will be saved
|
|
51
|
+
- `compression`: Boolean flag (true/false)
|
|
52
|
+
- `false` (default): Creates a raw patch that can be further compressed using Brotli
|
|
53
|
+
- `true`: Uses built-in bzip2 compression (Note: cannot be further compressed)
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
```bash
|
|
57
|
+
./bsdiff43 diff old.bundle new.bundle patch.diff false
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Applying a Patch
|
|
61
|
+
```bash
|
|
62
|
+
./bsdiff43 patch <old_file> <patch_file> <output_file> <is_patch_compressed>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Parameters:
|
|
66
|
+
- `old_file`: Path to the original bundle file
|
|
67
|
+
- `patch_file`: Path to the patch file
|
|
68
|
+
- `output_file`: Path where the reconstructed new file will be saved
|
|
69
|
+
- `is_patch_compressed`: Boolean flag (true/false)
|
|
70
|
+
- Must match the compression setting used when creating the patch
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
```bash
|
|
74
|
+
./bsdiff43 patch old.bundle patch.diff reconstructed.bundle false
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Compression Notes
|
|
78
|
+
- When `compression=false`, the patch is created in raw format
|
|
79
|
+
- This is recommended when using with code-push-itspar as it allows for Brotli compression later
|
|
80
|
+
- Results in better compression ratios in most cases
|
|
81
|
+
- When `compression=true`, the patch is compressed using bzip2
|
|
82
|
+
- Cannot be further compressed using other algorithms
|
|
83
|
+
- Useful for standalone usage without code-push-itspar
|
|
84
|
+
|
|
85
|
+
## File Structure
|
|
86
|
+
|
|
87
|
+
- `bsdiff/` - Contains the binary diff/patch utility
|
|
88
|
+
- `bsdiff43` - The compiled executable
|
|
89
|
+
- `bsdiff.c`, `bsdiff.h` - Source for diff functionality
|
|
90
|
+
- `bspatch.c`, `bspatch.h` - Source for patch functionality
|
|
91
|
+
- `main.c` - Main program entry point
|
|
92
|
+
- `Makefile` - Build configuration
|
|
93
|
+
- `LICENSE` - BSD 2-clause license
|
|
94
|
+
|
|
95
|
+
### Error Handling
|
|
96
|
+
|
|
97
|
+
Both functions return:
|
|
98
|
+
- `0` on success
|
|
99
|
+
- Non-zero value on error
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
Licensed under BSD 2-clause:
|
|
104
|
+
```
|
|
105
|
+
Copyright 2003-2005 Colin Percival
|
|
106
|
+
Copyright 2012 Matthew Endsley
|
|
107
|
+
All rights reserved
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Requirements:
|
|
111
|
+
1. Keep the copyright notice and license text in source files
|
|
112
|
+
2. Include the same copyright notice and license in binary distributions
|
|
113
|
+
|
|
114
|
+
See `bsdiff/LICENSE` for complete license text.
|
package/bsdiff/bsdiff43
ADDED
|
Binary file
|