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