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,531 @@
1
+ "use strict";
2
+ // Copyright (c) Microsoft Corporation.
3
+ // Licensed under the MIT License.
4
+ const fs = require("fs");
5
+ const os = require("os");
6
+ const path = require("path");
7
+ const Q = require("q");
8
+ const superagent = require("superagent");
9
+ const recursiveFs = require("recursive-fs");
10
+ const yazl = require("yazl");
11
+ const slash = require("slash");
12
+ const zlib = require("zlib");
13
+ const ORG_FILE_PATH = path.resolve(__dirname, 'organisations.json');
14
+ var Promise = Q.Promise;
15
+ const config_constants_1 = require("./utils/config.constants");
16
+ const packageJson = require("../../package.json");
17
+ // A template string tag function that URL encodes the substituted values
18
+ function urlEncode(strings, ...values) {
19
+ let result = "";
20
+ for (let i = 0; i < strings.length; i++) {
21
+ result += strings[i];
22
+ if (i < values.length) {
23
+ result += encodeURIComponent(values[i]);
24
+ }
25
+ }
26
+ return result;
27
+ }
28
+ function saveOrganizationsSync(orgs, forceSave = false) {
29
+ try {
30
+ // Check if file exists and is non-empty
31
+ const fileExists = fs.existsSync(ORG_FILE_PATH);
32
+ const isFileEmpty = fileExists && fs.readFileSync(ORG_FILE_PATH, 'utf-8').trim() === '';
33
+ if (forceSave || !fileExists || isFileEmpty) {
34
+ fs.writeFileSync(ORG_FILE_PATH, JSON.stringify(orgs, null, 2), 'utf-8');
35
+ //console.log(`Organizations saved to ${ORG_FILE_PATH}`);
36
+ }
37
+ else {
38
+ //console.log("Organizations already exist, skipping save.");
39
+ }
40
+ }
41
+ catch (error) {
42
+ console.error(`Error saving organizations: ${error.message}`);
43
+ }
44
+ }
45
+ // Load organizations from the file (synchronous)
46
+ function loadOrganizationsSync() {
47
+ try {
48
+ if (fs.existsSync(ORG_FILE_PATH)) {
49
+ const data = fs.readFileSync(ORG_FILE_PATH, 'utf-8');
50
+ // console.log("data ::", data);
51
+ return JSON.parse(data);
52
+ }
53
+ return [];
54
+ }
55
+ catch (error) {
56
+ console.error(`Error loading organizations: ${error.message}`);
57
+ return [];
58
+ }
59
+ }
60
+ class AccountManager {
61
+ static AppPermission = {
62
+ OWNER: "Owner",
63
+ COLLABORATOR: "Collaborator",
64
+ };
65
+ static SERVER_URL = "http://localhost:3000";
66
+ static API_VERSION = 2;
67
+ static ERROR_GATEWAY_TIMEOUT = 504; // Used if there is a network error
68
+ static ERROR_INTERNAL_SERVER = 500;
69
+ static ERROR_NOT_FOUND = 404;
70
+ static ERROR_CONFLICT = 409; // Used if the resource already exists
71
+ static ERROR_UNAUTHORIZED = 401;
72
+ organisations = [];
73
+ organisationsFetched = false;
74
+ _accessKey;
75
+ _serverUrl;
76
+ _customHeaders;
77
+ passedOrgName;
78
+ constructor(accessKey, customHeaders, serverUrl) {
79
+ if (!accessKey)
80
+ throw new Error("An access key must be specified.");
81
+ this._accessKey = accessKey;
82
+ this._customHeaders = customHeaders;
83
+ this._serverUrl = serverUrl || AccountManager.SERVER_URL;
84
+ this.organisations = loadOrganizationsSync();
85
+ }
86
+ get accessKey() {
87
+ return this._accessKey;
88
+ }
89
+ isAuthenticated(throwIfUnauthorized) {
90
+ return Promise((resolve, reject, _notify) => {
91
+ const request = superagent.get(`${this._serverUrl}${urlEncode(["/authenticated"])}`);
92
+ this.attachCredentials(request);
93
+ request.end((err, res) => {
94
+ const status = this.getErrorStatus(err, res);
95
+ if (err && status !== AccountManager.ERROR_UNAUTHORIZED) {
96
+ reject(this.getCodePushError(err, res));
97
+ return;
98
+ }
99
+ const authenticated = status === 200;
100
+ if (!authenticated && throwIfUnauthorized) {
101
+ reject(this.getCodePushError(err, res));
102
+ return;
103
+ }
104
+ resolve(authenticated);
105
+ });
106
+ });
107
+ }
108
+ //Tenants
109
+ getTenants() {
110
+ return this.get(urlEncode(["/tenants"])).then((res) => {
111
+ this.organisations = res.body.organisations;
112
+ saveOrganizationsSync(res.body.organisations, true);
113
+ return res.body.organisations;
114
+ });
115
+ }
116
+ getOrganisations() {
117
+ return this.organisations;
118
+ }
119
+ getTenantId(tenantName) {
120
+ if (!this.organisations || this.organisations.length === 0) {
121
+ return "";
122
+ }
123
+ let tenantId = "";
124
+ this.organisations.forEach((org) => {
125
+ if (org.displayName === tenantName) {
126
+ tenantId = org.id;
127
+ }
128
+ });
129
+ return tenantId;
130
+ }
131
+ addAccessKey(friendlyName, ttl) {
132
+ if (!friendlyName) {
133
+ throw new Error("A name must be specified when adding an access key.");
134
+ }
135
+ const accessKeyRequest = {
136
+ createdBy: os.hostname(),
137
+ friendlyName,
138
+ ttl,
139
+ };
140
+ return this.post(urlEncode(["/accessKeys/"]), JSON.stringify(accessKeyRequest), /*expectResponseBody=*/ true).then((response) => {
141
+ return {
142
+ createdTime: response.body.accessKey.createdTime,
143
+ expires: response.body.accessKey.expires,
144
+ key: response.body.accessKey.name,
145
+ name: response.body.accessKey.friendlyName,
146
+ };
147
+ });
148
+ }
149
+ getAccessKey(accessKeyName) {
150
+ return this.get(urlEncode([`/accessKeys/${accessKeyName}`])).then((res) => {
151
+ return {
152
+ createdTime: res.body.accessKey.createdTime,
153
+ expires: res.body.accessKey.expires,
154
+ name: res.body.accessKey.friendlyName,
155
+ };
156
+ });
157
+ }
158
+ getAccessKeys() {
159
+ return this.get(urlEncode(["/accessKeys"])).then((res) => {
160
+ const accessKeys = [];
161
+ res.body.accessKeys.forEach((serverAccessKey) => {
162
+ !serverAccessKey.isSession &&
163
+ accessKeys.push({
164
+ createdTime: serverAccessKey.createdTime,
165
+ expires: serverAccessKey.expires,
166
+ name: serverAccessKey.friendlyName,
167
+ });
168
+ });
169
+ return accessKeys;
170
+ });
171
+ }
172
+ getSessions() {
173
+ return this.get(urlEncode(["/accessKeys"])).then((res) => {
174
+ // A machine name might be associated with multiple session keys,
175
+ // but we should only return one per machine name.
176
+ const sessionMap = {};
177
+ const now = new Date().getTime();
178
+ res.body.accessKeys.forEach((serverAccessKey) => {
179
+ if (serverAccessKey.isSession && serverAccessKey.expires > now) {
180
+ sessionMap[serverAccessKey.createdBy] = {
181
+ loggedInTime: serverAccessKey.createdTime,
182
+ machineName: serverAccessKey.createdBy,
183
+ };
184
+ }
185
+ });
186
+ const sessions = Object.keys(sessionMap).map((machineName) => sessionMap[machineName]);
187
+ return sessions;
188
+ });
189
+ }
190
+ patchAccessKey(oldName, newName, ttl) {
191
+ const accessKeyRequest = {
192
+ friendlyName: newName,
193
+ ttl,
194
+ };
195
+ return this.patch(urlEncode([`/accessKeys/${oldName}`]), JSON.stringify(accessKeyRequest)).then((res) => {
196
+ return {
197
+ createdTime: res.body.accessKey.createdTime,
198
+ expires: res.body.accessKey.expires,
199
+ name: res.body.accessKey.friendlyName,
200
+ };
201
+ });
202
+ }
203
+ removeAccessKey(name) {
204
+ return this.del(urlEncode([`/accessKeys/${name}`])).then(() => null);
205
+ }
206
+ removeSession(machineName) {
207
+ return this.del(urlEncode([`/sessions/${machineName}`])).then(() => null);
208
+ }
209
+ // Account
210
+ getAccountInfo() {
211
+ return this.get(urlEncode(["/account"])).then((res) => res.body.account);
212
+ }
213
+ // Apps
214
+ getApps() {
215
+ //add tenant here
216
+ return this.get(urlEncode(["/apps"])).then((res) => res.body.apps);
217
+ }
218
+ getApp(appName) {
219
+ //add tenant here
220
+ return this.get(urlEncode([`/apps/${appName}`])).then((res) => res.body.app);
221
+ }
222
+ addApp(appName) {
223
+ //add tenant here
224
+ const app = { name: appName };
225
+ const tenantId = this.getTenantId(this.passedOrgName);
226
+ if (tenantId && tenantId.length > 0) {
227
+ app.organisation = {};
228
+ app.organisation.orgId = tenantId;
229
+ }
230
+ else if (this.passedOrgName && this.passedOrgName.length > 0) {
231
+ app.organisation = {};
232
+ app.organisation.orgName = this.passedOrgName;
233
+ }
234
+ return this.post(urlEncode(["/apps/"]), JSON.stringify(app), /*expectResponseBody=*/ false).then(() => app);
235
+ }
236
+ removeApp(appName) {
237
+ //add tenant here
238
+ return this.del(urlEncode([`/apps/${appName}`])).then(() => null);
239
+ }
240
+ renameApp(oldAppName, newAppName) {
241
+ //add tenant here
242
+ return this.patch(urlEncode([`/apps/${oldAppName}`]), JSON.stringify({ name: newAppName })).then(() => null);
243
+ }
244
+ transferApp(appName, email) {
245
+ return this.post(urlEncode([`/apps/${appName}/transfer/${email}`]), /*requestBody=*/ null, /*expectResponseBody=*/ false).then(() => null);
246
+ }
247
+ // Collaborators
248
+ getCollaborators(appName) {
249
+ return this.get(urlEncode([`/apps/${appName}/collaborators`])).then((res) => res.body.collaborators);
250
+ }
251
+ addCollaborator(appName, email) {
252
+ return this.post(urlEncode([`/apps/${appName}/collaborators/${email}`]),
253
+ /*requestBody=*/ null,
254
+ /*expectResponseBody=*/ false).then(() => null);
255
+ }
256
+ removeCollaborator(appName, email) {
257
+ return this.del(urlEncode([`/apps/${appName}/collaborators/${email}`])).then(() => null);
258
+ }
259
+ // Deployments
260
+ addDeployment(appName, deploymentName, deploymentKey) {
261
+ const deployment = { name: deploymentName, key: deploymentKey };
262
+ return this.post(urlEncode([`/apps/${appName}/deployments/`]), JSON.stringify(deployment), /*expectResponseBody=*/ true).then((res) => res.body.deployment);
263
+ }
264
+ clearDeploymentHistory(appName, deploymentName) {
265
+ return this.del(urlEncode([`/apps/${appName}/deployments/${deploymentName}/history`])).then(() => null);
266
+ }
267
+ getDeployments(appName) {
268
+ return this.get(urlEncode([`/apps/${appName}/deployments/`])).then((res) => res.body.deployments);
269
+ }
270
+ getDeployment(appName, deploymentName) {
271
+ return this.get(urlEncode([`/apps/${appName}/deployments/${deploymentName}`])).then((res) => res.body.deployment);
272
+ }
273
+ renameDeployment(appName, oldDeploymentName, newDeploymentName) {
274
+ return this.patch(urlEncode([`/apps/${appName}/deployments/${oldDeploymentName}`]), JSON.stringify({ name: newDeploymentName })).then(() => null);
275
+ }
276
+ removeDeployment(appName, deploymentName) {
277
+ return this.del(urlEncode([`/apps/${appName}/deployments/${deploymentName}`])).then(() => null);
278
+ }
279
+ getDeploymentMetrics(appName, deploymentName) {
280
+ return this.get(urlEncode([`/apps/${appName}/deployments/${deploymentName}/metrics`])).then((res) => res.body.metrics);
281
+ }
282
+ getDeploymentHistory(appName, deploymentName) {
283
+ return this.get(urlEncode([`/apps/${appName}/deployments/${deploymentName}/history`])).then((res) => res.body.history);
284
+ }
285
+ release(appName, deploymentName, filePath, targetBinaryVersion, updateMetadata, uploadProgressCallback, compression = config_constants_1.CompressionType.DEFLATE) {
286
+ return Promise((resolve, reject) => {
287
+ updateMetadata.appVersion = targetBinaryVersion;
288
+ const request = superagent.post(this._serverUrl + urlEncode([`/apps/${appName}/deployments/${deploymentName}/release`]));
289
+ this.attachCredentials(request);
290
+ const getPackageFilePromise = Q.Promise((resolve, reject) => {
291
+ this.packageFileFromPath(filePath, compression)
292
+ .then((result) => {
293
+ resolve(result);
294
+ })
295
+ .catch((error) => {
296
+ reject(error);
297
+ });
298
+ });
299
+ getPackageFilePromise.then((packageFile) => {
300
+ const file = fs.createReadStream(packageFile.path);
301
+ console.log('\nUploading Zip File of size ::', fs.statSync(packageFile.path).size);
302
+ request
303
+ .attach("package", file)
304
+ .field("packageInfo", JSON.stringify(updateMetadata))
305
+ .on("progress", (event) => {
306
+ if (uploadProgressCallback && event && event.total > 0) {
307
+ const currentProgress = (event.loaded / event.total) * 100;
308
+ uploadProgressCallback(currentProgress);
309
+ }
310
+ })
311
+ .end((err, res) => {
312
+ if (packageFile.isTemporary) {
313
+ fs.unlinkSync(packageFile.path);
314
+ }
315
+ if (err) {
316
+ reject(this.getCodePushError(err, res));
317
+ return;
318
+ }
319
+ if (res.ok) {
320
+ resolve(null);
321
+ }
322
+ else {
323
+ let body;
324
+ try {
325
+ body = JSON.parse(res.text);
326
+ }
327
+ catch (err) { }
328
+ if (body) {
329
+ reject({
330
+ message: body.message,
331
+ statusCode: res && res.status,
332
+ });
333
+ }
334
+ else {
335
+ reject({
336
+ message: res.text,
337
+ statusCode: res && res.status,
338
+ });
339
+ }
340
+ }
341
+ });
342
+ });
343
+ });
344
+ }
345
+ patchRelease(appName, deploymentName, label, updateMetadata) {
346
+ updateMetadata.label = label;
347
+ const requestBody = JSON.stringify({ packageInfo: updateMetadata });
348
+ return this.patch(urlEncode([`/apps/${appName}/deployments/${deploymentName}/release`]), requestBody,
349
+ /*expectResponseBody=*/ false).then(() => null);
350
+ }
351
+ promote(appName, sourceDeploymentName, destinationDeploymentName, updateMetadata) {
352
+ const requestBody = JSON.stringify({ packageInfo: updateMetadata });
353
+ return this.post(urlEncode([`/apps/${appName}/deployments/${sourceDeploymentName}/promote/${destinationDeploymentName}`]), requestBody,
354
+ /*expectResponseBody=*/ false).then(() => null);
355
+ }
356
+ rollback(appName, deploymentName, targetRelease) {
357
+ return this.post(urlEncode([`/apps/${appName}/deployments/${deploymentName}/rollback/${targetRelease || ``}`]),
358
+ /*requestBody=*/ null,
359
+ /*expectResponseBody=*/ false).then(() => null);
360
+ }
361
+ packageFileFromPath(filePath, compression) {
362
+ let getPackageFilePromise;
363
+ if (fs.lstatSync(filePath).isDirectory()) {
364
+ getPackageFilePromise = Promise((resolve, reject) => {
365
+ const directoryPath = filePath;
366
+ recursiveFs.readdirr(directoryPath, (error, directories, files) => {
367
+ if (error) {
368
+ reject(error);
369
+ return;
370
+ }
371
+ const baseDirectoryPath = path.dirname(directoryPath);
372
+ const fileName = this.generateRandomFilename(15) + ".zip";
373
+ const zipFile = new yazl.ZipFile();
374
+ const writeStream = fs.createWriteStream(fileName);
375
+ zipFile.outputStream
376
+ .pipe(writeStream)
377
+ .on("error", (error) => {
378
+ reject(error);
379
+ })
380
+ .on("close", () => {
381
+ filePath = path.join(process.cwd(), fileName);
382
+ resolve({ isTemporary: true, path: filePath });
383
+ });
384
+ try {
385
+ if (compression === config_constants_1.CompressionType.BROTLI) {
386
+ console.log(`\nCompressing ${files.length} files...`);
387
+ // For Brotli, compress each file individually
388
+ for (let i = 0; i < files.length; ++i) {
389
+ const file = files[i];
390
+ const relativePath = slash(path.relative(baseDirectoryPath, file));
391
+ const fileContent = fs.readFileSync(file);
392
+ // Create Brotli compressed content
393
+ const brotliStream = zlib.createBrotliCompress({
394
+ params: {
395
+ [zlib.constants.BROTLI_PARAM_QUALITY]: 11 // Maximum compression
396
+ }
397
+ });
398
+ // Add compressed content to zip
399
+ zipFile.addReadStream(brotliStream, `${relativePath}.br`);
400
+ // Write content to stream
401
+ brotliStream.end(fileContent);
402
+ }
403
+ }
404
+ else {
405
+ for (let i = 0; i < files.length; ++i) {
406
+ const file = files[i];
407
+ // yazl does not like backslash (\) in the metadata path.
408
+ const relativePath = slash(path.relative(baseDirectoryPath, file));
409
+ zipFile.addFile(file, relativePath);
410
+ }
411
+ }
412
+ }
413
+ catch (err) {
414
+ reject(err);
415
+ }
416
+ zipFile.end();
417
+ });
418
+ });
419
+ }
420
+ else {
421
+ console.log('Provided file path is a file. Ignoring compression.');
422
+ getPackageFilePromise = Q({ isTemporary: false, path: filePath });
423
+ }
424
+ return getPackageFilePromise;
425
+ }
426
+ generateRandomFilename(length) {
427
+ let filename = "";
428
+ const validChar = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
429
+ for (let i = 0; i < length; i++) {
430
+ filename += validChar.charAt(Math.floor(Math.random() * validChar.length));
431
+ }
432
+ return filename;
433
+ }
434
+ get(endpoint, expectResponseBody = true) {
435
+ return this.makeApiRequest("get", endpoint, /*requestBody=*/ null, expectResponseBody, /*contentType=*/ null);
436
+ }
437
+ post(endpoint, requestBody, expectResponseBody, contentType = "application/json;charset=UTF-8") {
438
+ return this.makeApiRequest("post", endpoint, requestBody, expectResponseBody, contentType);
439
+ }
440
+ patch(endpoint, requestBody, expectResponseBody = false, contentType = "application/json;charset=UTF-8") {
441
+ return this.makeApiRequest("patch", endpoint, requestBody, expectResponseBody, contentType);
442
+ }
443
+ del(endpoint, expectResponseBody = false) {
444
+ return this.makeApiRequest("del", endpoint, /*requestBody=*/ null, expectResponseBody, /*contentType=*/ null);
445
+ }
446
+ makeApiRequest(method, endpoint, requestBody, expectResponseBody, contentType) {
447
+ return Promise((resolve, reject, _notify) => {
448
+ let request = superagent[method](this._serverUrl + endpoint);
449
+ this.attachCredentials(request);
450
+ if (requestBody) {
451
+ if (contentType) {
452
+ request = request.set("Content-Type", contentType);
453
+ }
454
+ request = request.send(requestBody);
455
+ }
456
+ request.end((err, res) => {
457
+ if (err) {
458
+ reject(this.getCodePushError(err, res));
459
+ return;
460
+ }
461
+ let body;
462
+ try {
463
+ body = JSON.parse(res.text);
464
+ }
465
+ catch (err) { }
466
+ if (res.ok) {
467
+ if (expectResponseBody && !body) {
468
+ reject({
469
+ message: `Could not parse response: ${res.text}`,
470
+ statusCode: AccountManager.ERROR_INTERNAL_SERVER,
471
+ });
472
+ }
473
+ else {
474
+ resolve({
475
+ headers: res.header,
476
+ body: body,
477
+ });
478
+ }
479
+ }
480
+ else {
481
+ if (body) {
482
+ reject({
483
+ message: body.message,
484
+ statusCode: this.getErrorStatus(err, res),
485
+ });
486
+ }
487
+ else {
488
+ reject({
489
+ message: res.text,
490
+ statusCode: this.getErrorStatus(err, res),
491
+ });
492
+ }
493
+ }
494
+ });
495
+ });
496
+ }
497
+ getCodePushError(error, response) {
498
+ if (error.syscall === "getaddrinfo") {
499
+ error.message = `Unable to connect to the CodePush server. Are you offline, or behind a firewall or proxy?\n(${error.message})`;
500
+ }
501
+ return {
502
+ message: this.getErrorMessage(error, response),
503
+ statusCode: this.getErrorStatus(error, response),
504
+ };
505
+ }
506
+ getErrorStatus(error, response) {
507
+ return (error && error.status) || (response && response.status) || AccountManager.ERROR_GATEWAY_TIMEOUT;
508
+ }
509
+ getErrorMessage(error, response) {
510
+ return response && response.text ? response.text : error.message;
511
+ }
512
+ attachCredentials(request) {
513
+ if (this._customHeaders) {
514
+ for (const headerName in this._customHeaders) {
515
+ request.set(headerName, this._customHeaders[headerName]);
516
+ }
517
+ }
518
+ // console.log("this.organisations ::", this.organisations);
519
+ // console.log("this.passedOrgName ::", this.passedOrgName);
520
+ if (this.passedOrgName && this.passedOrgName.length > 0) {
521
+ // eslint-disable-next-line prefer-const
522
+ let tenantId = this.getTenantId(this.passedOrgName);
523
+ request.set("tenant", tenantId);
524
+ }
525
+ const bearerToken = "cli-" + this._accessKey;
526
+ request.set("Accept", `application/vnd.code-push.v${AccountManager.API_VERSION}+json`);
527
+ request.set("Authorization", `Bearer ${bearerToken}`);
528
+ request.set("X-CodePush-SDK-Version", packageJson.version);
529
+ }
530
+ }
531
+ module.exports = AccountManager;
@@ -0,0 +1,111 @@
1
+ #!/bin/bash
2
+
3
+ # Get the directory where the script is located
4
+ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
5
+
6
+ # Print detailed usage
7
+ print_usage() {
8
+ echo "Usage: $0 <old_bundle> <patch_file> <output_bundle> <is_patch_compressed>"
9
+ echo "Example: $0 originalBundle/index.android.bundle patch/bundle.patch patchedBundle/index.android.bundle false"
10
+ echo ""
11
+ echo "Arguments:"
12
+ echo " old_bundle - Path to the original bundle file"
13
+ echo " patch_file - Path to the patch file"
14
+ echo " output_bundle - Path where the new bundle will be created"
15
+ echo ""
16
+ echo "Current directory: $(pwd)"
17
+ }
18
+
19
+ # Check number of arguments
20
+ if [ "$#" -ne 4 ]; then
21
+ echo "Error: Incorrect number of arguments"
22
+ print_usage
23
+ exit 1
24
+ fi
25
+
26
+ # Convert to absolute paths, handling non-existent directories
27
+ OLD_BUNDLE="$1"
28
+ PATCH_FILE="$2"
29
+ OUTPUT_BUNDLE="$3"
30
+ IS_PATCH_COMPRESSED="$4"
31
+
32
+ # If paths are relative, make them absolute from current directory
33
+ if [[ ! "$OLD_BUNDLE" = /* ]]; then
34
+ OLD_BUNDLE="$(pwd)/$OLD_BUNDLE"
35
+ fi
36
+
37
+ if [[ ! "$PATCH_FILE" = /* ]]; then
38
+ PATCH_FILE="$(pwd)/$PATCH_FILE"
39
+ fi
40
+
41
+ if [[ ! "$OUTPUT_BUNDLE" = /* ]]; then
42
+ OUTPUT_BUNDLE="$(pwd)/$OUTPUT_BUNDLE"
43
+ fi
44
+
45
+ echo "Using paths:"
46
+ echo "Old bundle: $OLD_BUNDLE"
47
+ echo "Patch file: $PATCH_FILE"
48
+ echo "Output bundle: $OUTPUT_BUNDLE"
49
+ echo ""
50
+
51
+ # Check if input files exist with detailed error messages
52
+ if [ ! -e "$OLD_BUNDLE" ]; then
53
+ echo "Error: Old bundle file not found"
54
+ echo "Path: $OLD_BUNDLE"
55
+ echo "Directory contents of $(dirname "$OLD_BUNDLE"):"
56
+ ls -la "$(dirname "$OLD_BUNDLE")" 2>/dev/null || echo "Directory does not exist"
57
+ exit 1
58
+ fi
59
+
60
+ if [ ! -f "$OLD_BUNDLE" ]; then
61
+ echo "Error: Old bundle exists but is not a regular file"
62
+ echo "Path: $OLD_BUNDLE"
63
+ ls -la "$OLD_BUNDLE"
64
+ exit 1
65
+ fi
66
+
67
+ if [ ! -e "$PATCH_FILE" ]; then
68
+ echo "Error: Patch file not found"
69
+ echo "Path: $PATCH_FILE"
70
+ echo "Directory contents of $(dirname "$PATCH_FILE"):"
71
+ ls -la "$(dirname "$PATCH_FILE")" 2>/dev/null || echo "Directory does not exist"
72
+ exit 1
73
+ fi
74
+
75
+ if [ ! -f "$PATCH_FILE" ]; then
76
+ echo "Error: Patch file exists but is not a regular file"
77
+ echo "Path: $PATCH_FILE"
78
+ ls -la "$PATCH_FILE"
79
+ exit 1
80
+ fi
81
+
82
+ # Create output directory if it doesn't exist
83
+ OUTPUT_DIR="$(dirname "$OUTPUT_BUNDLE")"
84
+ if ! mkdir -p "$OUTPUT_DIR"; then
85
+ echo "Error: Failed to create output directory"
86
+ echo "Path: $OUTPUT_DIR"
87
+ exit 1
88
+ fi
89
+
90
+ # Verify patch format
91
+ if ! head -c 16 "$PATCH_FILE" 2>/dev/null | grep -q "ENDSLEY/BSDIFF43"; then
92
+ echo "Warning: Patch is not in ENDSLEY/BSDIFF43 format"
93
+ echo "First 16 bytes of patch file:"
94
+ head -c 16 "$PATCH_FILE" | xxd
95
+ fi
96
+
97
+ # Apply the patch using bsdiff43
98
+ echo "Applying patch..."
99
+ "$SCRIPT_DIR/../../../bsdiff/bsdiff43" patch "$OLD_BUNDLE" "$OUTPUT_BUNDLE" "$PATCH_FILE" "$IS_PATCH_COMPRESSED"
100
+
101
+ if [ $? -eq 0 ]; then
102
+ echo "Successfully applied patch:"
103
+ echo "Old bundle: $(wc -c < "$OLD_BUNDLE") bytes"
104
+ echo "Patch size: $(wc -c < "$PATCH_FILE") bytes"
105
+ echo "New bundle: $(wc -c < "$OUTPUT_BUNDLE") bytes"
106
+ echo "Output file: $OUTPUT_BUNDLE"
107
+ else
108
+ echo "Failed to apply patch"
109
+ echo "Command: $SCRIPT_DIR/../../bsdiff/bsdiff43 patch $OLD_BUNDLE $OUTPUT_BUNDLE $PATCH_FILE"
110
+ exit 1
111
+ fi
@@ -0,0 +1,53 @@
1
+ #!/bin/bash
2
+
3
+ # Get the directory where the script is located
4
+ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
5
+
6
+ if [ "$#" -ne 4 ]; then
7
+ echo "Usage: $0 <old_bundle> <new_bundle> <patch_file> <compression>"
8
+ echo "Example: $0 path/to/old.bundle path/to/new.bundle directory/to/bundle.patch false"
9
+ exit 1
10
+ fi
11
+
12
+ # Convert to absolute paths
13
+ OLD_BUNDLE="$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
14
+ NEW_BUNDLE="$(cd "$(dirname "$2")" && pwd)/$(basename "$2")"
15
+ COMPRESSION="$4"
16
+
17
+ # Check if third argument is a file path
18
+ if [[ -f "$3" ]]; then
19
+ echo "Error: Third argument must be a directory path, not a file path"
20
+ exit 1
21
+ fi
22
+
23
+ PATCH_DIR="$3"
24
+ PATCH_FILE="$PATCH_DIR/bundle.patch"
25
+
26
+ echo "==Using Paths=="
27
+ echo "Old bundle: $OLD_BUNDLE"
28
+ echo "New bundle: $NEW_BUNDLE"
29
+ echo "Patch file: $PATCH_FILE"
30
+
31
+ # Create the patch using bsdiff43
32
+ echo "Creating patch using bsdiff43..."
33
+ "$SCRIPT_DIR/../../../bsdiff/bsdiff43" diff "$OLD_BUNDLE" "$NEW_BUNDLE" "$PATCH_FILE" "$COMPRESSION"
34
+
35
+ if [ $? -eq 0 ]; then
36
+ echo "===Successfully created patch==="
37
+ echo "Old bundle Size: $(wc -c < "$OLD_BUNDLE") bytes"
38
+ echo "New bundle Size: $(wc -c < "$NEW_BUNDLE") bytes"
39
+ echo "Patch Size: $(wc -c < "$PATCH_FILE") bytes"
40
+ echo "Patch file created at: $PATCH_FILE"
41
+
42
+ # Verify patch format
43
+ echo "==Verifying patch format==="
44
+ if head -c 16 "$PATCH_FILE" | grep -q "ENDSLEY/BSDIFF43"; then
45
+ echo "Verified: Patch is in ENDSLEY/BSDIFF43 format"
46
+ else
47
+ echo "Patch is not in ENDSLEY/BSDIFF43 format"
48
+ exit 1
49
+ fi
50
+ else
51
+ echo "Failed to create patch"
52
+ exit 1
53
+ fi