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.
@@ -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
+ }
@@ -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.
Binary file