@revopush/code-push-cli 0.0.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.
Files changed (59) hide show
  1. package/.eslintrc.json +17 -0
  2. package/.idea/cli.iml +9 -0
  3. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  4. package/.idea/misc.xml +6 -0
  5. package/.idea/modules.xml +8 -0
  6. package/.idea/prettier.xml +6 -0
  7. package/.idea/vcs.xml +6 -0
  8. package/README.md +774 -0
  9. package/bin/script/acquisition-sdk.js +178 -0
  10. package/bin/script/cli.js +23 -0
  11. package/bin/script/command-executor.js +1290 -0
  12. package/bin/script/command-parser.js +1097 -0
  13. package/bin/script/commands/debug.js +125 -0
  14. package/bin/script/hash-utils.js +203 -0
  15. package/bin/script/index.js +5 -0
  16. package/bin/script/management-sdk.js +419 -0
  17. package/bin/script/react-native-utils.js +249 -0
  18. package/bin/script/sign.js +69 -0
  19. package/bin/script/types/cli.js +40 -0
  20. package/bin/script/types/rest-definitions.js +19 -0
  21. package/bin/script/types.js +4 -0
  22. package/bin/script/utils/file-utils.js +50 -0
  23. package/bin/test/acquisition-rest-mock.js +108 -0
  24. package/bin/test/acquisition-sdk.js +188 -0
  25. package/bin/test/cli.js +1342 -0
  26. package/bin/test/hash-utils.js +149 -0
  27. package/bin/test/management-sdk.js +338 -0
  28. package/package.json +68 -0
  29. package/prettier.config.js +7 -0
  30. package/script/acquisition-sdk.ts +273 -0
  31. package/script/cli.ts +27 -0
  32. package/script/command-executor.ts +1610 -0
  33. package/script/command-parser.ts +1310 -0
  34. package/script/commands/debug.ts +148 -0
  35. package/script/hash-utils.ts +241 -0
  36. package/script/index.ts +5 -0
  37. package/script/management-sdk.ts +575 -0
  38. package/script/react-native-utils.ts +283 -0
  39. package/script/sign.ts +80 -0
  40. package/script/types/cli.ts +232 -0
  41. package/script/types/rest-definitions.ts +152 -0
  42. package/script/types.ts +35 -0
  43. package/script/utils/file-utils.ts +46 -0
  44. package/test/acquisition-rest-mock.ts +125 -0
  45. package/test/acquisition-sdk.ts +272 -0
  46. package/test/cli.ts +1692 -0
  47. package/test/hash-utils.ts +170 -0
  48. package/test/management-sdk.ts +438 -0
  49. package/test/resources/TestApp/android/app/build.gradle +56 -0
  50. package/test/resources/TestApp/iOS/TestApp/Info.plist +49 -0
  51. package/test/resources/TestApp/index.android.js +2 -0
  52. package/test/resources/TestApp/index.ios.js +2 -0
  53. package/test/resources/TestApp/index.windows.js +2 -0
  54. package/test/resources/TestApp/package.json +6 -0
  55. package/test/resources/TestApp/windows/TestApp/Package.appxmanifest +46 -0
  56. package/test/resources/ignoredMetadata.zip +0 -0
  57. package/test/resources/test.zip +0 -0
  58. package/test/superagent-mock-config.js +58 -0
  59. package/tsconfig.json +13 -0
@@ -0,0 +1,148 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+
4
+ import * as childProcess from "child_process";
5
+ import * as cli from "../../script/types/cli";
6
+ import * as moment from "moment";
7
+ import * as path from "path";
8
+ import * as Q from "q";
9
+
10
+ const simctl = require("simctl");
11
+ const which = require("which");
12
+
13
+ interface IDebugPlatform {
14
+ getLogProcess(): any;
15
+ normalizeLogMessage(message: string): string;
16
+ }
17
+
18
+ class AndroidDebugPlatform implements IDebugPlatform {
19
+ public getLogProcess(): any {
20
+ try {
21
+ which.sync("adb");
22
+ } catch (e) {
23
+ throw new Error("ADB command not found. Please ensure it is installed and available on your path.");
24
+ }
25
+
26
+ const numberOfAvailableDevices = this.getNumberOfAvailableDevices();
27
+ if (numberOfAvailableDevices === 0) {
28
+ throw new Error("No Android devices found. Re-run this command after starting one.");
29
+ }
30
+
31
+ // For now there is no ability to specify device for debug like:
32
+ // code-push debug android "192.168.121.102:5555"
33
+ // So we have to throw an error in case more than 1 android device was attached
34
+ // otherwise we will very likely run into an exception while trying to read ‘adb logcat’ from device which codepushified app is not running on.
35
+ if (numberOfAvailableDevices > 1) {
36
+ throw new Error(`Found "${numberOfAvailableDevices}" android devices. Please leave only one device you need to debug.`);
37
+ }
38
+
39
+ return childProcess.spawn("adb", ["logcat"]);
40
+ }
41
+
42
+ // The following is an example of what the output looks
43
+ // like when running the "adb devices" command.
44
+ //
45
+ // List of devices attached
46
+ // emulator-5554 device
47
+ // 192.168.121.102:5555 device
48
+ private getNumberOfAvailableDevices(): number {
49
+ const output = childProcess.execSync("adb devices").toString();
50
+ const matches = output.match(/\b(device)\b/gim);
51
+ if (matches != null) {
52
+ return matches.length;
53
+ }
54
+ return 0;
55
+ }
56
+
57
+ public normalizeLogMessage(message: string): string {
58
+ // Check to see whether the message includes the source URL
59
+ // suffix, and if so, strip it. This can occur in Android Cordova apps.
60
+ const sourceURLIndex: number = message.indexOf('", source: file:///');
61
+ if (~sourceURLIndex) {
62
+ return message.substring(0, sourceURLIndex);
63
+ } else {
64
+ return message;
65
+ }
66
+ }
67
+ }
68
+
69
+ class iOSDebugPlatform implements IDebugPlatform {
70
+ private getSimulatorID(): string {
71
+ const output: any = simctl.list({ devices: true, silent: true });
72
+ const simulators: string[] = output.json.devices
73
+ .map((platform: any) => platform.devices)
74
+ .reduce((prev: any, next: any) => prev.concat(next))
75
+ .filter((device: any) => device.state === "Booted")
76
+ .map((device: any) => device.id);
77
+
78
+ return simulators[0];
79
+ }
80
+
81
+ public getLogProcess(): any {
82
+ if (process.platform !== "darwin") {
83
+ throw new Error("iOS debug logs can only be viewed on OS X.");
84
+ }
85
+
86
+ const simulatorID: string = this.getSimulatorID();
87
+ if (!simulatorID) {
88
+ throw new Error("No iOS simulators found. Re-run this command after starting one.");
89
+ }
90
+
91
+ const logFilePath: string = path.join(process.env.HOME, "Library/Logs/CoreSimulator", simulatorID, "system.log");
92
+ return childProcess.spawn("tail", ["-f", logFilePath]);
93
+ }
94
+
95
+ public normalizeLogMessage(message: string): string {
96
+ return message;
97
+ }
98
+ }
99
+
100
+ const logMessagePrefix = "[CodePush] ";
101
+ function processLogData(logData: Buffer) {
102
+ const content = logData.toString();
103
+ content
104
+ .split("\n")
105
+ .filter((line: string) => line.indexOf(logMessagePrefix) > -1)
106
+ .map((line: string) => {
107
+ // Allow the current platform
108
+ // to normalize the message first.
109
+ line = this.normalizeLogMessage(line);
110
+
111
+ // Strip the CodePush-specific, platform agnostic
112
+ // log message prefix that is added to each entry.
113
+ const message = line.substring(line.indexOf(logMessagePrefix) + logMessagePrefix.length);
114
+
115
+ const timeStamp = moment().format("hh:mm:ss");
116
+ return `[${timeStamp}] ${message}`;
117
+ })
118
+ .forEach((line: string) => console.log(line));
119
+ }
120
+
121
+ const debugPlatforms: any = {
122
+ android: new AndroidDebugPlatform(),
123
+ ios: new iOSDebugPlatform(),
124
+ };
125
+
126
+ export default function (command: cli.IDebugCommand): Q.Promise<void> {
127
+ return Q.Promise<void>((resolve, reject) => {
128
+ const platform: string = command.platform.toLowerCase();
129
+ const debugPlatform: IDebugPlatform = debugPlatforms[platform];
130
+
131
+ if (!debugPlatform) {
132
+ const availablePlatforms = Object.getOwnPropertyNames(debugPlatforms);
133
+ return reject(new Error(`"${platform}" is an unsupported platform. Available options are ${availablePlatforms.join(", ")}.`));
134
+ }
135
+
136
+ try {
137
+ const logProcess = debugPlatform.getLogProcess();
138
+ console.log(`Listening for ${platform} debug logs (Press CTRL+C to exit)`);
139
+
140
+ logProcess.stdout.on("data", processLogData.bind(debugPlatform));
141
+ logProcess.stderr.on("data", reject);
142
+
143
+ logProcess.on("close", resolve);
144
+ } catch (e) {
145
+ reject(e);
146
+ }
147
+ });
148
+ }
@@ -0,0 +1,241 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+
4
+ /**
5
+ * NOTE!!! This utility file is duplicated for use by the CodePush service (for server-driven hashing/
6
+ * integrity checks) and Management SDK (for end-to-end code signing), please keep them in sync.
7
+ */
8
+
9
+ import * as crypto from "crypto";
10
+ import * as fs from "fs";
11
+ import * as path from "path";
12
+ import * as q from "q";
13
+ import * as stream from "stream";
14
+
15
+ // Do not throw an exception if either of these modules are missing, as they may not be needed by the
16
+ // consumer of this file.
17
+ // - recursiveFs: Only required for hashing of directories
18
+ // - yauzl: Only required for in-memory hashing of zip files
19
+ let recursiveFs, yauzl;
20
+
21
+ try {
22
+ recursiveFs = require("recursive-fs");
23
+ } catch (e) {}
24
+ try {
25
+ yauzl = require("yauzl");
26
+ } catch (e) {}
27
+
28
+ import Promise = q.Promise;
29
+ const HASH_ALGORITHM = "sha256";
30
+
31
+ export function generatePackageHashFromDirectory(directoryPath: string, basePath: string): Promise<string> {
32
+ if (!fs.lstatSync(directoryPath).isDirectory()) {
33
+ throw new Error("Not a directory. Please either create a directory, or use hashFile().");
34
+ }
35
+
36
+ return generatePackageManifestFromDirectory(directoryPath, basePath).then((manifest: PackageManifest) => {
37
+ return manifest.computePackageHash();
38
+ });
39
+ }
40
+
41
+ export function generatePackageManifestFromZip(filePath: string): Promise<PackageManifest> {
42
+ const deferred: q.Deferred<PackageManifest> = q.defer<PackageManifest>();
43
+ const reject = (error: Error) => {
44
+ if (deferred.promise.isPending()) {
45
+ deferred.reject(error);
46
+ }
47
+ };
48
+
49
+ const resolve = (manifest: PackageManifest) => {
50
+ if (deferred.promise.isPending()) {
51
+ deferred.resolve(manifest);
52
+ }
53
+ };
54
+
55
+ let zipFile: any;
56
+
57
+ yauzl.open(filePath, { lazyEntries: true }, (error?: any, openedZipFile?: any): void => {
58
+ if (error) {
59
+ // This is the first time we try to read the package as a .zip file;
60
+ // however, it may not be a .zip file. Handle this gracefully.
61
+ resolve(null);
62
+ return;
63
+ }
64
+
65
+ zipFile = openedZipFile;
66
+ const fileHashesMap = new Map<string, string>();
67
+ const hashFilePromises: q.Promise<void>[] = [];
68
+
69
+ // Read each entry in the archive sequentially and generate a hash for it.
70
+ zipFile.readEntry();
71
+ zipFile
72
+ .on("error", (error: any): void => {
73
+ reject(error);
74
+ })
75
+ .on("entry", (entry: any): void => {
76
+ const fileName: string = PackageManifest.normalizePath(entry.fileName);
77
+ if (PackageManifest.isIgnored(fileName)) {
78
+ zipFile.readEntry();
79
+ return;
80
+ }
81
+
82
+ zipFile.openReadStream(entry, (error?: any, readStream?: stream.Readable): void => {
83
+ if (error) {
84
+ reject(error);
85
+ return;
86
+ }
87
+
88
+ hashFilePromises.push(
89
+ hashStream(readStream).then((hash: string) => {
90
+ fileHashesMap.set(fileName, hash);
91
+ zipFile.readEntry();
92
+ }, reject)
93
+ );
94
+ });
95
+ })
96
+ .on("end", (): void => {
97
+ q.all(hashFilePromises).then(() => resolve(new PackageManifest(fileHashesMap)), reject);
98
+ });
99
+ });
100
+
101
+ return deferred.promise.finally(() => zipFile && zipFile.close());
102
+ }
103
+
104
+ export function generatePackageManifestFromDirectory(directoryPath: string, basePath: string): Promise<PackageManifest> {
105
+ const deferred: q.Deferred<PackageManifest> = q.defer<PackageManifest>();
106
+ const fileHashesMap = new Map<string, string>();
107
+
108
+ recursiveFs.readdirr(directoryPath, (error?: any, directories?: string[], files?: string[]): void => {
109
+ if (error) {
110
+ deferred.reject(error);
111
+ return;
112
+ }
113
+
114
+ if (!files || files.length === 0) {
115
+ deferred.reject("Error: Can't sign the release because no files were found.");
116
+ return;
117
+ }
118
+
119
+ // Hash the files sequentially, because streaming them in parallel is not necessarily faster
120
+ const generateManifestPromise: Promise<void> = files.reduce((soFar: Promise<void>, filePath: string) => {
121
+ return soFar.then(() => {
122
+ const relativePath: string = PackageManifest.normalizePath(path.relative(basePath, filePath));
123
+ if (!PackageManifest.isIgnored(relativePath)) {
124
+ return hashFile(filePath).then((hash: string) => {
125
+ fileHashesMap.set(relativePath, hash);
126
+ });
127
+ }
128
+ });
129
+ }, q(<void>null));
130
+
131
+ generateManifestPromise
132
+ .then(() => {
133
+ deferred.resolve(new PackageManifest(fileHashesMap));
134
+ }, deferred.reject)
135
+ .done();
136
+ });
137
+
138
+ return deferred.promise;
139
+ }
140
+
141
+ export function hashFile(filePath: string): Promise<string> {
142
+ const readStream: fs.ReadStream = fs.createReadStream(filePath);
143
+ return hashStream(readStream);
144
+ }
145
+
146
+ export function hashStream(readStream: stream.Readable): Promise<string> {
147
+ const hashStream = <stream.Transform>(<any>crypto.createHash(HASH_ALGORITHM));
148
+ const deferred: q.Deferred<string> = q.defer<string>();
149
+
150
+ readStream
151
+ .on("error", (error: any): void => {
152
+ if (deferred.promise.isPending()) {
153
+ hashStream.end();
154
+ deferred.reject(error);
155
+ }
156
+ })
157
+ .on("end", (): void => {
158
+ if (deferred.promise.isPending()) {
159
+ hashStream.end();
160
+
161
+ const buffer = <Buffer>hashStream.read();
162
+ const hash: string = buffer.toString("hex");
163
+
164
+ deferred.resolve(hash);
165
+ }
166
+ });
167
+
168
+ readStream.pipe(hashStream);
169
+
170
+ return deferred.promise;
171
+ }
172
+
173
+ export class PackageManifest {
174
+ private _map: Map<string, string>;
175
+
176
+ public constructor(map?: Map<string, string>) {
177
+ if (!map) {
178
+ map = new Map<string, string>();
179
+ }
180
+ this._map = map;
181
+ }
182
+
183
+ public toMap(): Map<string, string> {
184
+ return this._map;
185
+ }
186
+
187
+ public computePackageHash(): Promise<string> {
188
+ let entries: string[] = [];
189
+ this._map.forEach((hash: string, name: string): void => {
190
+ entries.push(name + ":" + hash);
191
+ });
192
+
193
+ // Make sure this list is alphabetically ordered so that other clients
194
+ // can also compute this hash easily given the update contents.
195
+ entries = entries.sort();
196
+
197
+ return q(crypto.createHash(HASH_ALGORITHM).update(JSON.stringify(entries)).digest("hex"));
198
+ }
199
+
200
+ public serialize(): string {
201
+ const obj: any = {};
202
+
203
+ this._map.forEach(function (value, key) {
204
+ obj[key] = value;
205
+ });
206
+
207
+ return JSON.stringify(obj);
208
+ }
209
+
210
+ public static deserialize(serializedContents: string): PackageManifest {
211
+ try {
212
+ const obj: any = JSON.parse(serializedContents);
213
+ const map = new Map<string, string>();
214
+
215
+ for (const key of Object.keys(obj)) {
216
+ map.set(key, obj[key]);
217
+ }
218
+
219
+ return new PackageManifest(map);
220
+ } catch (e) {}
221
+ }
222
+
223
+ public static normalizePath(filePath: string): string {
224
+ return filePath.replace(/\\/g, "/");
225
+ }
226
+
227
+ public static isIgnored(relativeFilePath: string): boolean {
228
+ const __MACOSX = "__MACOSX/";
229
+ const DS_STORE = ".DS_Store";
230
+
231
+ return startsWith(relativeFilePath, __MACOSX) || relativeFilePath === DS_STORE || endsWith(relativeFilePath, "/" + DS_STORE);
232
+ }
233
+ }
234
+
235
+ function startsWith(str: string, prefix: string): boolean {
236
+ return str && str.substring(0, prefix.length) === prefix;
237
+ }
238
+
239
+ function endsWith(str: string, suffix: string): boolean {
240
+ return str && str.indexOf(suffix, str.length - suffix.length) !== -1;
241
+ }
@@ -0,0 +1,5 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+
4
+ import AccountManager = require("./management-sdk");
5
+ export = AccountManager;