@revopush/code-push-cli 0.0.7 → 0.0.8-rc.1

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,209 @@
1
+ import * as path from "path";
2
+ import * as fs from "fs";
3
+ import * as chalk from "chalk";
4
+ import { log } from "./command-executor";
5
+ import { hashFile } from "./hash-utils";
6
+ import * as os from "os";
7
+ import * as Q from "q";
8
+ import * as yazl from "yazl";
9
+ import { readFile } from "node:fs/promises";
10
+ import plist from "plist"
11
+ import * as bplist from "bplist-parser";
12
+
13
+ export async function extractMetadataFromAndroid(extractFolder, outputFolder) {
14
+ const assetsFolder = path.join(extractFolder, "assets");
15
+ if (!fs.existsSync(assetsFolder)) {
16
+ throw new Error("Invalid APK structure: assets folder not found.");
17
+ }
18
+
19
+ const codepushMetadata = path.join(assetsFolder, "CodePushMetadata");
20
+
21
+ let fileHashes: { [key: string]: string } = {};
22
+ if (fs.existsSync(codepushMetadata)) {
23
+ fileHashes = await takeHashesFromMetadata(codepushMetadata);
24
+ } else {
25
+ log(chalk.yellow(`\nWarning: CodepushMetadata file not found in APK. Check used version of SDK\n`));
26
+ }
27
+
28
+ // Get index.android.bundle from root of app folder
29
+ const mainJsBundlePath = path.join(assetsFolder, "index.android.bundle");
30
+ if (fs.existsSync(mainJsBundlePath)) {
31
+ // Copy bundle to output folder
32
+ const outputCodePushFolder = path.join(outputFolder, "CodePush");
33
+ fs.mkdirSync(outputCodePushFolder, { recursive: true });
34
+ const outputBundlePath = path.join(outputCodePushFolder, "index.android.bundle");
35
+ fs.copyFileSync(mainJsBundlePath, outputBundlePath);
36
+ } else {
37
+ throw new Error("index.android.bundle not found in APK root folder.");
38
+ }
39
+
40
+ // Save packageManifest.json
41
+ const manifestPath = path.join(outputFolder, "packageManifest.json");
42
+ fs.writeFileSync(manifestPath, JSON.stringify(fileHashes, null, 2));
43
+ log(chalk.cyan(`\nSaved packageManifest.json with ${Object.keys(fileHashes).length} entries.\n`));
44
+
45
+ // Create zip archive with packageManifest.json and bundle file
46
+ const zipPath = path.join(os.tmpdir(), `CodePushBinary-${Date.now()}.zip`);
47
+ await createZipArchive(outputFolder, zipPath, ["packageManifest.json", "CodePush/index.android.bundle"]);
48
+
49
+ return zipPath;
50
+ }
51
+
52
+ export async function extractMetadataFromIOS(extractFolder, outputFolder) {
53
+ const payloadFolder = path.join(extractFolder, "Payload");
54
+ if (!fs.existsSync(payloadFolder)) {
55
+ throw new Error("Invalid IPA structure: Payload folder not found.");
56
+ }
57
+
58
+ const appFolders = fs.readdirSync(payloadFolder).filter((item) => {
59
+ const itemPath = path.join(payloadFolder, item);
60
+ return fs.statSync(itemPath).isDirectory() && item.endsWith(".app");
61
+ });
62
+
63
+ if (appFolders.length === 0) {
64
+ throw new Error("Invalid IPA structure: No .app folder found in Payload.");
65
+ }
66
+
67
+ const appFolder = path.join(payloadFolder, appFolders[0]);
68
+ const codePushFolder = path.join(appFolder, "assets");
69
+
70
+ const fileHashes: { [key: string]: string } = {};
71
+
72
+ if (fs.existsSync(codePushFolder)) {
73
+ await calculateHashesForDirectory(codePushFolder, appFolder, fileHashes);
74
+ } else {
75
+ log(chalk.yellow(`\nWarning: CodePush folder not found in IPA.\n`));
76
+ }
77
+
78
+ const mainJsBundlePath = path.join(appFolder, "main.jsbundle");
79
+ if (fs.existsSync(mainJsBundlePath)) {
80
+ log(chalk.cyan(`\nFound main.jsbundle, calculating hash:\n`));
81
+ const bundleHash = await hashFile(mainJsBundlePath);
82
+ fileHashes["CodePush/main.jsbundle"] = bundleHash;
83
+
84
+ // Copy bundle to output folder
85
+ const outputCodePushFolder = path.join(outputFolder, "CodePush");
86
+ fs.mkdirSync(outputCodePushFolder, { recursive: true });
87
+ const outputBundlePath = path.join(outputCodePushFolder, "main.jsbundle");
88
+ fs.copyFileSync(mainJsBundlePath, outputBundlePath);
89
+ } else {
90
+ throw new Error("main.jsbundle not found in IPA root folder.");
91
+ }
92
+
93
+ // Save packageManifest.json
94
+ const manifestPath = path.join(outputFolder, "packageManifest.json");
95
+ fs.writeFileSync(manifestPath, JSON.stringify(fileHashes, null, 2));
96
+ log(chalk.cyan(`\nSaved packageManifest.json with ${Object.keys(fileHashes).length} entries.\n`));
97
+
98
+ // Create zip archive with packageManifest.json and bundle file
99
+ const zipPath = path.join(os.tmpdir(), `CodePushBinary-${Date.now()}.zip`);
100
+ await createZipArchive(outputFolder, zipPath, ["packageManifest.json", "CodePush/main.jsbundle"]);
101
+
102
+ return zipPath;
103
+ }
104
+
105
+ async function calculateHashesForDirectory(
106
+ directoryPath: string,
107
+ basePath: string,
108
+ fileHashes: { [key: string]: string }
109
+ ) {
110
+ const items = fs.readdirSync(directoryPath);
111
+
112
+ for (const item of items) {
113
+ const itemPath = path.join(directoryPath, item);
114
+ const stat = fs.statSync(itemPath);
115
+
116
+ if (stat.isDirectory()) {
117
+ await calculateHashesForDirectory(itemPath, basePath, fileHashes);
118
+ } else {
119
+ // Calculate relative path from basePath (app folder) to the file
120
+ const relativePath = path.relative(basePath, itemPath).replace(/\\/g, "/");
121
+ const hash = await hashFile(itemPath);
122
+ const hashKey = `CodePush/${relativePath}`
123
+ fileHashes[hashKey] = hash;
124
+ log(chalk.gray(` ${relativePath}:${hash.substring(0, 8)}...\n`));
125
+ }
126
+ }
127
+ }
128
+
129
+
130
+ type BinaryHashes = { [p: string]: string };
131
+
132
+ async function takeHashesFromMetadata(metadataPath: string): Promise<BinaryHashes> {
133
+ const content = await readFile(metadataPath, "utf-8");
134
+ const metadata = JSON.parse(content);
135
+ if (!metadata || !metadata.manifest) {
136
+ throw new Error("Failed to take manifest from metadata file of APK");
137
+ }
138
+
139
+ return Object.fromEntries(metadata.manifest.map((item) => item.split(":")));
140
+ }
141
+
142
+ function createZipArchive(sourceFolder: string, zipPath: string, filesToInclude: string[]): Q.Promise<void> {
143
+ return Q.Promise<void>((resolve, reject) => {
144
+ const zipFile = new yazl.ZipFile();
145
+ const writeStream = fs.createWriteStream(zipPath);
146
+
147
+ zipFile.outputStream
148
+ .pipe(writeStream)
149
+ .on("error", (error: Error) => {
150
+ reject(error);
151
+ })
152
+ .on("close", () => {
153
+ resolve();
154
+ });
155
+
156
+ for (const file of filesToInclude) {
157
+ const filePath = path.join(sourceFolder, file);
158
+ if (fs.existsSync(filePath)) {
159
+ zipFile.addFile(filePath, file);
160
+ }
161
+ }
162
+
163
+ zipFile.end();
164
+ });
165
+ }
166
+
167
+ function parseAnyPlistFile(plistPath: string): any {
168
+ const buf = fs.readFileSync(plistPath);
169
+
170
+ if (buf.slice(0, 6).toString("ascii") === "bplist") {
171
+ const arr = bplist.parseBuffer(buf);
172
+ if (!arr?.length) throw new Error("Empty binary plist");
173
+ return arr[0];
174
+ }
175
+
176
+ const xml = buf.toString("utf8");
177
+ return plist.parse(xml);
178
+ }
179
+
180
+ export async function getIosVersion(extractFolder: string) {
181
+ const payloadFolder = path.join(extractFolder, "Payload");
182
+ if (!fs.existsSync(payloadFolder)) {
183
+ throw new Error("Invalid IPA structure: Payload folder not found.");
184
+ }
185
+
186
+ const appFolders = fs.readdirSync(payloadFolder).filter((item) => {
187
+ const itemPath = path.join(payloadFolder, item);
188
+ return fs.statSync(itemPath).isDirectory() && item.endsWith(".app");
189
+ });
190
+
191
+ if (appFolders.length === 0) {
192
+ throw new Error("Invalid IPA structure: No .app folder found in Payload.");
193
+ }
194
+
195
+ const appFolder = path.join(payloadFolder, appFolders[0]);
196
+
197
+ const plistPath = path.join(appFolder, "Info.plist");
198
+
199
+ const data = parseAnyPlistFile(plistPath);
200
+
201
+ console.log('App Version (Short):', data.CFBundleShortVersionString);
202
+ console.log('Build Number:', data.CFBundleVersion);
203
+ console.log('Bundle ID:', data.CFBundleIdentifier);
204
+
205
+ return {
206
+ version: data.CFBundleShortVersionString,
207
+ build: data.CFBundleVersion
208
+ };
209
+ }