@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,1290 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) Microsoft Corporation.
|
|
3
|
+
// Licensed under the MIT License.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.runReactNativeBundleCommand = exports.releaseReact = exports.release = exports.execute = exports.deploymentList = exports.createEmptyTempReleaseFolder = exports.confirm = exports.execSync = exports.spawn = exports.sdk = exports.log = void 0;
|
|
6
|
+
const AccountManager = require("./management-sdk");
|
|
7
|
+
const childProcess = require("child_process");
|
|
8
|
+
const debug_1 = require("./commands/debug");
|
|
9
|
+
const fs = require("fs");
|
|
10
|
+
const chalk = require("chalk");
|
|
11
|
+
const g2js = require("gradle-to-js/lib/parser");
|
|
12
|
+
const moment = require("moment");
|
|
13
|
+
const opener = require("opener");
|
|
14
|
+
const os = require("os");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
const plist = require("plist");
|
|
17
|
+
const progress = require("progress");
|
|
18
|
+
const prompt = require("prompt");
|
|
19
|
+
const Q = require("q");
|
|
20
|
+
const rimraf = require("rimraf");
|
|
21
|
+
const semver = require("semver");
|
|
22
|
+
const Table = require("cli-table");
|
|
23
|
+
const which = require("which");
|
|
24
|
+
const wordwrap = require("wordwrap");
|
|
25
|
+
const cli = require("../script/types/cli");
|
|
26
|
+
const sign_1 = require("./sign");
|
|
27
|
+
const xcode = require("xcode");
|
|
28
|
+
const react_native_utils_1 = require("./react-native-utils");
|
|
29
|
+
const file_utils_1 = require("./utils/file-utils");
|
|
30
|
+
const configFilePath = path.join(process.env.LOCALAPPDATA || process.env.HOME, ".code-push.config");
|
|
31
|
+
const emailValidator = require("email-validator");
|
|
32
|
+
const packageJson = require("../../package.json");
|
|
33
|
+
const parseXml = Q.denodeify(require("xml2js").parseString);
|
|
34
|
+
var Promise = Q.Promise;
|
|
35
|
+
const properties = require("properties");
|
|
36
|
+
const CLI_HEADERS = {
|
|
37
|
+
"X-CodePush-CLI-Version": packageJson.version,
|
|
38
|
+
};
|
|
39
|
+
const log = (message) => console.log(message);
|
|
40
|
+
exports.log = log;
|
|
41
|
+
exports.spawn = childProcess.spawn;
|
|
42
|
+
exports.execSync = childProcess.execSync;
|
|
43
|
+
let connectionInfo;
|
|
44
|
+
const confirm = (message = "Are you sure?") => {
|
|
45
|
+
message += " (y/N):";
|
|
46
|
+
return Promise((resolve, reject, notify) => {
|
|
47
|
+
prompt.message = "";
|
|
48
|
+
prompt.delimiter = "";
|
|
49
|
+
prompt.start();
|
|
50
|
+
prompt.get({
|
|
51
|
+
properties: {
|
|
52
|
+
response: {
|
|
53
|
+
description: chalk.cyan(message),
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
}, (err, result) => {
|
|
57
|
+
const accepted = result.response && result.response.toLowerCase() === "y";
|
|
58
|
+
const rejected = !result.response || result.response.toLowerCase() === "n";
|
|
59
|
+
if (accepted) {
|
|
60
|
+
resolve(true);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
if (!rejected) {
|
|
64
|
+
console.log('Invalid response: "' + result.response + '"');
|
|
65
|
+
}
|
|
66
|
+
resolve(false);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
exports.confirm = confirm;
|
|
72
|
+
function accessKeyAdd(command) {
|
|
73
|
+
return exports.sdk.addAccessKey(command.name, command.ttl).then((accessKey) => {
|
|
74
|
+
(0, exports.log)(`Successfully created the "${command.name}" access key: ${accessKey.key}`);
|
|
75
|
+
(0, exports.log)("Make sure to save this key value somewhere safe, since you won't be able to view it from the CLI again!");
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function accessKeyPatch(command) {
|
|
79
|
+
const willUpdateName = isCommandOptionSpecified(command.newName) && command.oldName !== command.newName;
|
|
80
|
+
const willUpdateTtl = isCommandOptionSpecified(command.ttl);
|
|
81
|
+
if (!willUpdateName && !willUpdateTtl) {
|
|
82
|
+
throw new Error("A new name and/or TTL must be provided.");
|
|
83
|
+
}
|
|
84
|
+
return exports.sdk.patchAccessKey(command.oldName, command.newName, command.ttl).then((accessKey) => {
|
|
85
|
+
let logMessage = "Successfully ";
|
|
86
|
+
if (willUpdateName) {
|
|
87
|
+
logMessage += `renamed the access key "${command.oldName}" to "${command.newName}"`;
|
|
88
|
+
}
|
|
89
|
+
if (willUpdateTtl) {
|
|
90
|
+
const expirationDate = moment(accessKey.expires).format("LLLL");
|
|
91
|
+
if (willUpdateName) {
|
|
92
|
+
logMessage += ` and changed its expiration date to ${expirationDate}`;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
logMessage += `changed the expiration date of the "${command.oldName}" access key to ${expirationDate}`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
(0, exports.log)(`${logMessage}.`);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function accessKeyList(command) {
|
|
102
|
+
throwForInvalidOutputFormat(command.format);
|
|
103
|
+
return exports.sdk.getAccessKeys().then((accessKeys) => {
|
|
104
|
+
printAccessKeys(command.format, accessKeys);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function accessKeyRemove(command) {
|
|
108
|
+
return (0, exports.confirm)().then((wasConfirmed) => {
|
|
109
|
+
if (wasConfirmed) {
|
|
110
|
+
return exports.sdk.removeAccessKey(command.accessKey).then(() => {
|
|
111
|
+
(0, exports.log)(`Successfully removed the "${command.accessKey}" access key.`);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
(0, exports.log)("Access key removal cancelled.");
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function appAdd(command) {
|
|
118
|
+
return exports.sdk.addApp(command.appName).then((app) => {
|
|
119
|
+
(0, exports.log)('Successfully added the "' + command.appName + '" app, along with the following default deployments:');
|
|
120
|
+
const deploymentListCommand = {
|
|
121
|
+
type: cli.CommandType.deploymentList,
|
|
122
|
+
appName: app.name,
|
|
123
|
+
format: "table",
|
|
124
|
+
displayKeys: true,
|
|
125
|
+
};
|
|
126
|
+
return (0, exports.deploymentList)(deploymentListCommand, /*showPackage=*/ false);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function appList(command) {
|
|
130
|
+
throwForInvalidOutputFormat(command.format);
|
|
131
|
+
let apps;
|
|
132
|
+
return exports.sdk.getApps().then((retrievedApps) => {
|
|
133
|
+
printAppList(command.format, retrievedApps);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function appRemove(command) {
|
|
137
|
+
return (0, exports.confirm)("Are you sure you want to remove this app? Note that its deployment keys will be PERMANENTLY unrecoverable.").then((wasConfirmed) => {
|
|
138
|
+
if (wasConfirmed) {
|
|
139
|
+
return exports.sdk.removeApp(command.appName).then(() => {
|
|
140
|
+
(0, exports.log)('Successfully removed the "' + command.appName + '" app.');
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
(0, exports.log)("App removal cancelled.");
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function appRename(command) {
|
|
147
|
+
return exports.sdk.renameApp(command.currentAppName, command.newAppName).then(() => {
|
|
148
|
+
(0, exports.log)('Successfully renamed the "' + command.currentAppName + '" app to "' + command.newAppName + '".');
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const createEmptyTempReleaseFolder = (folderPath) => {
|
|
152
|
+
return deleteFolder(folderPath).then(() => {
|
|
153
|
+
fs.mkdirSync(folderPath);
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
exports.createEmptyTempReleaseFolder = createEmptyTempReleaseFolder;
|
|
157
|
+
function appTransfer(command) {
|
|
158
|
+
throwForInvalidEmail(command.email);
|
|
159
|
+
return (0, exports.confirm)().then((wasConfirmed) => {
|
|
160
|
+
if (wasConfirmed) {
|
|
161
|
+
return exports.sdk.transferApp(command.appName, command.email).then(() => {
|
|
162
|
+
(0, exports.log)('Successfully transferred the ownership of app "' + command.appName + '" to the account with email "' + command.email + '".');
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
(0, exports.log)("App transfer cancelled.");
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function addCollaborator(command) {
|
|
169
|
+
throwForInvalidEmail(command.email);
|
|
170
|
+
return exports.sdk.addCollaborator(command.appName, command.email).then(() => {
|
|
171
|
+
(0, exports.log)('Successfully added "' + command.email + '" as a collaborator to the app "' + command.appName + '".');
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function listCollaborators(command) {
|
|
175
|
+
throwForInvalidOutputFormat(command.format);
|
|
176
|
+
return exports.sdk.getCollaborators(command.appName).then((retrievedCollaborators) => {
|
|
177
|
+
printCollaboratorsList(command.format, retrievedCollaborators);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function removeCollaborator(command) {
|
|
181
|
+
throwForInvalidEmail(command.email);
|
|
182
|
+
return (0, exports.confirm)().then((wasConfirmed) => {
|
|
183
|
+
if (wasConfirmed) {
|
|
184
|
+
return exports.sdk.removeCollaborator(command.appName, command.email).then(() => {
|
|
185
|
+
(0, exports.log)('Successfully removed "' + command.email + '" as a collaborator from the app "' + command.appName + '".');
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
(0, exports.log)("App collaborator removal cancelled.");
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function deleteConnectionInfoCache(printMessage = true) {
|
|
192
|
+
try {
|
|
193
|
+
fs.unlinkSync(configFilePath);
|
|
194
|
+
if (printMessage) {
|
|
195
|
+
(0, exports.log)(`Successfully logged-out. The session file located at ${chalk.cyan(configFilePath)} has been deleted.\r\n`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (ex) { }
|
|
199
|
+
}
|
|
200
|
+
function deleteFolder(folderPath) {
|
|
201
|
+
return Promise((resolve, reject, notify) => {
|
|
202
|
+
rimraf(folderPath, (err) => {
|
|
203
|
+
if (err) {
|
|
204
|
+
reject(err);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
resolve(null);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
function deploymentAdd(command) {
|
|
213
|
+
return exports.sdk.addDeployment(command.appName, command.deploymentName).then((deployment) => {
|
|
214
|
+
(0, exports.log)('Successfully added the "' +
|
|
215
|
+
command.deploymentName +
|
|
216
|
+
'" deployment with key "' +
|
|
217
|
+
deployment.key +
|
|
218
|
+
'" to the "' +
|
|
219
|
+
command.appName +
|
|
220
|
+
'" app.');
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
function deploymentHistoryClear(command) {
|
|
224
|
+
return (0, exports.confirm)().then((wasConfirmed) => {
|
|
225
|
+
if (wasConfirmed) {
|
|
226
|
+
return exports.sdk.clearDeploymentHistory(command.appName, command.deploymentName).then(() => {
|
|
227
|
+
(0, exports.log)('Successfully cleared the release history associated with the "' +
|
|
228
|
+
command.deploymentName +
|
|
229
|
+
'" deployment from the "' +
|
|
230
|
+
command.appName +
|
|
231
|
+
'" app.');
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
(0, exports.log)("Clear deployment cancelled.");
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const deploymentList = (command, showPackage = true) => {
|
|
238
|
+
throwForInvalidOutputFormat(command.format);
|
|
239
|
+
let deployments;
|
|
240
|
+
return exports.sdk
|
|
241
|
+
.getDeployments(command.appName)
|
|
242
|
+
.then((retrievedDeployments) => {
|
|
243
|
+
deployments = retrievedDeployments;
|
|
244
|
+
if (showPackage) {
|
|
245
|
+
const metricsPromises = deployments.map((deployment) => {
|
|
246
|
+
if (deployment.package) {
|
|
247
|
+
return exports.sdk.getDeploymentMetrics(command.appName, deployment.name).then((metrics) => {
|
|
248
|
+
if (metrics[deployment.package.label]) {
|
|
249
|
+
const totalActive = getTotalActiveFromDeploymentMetrics(metrics);
|
|
250
|
+
deployment.package.metrics = {
|
|
251
|
+
active: metrics[deployment.package.label].active,
|
|
252
|
+
downloaded: metrics[deployment.package.label].downloaded,
|
|
253
|
+
failed: metrics[deployment.package.label].failed,
|
|
254
|
+
installed: metrics[deployment.package.label].installed,
|
|
255
|
+
totalActive: totalActive,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
return Q(null);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
return Q.all(metricsPromises);
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
.then(() => {
|
|
268
|
+
printDeploymentList(command, deployments, showPackage);
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
exports.deploymentList = deploymentList;
|
|
272
|
+
function deploymentRemove(command) {
|
|
273
|
+
return (0, exports.confirm)("Are you sure you want to remove this deployment? Note that its deployment key will be PERMANENTLY unrecoverable.").then((wasConfirmed) => {
|
|
274
|
+
if (wasConfirmed) {
|
|
275
|
+
return exports.sdk.removeDeployment(command.appName, command.deploymentName).then(() => {
|
|
276
|
+
(0, exports.log)('Successfully removed the "' + command.deploymentName + '" deployment from the "' + command.appName + '" app.');
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
(0, exports.log)("Deployment removal cancelled.");
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
function deploymentRename(command) {
|
|
283
|
+
return exports.sdk.renameDeployment(command.appName, command.currentDeploymentName, command.newDeploymentName).then(() => {
|
|
284
|
+
(0, exports.log)('Successfully renamed the "' +
|
|
285
|
+
command.currentDeploymentName +
|
|
286
|
+
'" deployment to "' +
|
|
287
|
+
command.newDeploymentName +
|
|
288
|
+
'" for the "' +
|
|
289
|
+
command.appName +
|
|
290
|
+
'" app.');
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
function deploymentHistory(command) {
|
|
294
|
+
throwForInvalidOutputFormat(command.format);
|
|
295
|
+
return Q.all([
|
|
296
|
+
exports.sdk.getAccountInfo(),
|
|
297
|
+
exports.sdk.getDeploymentHistory(command.appName, command.deploymentName),
|
|
298
|
+
exports.sdk.getDeploymentMetrics(command.appName, command.deploymentName),
|
|
299
|
+
]).spread((account, deploymentHistory, metrics) => {
|
|
300
|
+
const totalActive = getTotalActiveFromDeploymentMetrics(metrics);
|
|
301
|
+
deploymentHistory.forEach((packageObject) => {
|
|
302
|
+
if (metrics[packageObject.label]) {
|
|
303
|
+
packageObject.metrics = {
|
|
304
|
+
active: metrics[packageObject.label].active,
|
|
305
|
+
downloaded: metrics[packageObject.label].downloaded,
|
|
306
|
+
failed: metrics[packageObject.label].failed,
|
|
307
|
+
installed: metrics[packageObject.label].installed,
|
|
308
|
+
totalActive: totalActive,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
printDeploymentHistory(command, deploymentHistory, account.email);
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
function deserializeConnectionInfo() {
|
|
316
|
+
try {
|
|
317
|
+
const savedConnection = fs.readFileSync(configFilePath, {
|
|
318
|
+
encoding: "utf8",
|
|
319
|
+
});
|
|
320
|
+
let connectionInfo = JSON.parse(savedConnection);
|
|
321
|
+
// If the connection info is in the legacy format, convert it to the modern format
|
|
322
|
+
if (connectionInfo.accessKeyName) {
|
|
323
|
+
connectionInfo = {
|
|
324
|
+
accessKey: connectionInfo.accessKeyName,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
const connInfo = connectionInfo;
|
|
328
|
+
return connInfo;
|
|
329
|
+
}
|
|
330
|
+
catch (ex) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function execute(command) {
|
|
335
|
+
connectionInfo = deserializeConnectionInfo();
|
|
336
|
+
return Q(null).then(() => {
|
|
337
|
+
switch (command.type) {
|
|
338
|
+
// Must not be logged in
|
|
339
|
+
case cli.CommandType.login:
|
|
340
|
+
case cli.CommandType.register:
|
|
341
|
+
if (connectionInfo) {
|
|
342
|
+
throw new Error("You are already logged in from this machine.");
|
|
343
|
+
}
|
|
344
|
+
break;
|
|
345
|
+
// It does not matter whether you are logged in or not
|
|
346
|
+
case cli.CommandType.link:
|
|
347
|
+
break;
|
|
348
|
+
// Must be logged in
|
|
349
|
+
default:
|
|
350
|
+
if (!!exports.sdk)
|
|
351
|
+
break; // Used by unit tests to skip authentication
|
|
352
|
+
if (!connectionInfo) {
|
|
353
|
+
throw new Error("You are not currently logged in. Run the 'code-push-standalone login' command to authenticate with the CodePush server.");
|
|
354
|
+
}
|
|
355
|
+
exports.sdk = getSdk(connectionInfo.accessKey, CLI_HEADERS, connectionInfo.customServerUrl);
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
switch (command.type) {
|
|
359
|
+
case cli.CommandType.accessKeyAdd:
|
|
360
|
+
return accessKeyAdd(command);
|
|
361
|
+
case cli.CommandType.accessKeyPatch:
|
|
362
|
+
return accessKeyPatch(command);
|
|
363
|
+
case cli.CommandType.accessKeyList:
|
|
364
|
+
return accessKeyList(command);
|
|
365
|
+
case cli.CommandType.accessKeyRemove:
|
|
366
|
+
return accessKeyRemove(command);
|
|
367
|
+
case cli.CommandType.appAdd:
|
|
368
|
+
return appAdd(command);
|
|
369
|
+
case cli.CommandType.appList:
|
|
370
|
+
return appList(command);
|
|
371
|
+
case cli.CommandType.appRemove:
|
|
372
|
+
return appRemove(command);
|
|
373
|
+
case cli.CommandType.appRename:
|
|
374
|
+
return appRename(command);
|
|
375
|
+
case cli.CommandType.appTransfer:
|
|
376
|
+
return appTransfer(command);
|
|
377
|
+
case cli.CommandType.collaboratorAdd:
|
|
378
|
+
return addCollaborator(command);
|
|
379
|
+
case cli.CommandType.collaboratorList:
|
|
380
|
+
return listCollaborators(command);
|
|
381
|
+
case cli.CommandType.collaboratorRemove:
|
|
382
|
+
return removeCollaborator(command);
|
|
383
|
+
case cli.CommandType.debug:
|
|
384
|
+
return (0, debug_1.default)(command);
|
|
385
|
+
case cli.CommandType.deploymentAdd:
|
|
386
|
+
return deploymentAdd(command);
|
|
387
|
+
case cli.CommandType.deploymentHistoryClear:
|
|
388
|
+
return deploymentHistoryClear(command);
|
|
389
|
+
case cli.CommandType.deploymentHistory:
|
|
390
|
+
return deploymentHistory(command);
|
|
391
|
+
case cli.CommandType.deploymentList:
|
|
392
|
+
return (0, exports.deploymentList)(command);
|
|
393
|
+
case cli.CommandType.deploymentRemove:
|
|
394
|
+
return deploymentRemove(command);
|
|
395
|
+
case cli.CommandType.deploymentRename:
|
|
396
|
+
return deploymentRename(command);
|
|
397
|
+
case cli.CommandType.link:
|
|
398
|
+
return link(command);
|
|
399
|
+
case cli.CommandType.login:
|
|
400
|
+
return login(command);
|
|
401
|
+
case cli.CommandType.logout:
|
|
402
|
+
return logout(command);
|
|
403
|
+
case cli.CommandType.patch:
|
|
404
|
+
return patch(command);
|
|
405
|
+
case cli.CommandType.promote:
|
|
406
|
+
return promote(command);
|
|
407
|
+
case cli.CommandType.register:
|
|
408
|
+
return register(command);
|
|
409
|
+
case cli.CommandType.release:
|
|
410
|
+
return (0, exports.release)(command);
|
|
411
|
+
case cli.CommandType.releaseReact:
|
|
412
|
+
return (0, exports.releaseReact)(command);
|
|
413
|
+
case cli.CommandType.rollback:
|
|
414
|
+
return rollback(command);
|
|
415
|
+
case cli.CommandType.sessionList:
|
|
416
|
+
return sessionList(command);
|
|
417
|
+
case cli.CommandType.sessionRemove:
|
|
418
|
+
return sessionRemove(command);
|
|
419
|
+
case cli.CommandType.whoami:
|
|
420
|
+
return whoami(command);
|
|
421
|
+
default:
|
|
422
|
+
// We should never see this message as invalid commands should be caught by the argument parser.
|
|
423
|
+
throw new Error("Invalid command: " + JSON.stringify(command));
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
exports.execute = execute;
|
|
428
|
+
function getTotalActiveFromDeploymentMetrics(metrics) {
|
|
429
|
+
let totalActive = 0;
|
|
430
|
+
Object.keys(metrics).forEach((label) => {
|
|
431
|
+
totalActive += metrics[label].active;
|
|
432
|
+
});
|
|
433
|
+
return totalActive;
|
|
434
|
+
}
|
|
435
|
+
function initiateExternalAuthenticationAsync(action, serverUrl) {
|
|
436
|
+
const message = `A browser is being launched to authenticate your account. Follow the instructions ` +
|
|
437
|
+
`it displays to complete your ${action === "register" ? "registration" : action}.`;
|
|
438
|
+
(0, exports.log)(message);
|
|
439
|
+
const hostname = os.hostname();
|
|
440
|
+
const url = `${serverUrl || AccountManager.SERVER_URL}/auth/${action}?hostname=${hostname}`;
|
|
441
|
+
opener(url);
|
|
442
|
+
}
|
|
443
|
+
function link(command) {
|
|
444
|
+
initiateExternalAuthenticationAsync("link", command.serverUrl);
|
|
445
|
+
return Q(null);
|
|
446
|
+
}
|
|
447
|
+
function login(command) {
|
|
448
|
+
// Check if one of the flags were provided.
|
|
449
|
+
if (command.accessKey) {
|
|
450
|
+
exports.sdk = getSdk(command.accessKey, CLI_HEADERS, command.serverUrl);
|
|
451
|
+
return exports.sdk.isAuthenticated().then((isAuthenticated) => {
|
|
452
|
+
if (isAuthenticated) {
|
|
453
|
+
serializeConnectionInfo(command.accessKey, /*preserveAccessKeyOnLogout*/ true, command.serverUrl);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
throw new Error("Invalid access key.");
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
return loginWithExternalAuthentication("login", command.serverUrl);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function loginWithExternalAuthentication(action, serverUrl) {
|
|
465
|
+
initiateExternalAuthenticationAsync(action, serverUrl);
|
|
466
|
+
(0, exports.log)(""); // Insert newline
|
|
467
|
+
return requestAccessKey().then((accessKey) => {
|
|
468
|
+
if (accessKey === null) {
|
|
469
|
+
// The user has aborted the synchronous prompt (e.g.: via [CTRL]+[C]).
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
exports.sdk = getSdk(accessKey, CLI_HEADERS, serverUrl);
|
|
473
|
+
return exports.sdk.isAuthenticated().then((isAuthenticated) => {
|
|
474
|
+
if (isAuthenticated) {
|
|
475
|
+
serializeConnectionInfo(accessKey, /*preserveAccessKeyOnLogout*/ false, serverUrl);
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
throw new Error("Invalid access key.");
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
function logout(command) {
|
|
484
|
+
return Q(null)
|
|
485
|
+
.then(() => {
|
|
486
|
+
if (!connectionInfo.preserveAccessKeyOnLogout) {
|
|
487
|
+
const machineName = os.hostname();
|
|
488
|
+
return exports.sdk.removeSession(machineName).catch((error) => {
|
|
489
|
+
// If we are not authenticated or the session doesn't exist anymore, just swallow the error instead of displaying it
|
|
490
|
+
if (error.statusCode !== AccountManager.ERROR_UNAUTHORIZED && error.statusCode !== AccountManager.ERROR_NOT_FOUND) {
|
|
491
|
+
throw error;
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
})
|
|
496
|
+
.then(() => {
|
|
497
|
+
exports.sdk = null;
|
|
498
|
+
deleteConnectionInfoCache();
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
function formatDate(unixOffset) {
|
|
502
|
+
const date = moment(unixOffset);
|
|
503
|
+
const now = moment();
|
|
504
|
+
if (Math.abs(now.diff(date, "days")) < 30) {
|
|
505
|
+
return date.fromNow(); // "2 hours ago"
|
|
506
|
+
}
|
|
507
|
+
else if (now.year() === date.year()) {
|
|
508
|
+
return date.format("MMM D"); // "Nov 6"
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
return date.format("MMM D, YYYY"); // "Nov 6, 2014"
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function printAppList(format, apps) {
|
|
515
|
+
if (format === "json") {
|
|
516
|
+
printJson(apps);
|
|
517
|
+
}
|
|
518
|
+
else if (format === "table") {
|
|
519
|
+
const headers = ["Name", "Deployments"];
|
|
520
|
+
printTable(headers, (dataSource) => {
|
|
521
|
+
apps.forEach((app, index) => {
|
|
522
|
+
const row = [app.name, wordwrap(50)(app.deployments.join(", "))];
|
|
523
|
+
dataSource.push(row);
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
function getCollaboratorDisplayName(email, collaboratorProperties) {
|
|
529
|
+
return collaboratorProperties.permission === AccountManager.AppPermission.OWNER ? email + chalk.magenta(" (Owner)") : email;
|
|
530
|
+
}
|
|
531
|
+
function printCollaboratorsList(format, collaborators) {
|
|
532
|
+
if (format === "json") {
|
|
533
|
+
const dataSource = { collaborators: collaborators };
|
|
534
|
+
printJson(dataSource);
|
|
535
|
+
}
|
|
536
|
+
else if (format === "table") {
|
|
537
|
+
const headers = ["E-mail Address"];
|
|
538
|
+
printTable(headers, (dataSource) => {
|
|
539
|
+
Object.keys(collaborators).forEach((email) => {
|
|
540
|
+
const row = [getCollaboratorDisplayName(email, collaborators[email])];
|
|
541
|
+
dataSource.push(row);
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
function printDeploymentList(command, deployments, showPackage = true) {
|
|
547
|
+
if (command.format === "json") {
|
|
548
|
+
printJson(deployments);
|
|
549
|
+
}
|
|
550
|
+
else if (command.format === "table") {
|
|
551
|
+
const headers = ["Name"];
|
|
552
|
+
if (command.displayKeys) {
|
|
553
|
+
headers.push("Deployment Key");
|
|
554
|
+
}
|
|
555
|
+
if (showPackage) {
|
|
556
|
+
headers.push("Update Metadata");
|
|
557
|
+
headers.push("Install Metrics");
|
|
558
|
+
}
|
|
559
|
+
printTable(headers, (dataSource) => {
|
|
560
|
+
deployments.forEach((deployment) => {
|
|
561
|
+
const row = [deployment.name];
|
|
562
|
+
if (command.displayKeys) {
|
|
563
|
+
row.push(deployment.key);
|
|
564
|
+
}
|
|
565
|
+
if (showPackage) {
|
|
566
|
+
row.push(getPackageString(deployment.package));
|
|
567
|
+
row.push(getPackageMetricsString(deployment.package));
|
|
568
|
+
}
|
|
569
|
+
dataSource.push(row);
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function printDeploymentHistory(command, deploymentHistory, currentUserEmail) {
|
|
575
|
+
if (command.format === "json") {
|
|
576
|
+
printJson(deploymentHistory);
|
|
577
|
+
}
|
|
578
|
+
else if (command.format === "table") {
|
|
579
|
+
const headers = ["Label", "Release Time", "App Version", "Mandatory"];
|
|
580
|
+
if (command.displayAuthor) {
|
|
581
|
+
headers.push("Released By");
|
|
582
|
+
}
|
|
583
|
+
headers.push("Description", "Install Metrics");
|
|
584
|
+
printTable(headers, (dataSource) => {
|
|
585
|
+
deploymentHistory.forEach((packageObject) => {
|
|
586
|
+
let releaseTime = formatDate(packageObject.uploadTime);
|
|
587
|
+
let releaseSource;
|
|
588
|
+
if (packageObject.releaseMethod === "Promote") {
|
|
589
|
+
releaseSource = `Promoted ${packageObject.originalLabel} from "${packageObject.originalDeployment}"`;
|
|
590
|
+
}
|
|
591
|
+
else if (packageObject.releaseMethod === "Rollback") {
|
|
592
|
+
const labelNumber = parseInt(packageObject.label.substring(1));
|
|
593
|
+
const lastLabel = "v" + (labelNumber - 1);
|
|
594
|
+
releaseSource = `Rolled back ${lastLabel} to ${packageObject.originalLabel}`;
|
|
595
|
+
}
|
|
596
|
+
if (releaseSource) {
|
|
597
|
+
releaseTime += "\n" + chalk.magenta(`(${releaseSource})`).toString();
|
|
598
|
+
}
|
|
599
|
+
let row = [packageObject.label, releaseTime, packageObject.appVersion, packageObject.isMandatory ? "Yes" : "No"];
|
|
600
|
+
if (command.displayAuthor) {
|
|
601
|
+
let releasedBy = packageObject.releasedBy ? packageObject.releasedBy : "";
|
|
602
|
+
if (currentUserEmail && releasedBy === currentUserEmail) {
|
|
603
|
+
releasedBy = "You";
|
|
604
|
+
}
|
|
605
|
+
row.push(releasedBy);
|
|
606
|
+
}
|
|
607
|
+
row.push(packageObject.description ? wordwrap(30)(packageObject.description) : "");
|
|
608
|
+
row.push(getPackageMetricsString(packageObject) + (packageObject.isDisabled ? `\n${chalk.green("Disabled:")} Yes` : ""));
|
|
609
|
+
if (packageObject.isDisabled) {
|
|
610
|
+
row = row.map((cellContents) => applyChalkSkippingLineBreaks(cellContents, chalk.dim));
|
|
611
|
+
}
|
|
612
|
+
dataSource.push(row);
|
|
613
|
+
});
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
function applyChalkSkippingLineBreaks(applyString, chalkMethod) {
|
|
618
|
+
// Used to prevent "chalk" from applying styles to linebreaks which
|
|
619
|
+
// causes table border chars to have the style applied as well.
|
|
620
|
+
return applyString
|
|
621
|
+
.split("\n")
|
|
622
|
+
.map((token) => chalkMethod(token))
|
|
623
|
+
.join("\n");
|
|
624
|
+
}
|
|
625
|
+
function getPackageString(packageObject) {
|
|
626
|
+
if (!packageObject) {
|
|
627
|
+
return chalk.magenta("No updates released").toString();
|
|
628
|
+
}
|
|
629
|
+
let packageString = chalk.green("Label: ") +
|
|
630
|
+
packageObject.label +
|
|
631
|
+
"\n" +
|
|
632
|
+
chalk.green("App Version: ") +
|
|
633
|
+
packageObject.appVersion +
|
|
634
|
+
"\n" +
|
|
635
|
+
chalk.green("Mandatory: ") +
|
|
636
|
+
(packageObject.isMandatory ? "Yes" : "No") +
|
|
637
|
+
"\n" +
|
|
638
|
+
chalk.green("Release Time: ") +
|
|
639
|
+
formatDate(packageObject.uploadTime) +
|
|
640
|
+
"\n" +
|
|
641
|
+
chalk.green("Released By: ") +
|
|
642
|
+
(packageObject.releasedBy ? packageObject.releasedBy : "") +
|
|
643
|
+
(packageObject.description ? wordwrap(70)("\n" + chalk.green("Description: ") + packageObject.description) : "");
|
|
644
|
+
if (packageObject.isDisabled) {
|
|
645
|
+
packageString += `\n${chalk.green("Disabled:")} Yes`;
|
|
646
|
+
}
|
|
647
|
+
return packageString;
|
|
648
|
+
}
|
|
649
|
+
function getPackageMetricsString(obj) {
|
|
650
|
+
const packageObject = obj;
|
|
651
|
+
const rolloutString = obj && obj.rollout && obj.rollout !== 100 ? `\n${chalk.green("Rollout:")} ${obj.rollout.toLocaleString()}%` : "";
|
|
652
|
+
if (!packageObject || !packageObject.metrics) {
|
|
653
|
+
return chalk.magenta("No installs recorded").toString() + (rolloutString || "");
|
|
654
|
+
}
|
|
655
|
+
const activePercent = packageObject.metrics.totalActive
|
|
656
|
+
? (packageObject.metrics.active / packageObject.metrics.totalActive) * 100
|
|
657
|
+
: 0.0;
|
|
658
|
+
let percentString;
|
|
659
|
+
if (activePercent === 100.0) {
|
|
660
|
+
percentString = "100%";
|
|
661
|
+
}
|
|
662
|
+
else if (activePercent === 0.0) {
|
|
663
|
+
percentString = "0%";
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
percentString = activePercent.toPrecision(2) + "%";
|
|
667
|
+
}
|
|
668
|
+
const numPending = packageObject.metrics.downloaded - packageObject.metrics.installed - packageObject.metrics.failed;
|
|
669
|
+
let returnString = chalk.green("Active: ") +
|
|
670
|
+
percentString +
|
|
671
|
+
" (" +
|
|
672
|
+
packageObject.metrics.active.toLocaleString() +
|
|
673
|
+
" of " +
|
|
674
|
+
packageObject.metrics.totalActive.toLocaleString() +
|
|
675
|
+
")\n" +
|
|
676
|
+
chalk.green("Total: ") +
|
|
677
|
+
packageObject.metrics.installed.toLocaleString();
|
|
678
|
+
if (numPending > 0) {
|
|
679
|
+
returnString += " (" + numPending.toLocaleString() + " pending)";
|
|
680
|
+
}
|
|
681
|
+
if (packageObject.metrics.failed) {
|
|
682
|
+
returnString += "\n" + chalk.green("Rollbacks: ") + chalk.red(packageObject.metrics.failed.toLocaleString() + "");
|
|
683
|
+
}
|
|
684
|
+
if (rolloutString) {
|
|
685
|
+
returnString += rolloutString;
|
|
686
|
+
}
|
|
687
|
+
return returnString;
|
|
688
|
+
}
|
|
689
|
+
function getReactNativeProjectAppVersion(command, projectName) {
|
|
690
|
+
(0, exports.log)(chalk.cyan(`Detecting ${command.platform} app version:\n`));
|
|
691
|
+
if (command.platform === "ios") {
|
|
692
|
+
let resolvedPlistFile = command.plistFile;
|
|
693
|
+
if (resolvedPlistFile) {
|
|
694
|
+
// If a plist file path is explicitly provided, then we don't
|
|
695
|
+
// need to attempt to "resolve" it within the well-known locations.
|
|
696
|
+
if (!(0, file_utils_1.fileExists)(resolvedPlistFile)) {
|
|
697
|
+
throw new Error("The specified plist file doesn't exist. Please check that the provided path is correct.");
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
// Allow the plist prefix to be specified with or without a trailing
|
|
702
|
+
// separator character, but prescribe the use of a hyphen when omitted,
|
|
703
|
+
// since this is the most commonly used convetion for plist files.
|
|
704
|
+
if (command.plistFilePrefix && /.+[^-.]$/.test(command.plistFilePrefix)) {
|
|
705
|
+
command.plistFilePrefix += "-";
|
|
706
|
+
}
|
|
707
|
+
const iOSDirectory = "ios";
|
|
708
|
+
const plistFileName = `${command.plistFilePrefix || ""}Info.plist`;
|
|
709
|
+
const knownLocations = [path.join(iOSDirectory, projectName, plistFileName), path.join(iOSDirectory, plistFileName)];
|
|
710
|
+
resolvedPlistFile = knownLocations.find(file_utils_1.fileExists);
|
|
711
|
+
if (!resolvedPlistFile) {
|
|
712
|
+
throw new Error(`Unable to find either of the following plist files in order to infer your app's binary version: "${knownLocations.join('", "')}". If your plist has a different name, or is located in a different directory, consider using either the "--plistFile" or "--plistFilePrefix" parameters to help inform the CLI how to find it.`);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
const plistContents = fs.readFileSync(resolvedPlistFile).toString();
|
|
716
|
+
let parsedPlist;
|
|
717
|
+
try {
|
|
718
|
+
parsedPlist = plist.parse(plistContents);
|
|
719
|
+
}
|
|
720
|
+
catch (e) {
|
|
721
|
+
throw new Error(`Unable to parse "${resolvedPlistFile}". Please ensure it is a well-formed plist file.`);
|
|
722
|
+
}
|
|
723
|
+
if (parsedPlist && parsedPlist.CFBundleShortVersionString) {
|
|
724
|
+
if ((0, react_native_utils_1.isValidVersion)(parsedPlist.CFBundleShortVersionString)) {
|
|
725
|
+
(0, exports.log)(`Using the target binary version value "${parsedPlist.CFBundleShortVersionString}" from "${resolvedPlistFile}".\n`);
|
|
726
|
+
return Q(parsedPlist.CFBundleShortVersionString);
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
if (parsedPlist.CFBundleShortVersionString !== "$(MARKETING_VERSION)") {
|
|
730
|
+
throw new Error(`The "CFBundleShortVersionString" key in the "${resolvedPlistFile}" file needs to specify a valid semver string, containing both a major and minor version (e.g. 1.3.2, 1.1).`);
|
|
731
|
+
}
|
|
732
|
+
return getAppVersionFromXcodeProject(command, projectName);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
throw new Error(`The "CFBundleShortVersionString" key doesn't exist within the "${resolvedPlistFile}" file.`);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
else if (command.platform === "android") {
|
|
740
|
+
let buildGradlePath = path.join("android", "app");
|
|
741
|
+
if (command.gradleFile) {
|
|
742
|
+
buildGradlePath = command.gradleFile;
|
|
743
|
+
}
|
|
744
|
+
if (fs.lstatSync(buildGradlePath).isDirectory()) {
|
|
745
|
+
buildGradlePath = path.join(buildGradlePath, "build.gradle");
|
|
746
|
+
}
|
|
747
|
+
if ((0, file_utils_1.fileDoesNotExistOrIsDirectory)(buildGradlePath)) {
|
|
748
|
+
throw new Error(`Unable to find gradle file "${buildGradlePath}".`);
|
|
749
|
+
}
|
|
750
|
+
return g2js
|
|
751
|
+
.parseFile(buildGradlePath)
|
|
752
|
+
.catch(() => {
|
|
753
|
+
throw new Error(`Unable to parse the "${buildGradlePath}" file. Please ensure it is a well-formed Gradle file.`);
|
|
754
|
+
})
|
|
755
|
+
.then((buildGradle) => {
|
|
756
|
+
let versionName = null;
|
|
757
|
+
// First 'if' statement was implemented as workaround for case
|
|
758
|
+
// when 'build.gradle' file contains several 'android' nodes.
|
|
759
|
+
// In this case 'buildGradle.android' prop represents array instead of object
|
|
760
|
+
// due to parsing issue in 'g2js.parseFile' method.
|
|
761
|
+
if (buildGradle.android instanceof Array) {
|
|
762
|
+
for (let i = 0; i < buildGradle.android.length; i++) {
|
|
763
|
+
const gradlePart = buildGradle.android[i];
|
|
764
|
+
if (gradlePart.defaultConfig && gradlePart.defaultConfig.versionName) {
|
|
765
|
+
versionName = gradlePart.defaultConfig.versionName;
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
else if (buildGradle.android && buildGradle.android.defaultConfig && buildGradle.android.defaultConfig.versionName) {
|
|
771
|
+
versionName = buildGradle.android.defaultConfig.versionName;
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
throw new Error(`The "${buildGradlePath}" file doesn't specify a value for the "android.defaultConfig.versionName" property.`);
|
|
775
|
+
}
|
|
776
|
+
if (typeof versionName !== "string") {
|
|
777
|
+
throw new Error(`The "android.defaultConfig.versionName" property value in "${buildGradlePath}" is not a valid string. If this is expected, consider using the --targetBinaryVersion option to specify the value manually.`);
|
|
778
|
+
}
|
|
779
|
+
let appVersion = versionName.replace(/"/g, "").trim();
|
|
780
|
+
if ((0, react_native_utils_1.isValidVersion)(appVersion)) {
|
|
781
|
+
// The versionName property is a valid semver string,
|
|
782
|
+
// so we can safely use that and move on.
|
|
783
|
+
(0, exports.log)(`Using the target binary version value "${appVersion}" from "${buildGradlePath}".\n`);
|
|
784
|
+
return appVersion;
|
|
785
|
+
}
|
|
786
|
+
else if (/^\d.*/.test(appVersion)) {
|
|
787
|
+
// The versionName property isn't a valid semver string,
|
|
788
|
+
// but it starts with a number, and therefore, it can't
|
|
789
|
+
// be a valid Gradle property reference.
|
|
790
|
+
throw new Error(`The "android.defaultConfig.versionName" property in the "${buildGradlePath}" file needs to specify a valid semver string, containing both a major and minor version (e.g. 1.3.2, 1.1).`);
|
|
791
|
+
}
|
|
792
|
+
// The version property isn't a valid semver string
|
|
793
|
+
// so we assume it is a reference to a property variable.
|
|
794
|
+
const propertyName = appVersion.replace("project.", "");
|
|
795
|
+
const propertiesFileName = "gradle.properties";
|
|
796
|
+
const knownLocations = [path.join("android", "app", propertiesFileName), path.join("android", propertiesFileName)];
|
|
797
|
+
// Search for gradle properties across all `gradle.properties` files
|
|
798
|
+
let propertiesFile = null;
|
|
799
|
+
for (let i = 0; i < knownLocations.length; i++) {
|
|
800
|
+
propertiesFile = knownLocations[i];
|
|
801
|
+
if ((0, file_utils_1.fileExists)(propertiesFile)) {
|
|
802
|
+
const propertiesContent = fs.readFileSync(propertiesFile).toString();
|
|
803
|
+
try {
|
|
804
|
+
const parsedProperties = properties.parse(propertiesContent);
|
|
805
|
+
appVersion = parsedProperties[propertyName];
|
|
806
|
+
if (appVersion) {
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
catch (e) {
|
|
811
|
+
throw new Error(`Unable to parse "${propertiesFile}". Please ensure it is a well-formed properties file.`);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
if (!appVersion) {
|
|
816
|
+
throw new Error(`No property named "${propertyName}" exists in the "${propertiesFile}" file.`);
|
|
817
|
+
}
|
|
818
|
+
if (!(0, react_native_utils_1.isValidVersion)(appVersion)) {
|
|
819
|
+
throw new Error(`The "${propertyName}" property in the "${propertiesFile}" file needs to specify a valid semver string, containing both a major and minor version (e.g. 1.3.2, 1.1).`);
|
|
820
|
+
}
|
|
821
|
+
(0, exports.log)(`Using the target binary version value "${appVersion}" from the "${propertyName}" key in the "${propertiesFile}" file.\n`);
|
|
822
|
+
return appVersion.toString();
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
const appxManifestFileName = "Package.appxmanifest";
|
|
827
|
+
let appxManifestContainingFolder;
|
|
828
|
+
let appxManifestContents;
|
|
829
|
+
try {
|
|
830
|
+
appxManifestContainingFolder = path.join("windows", projectName);
|
|
831
|
+
appxManifestContents = fs.readFileSync(path.join(appxManifestContainingFolder, "Package.appxmanifest")).toString();
|
|
832
|
+
}
|
|
833
|
+
catch (err) {
|
|
834
|
+
throw new Error(`Unable to find or read "${appxManifestFileName}" in the "${path.join("windows", projectName)}" folder.`);
|
|
835
|
+
}
|
|
836
|
+
return parseXml(appxManifestContents)
|
|
837
|
+
.catch((err) => {
|
|
838
|
+
throw new Error(`Unable to parse the "${path.join(appxManifestContainingFolder, appxManifestFileName)}" file, it could be malformed.`);
|
|
839
|
+
})
|
|
840
|
+
.then((parsedAppxManifest) => {
|
|
841
|
+
try {
|
|
842
|
+
return parsedAppxManifest.Package.Identity[0]["$"].Version.match(/^\d+\.\d+\.\d+/)[0];
|
|
843
|
+
}
|
|
844
|
+
catch (e) {
|
|
845
|
+
throw new Error(`Unable to parse the package version from the "${path.join(appxManifestContainingFolder, appxManifestFileName)}" file.`);
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
function getAppVersionFromXcodeProject(command, projectName) {
|
|
851
|
+
const pbxprojFileName = "project.pbxproj";
|
|
852
|
+
let resolvedPbxprojFile = command.xcodeProjectFile;
|
|
853
|
+
if (resolvedPbxprojFile) {
|
|
854
|
+
// If the xcode project file path is explicitly provided, then we don't
|
|
855
|
+
// need to attempt to "resolve" it within the well-known locations.
|
|
856
|
+
if (!resolvedPbxprojFile.endsWith(pbxprojFileName)) {
|
|
857
|
+
// Specify path to pbxproj file if the provided file path is an Xcode project file.
|
|
858
|
+
resolvedPbxprojFile = path.join(resolvedPbxprojFile, pbxprojFileName);
|
|
859
|
+
}
|
|
860
|
+
if (!(0, file_utils_1.fileExists)(resolvedPbxprojFile)) {
|
|
861
|
+
throw new Error("The specified pbx project file doesn't exist. Please check that the provided path is correct.");
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
else {
|
|
865
|
+
const iOSDirectory = "ios";
|
|
866
|
+
const xcodeprojDirectory = `${projectName}.xcodeproj`;
|
|
867
|
+
const pbxprojKnownLocations = [
|
|
868
|
+
path.join(iOSDirectory, xcodeprojDirectory, pbxprojFileName),
|
|
869
|
+
path.join(iOSDirectory, pbxprojFileName),
|
|
870
|
+
];
|
|
871
|
+
resolvedPbxprojFile = pbxprojKnownLocations.find(file_utils_1.fileExists);
|
|
872
|
+
if (!resolvedPbxprojFile) {
|
|
873
|
+
throw new Error(`Unable to find either of the following pbxproj files in order to infer your app's binary version: "${pbxprojKnownLocations.join('", "')}".`);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
const xcodeProj = xcode.project(resolvedPbxprojFile).parseSync();
|
|
877
|
+
const marketingVersion = xcodeProj.getBuildProperty("MARKETING_VERSION", command.buildConfigurationName, command.xcodeTargetName);
|
|
878
|
+
if (!(0, react_native_utils_1.isValidVersion)(marketingVersion)) {
|
|
879
|
+
throw new Error(`The "MARKETING_VERSION" key in the "${resolvedPbxprojFile}" file needs to specify a valid semver string, containing both a major and minor version (e.g. 1.3.2, 1.1).`);
|
|
880
|
+
}
|
|
881
|
+
console.log(`Using the target binary version value "${marketingVersion}" from "${resolvedPbxprojFile}".\n`);
|
|
882
|
+
return marketingVersion;
|
|
883
|
+
}
|
|
884
|
+
function printJson(object) {
|
|
885
|
+
(0, exports.log)(JSON.stringify(object, /*replacer=*/ null, /*spacing=*/ 2));
|
|
886
|
+
}
|
|
887
|
+
function printAccessKeys(format, keys) {
|
|
888
|
+
if (format === "json") {
|
|
889
|
+
printJson(keys);
|
|
890
|
+
}
|
|
891
|
+
else if (format === "table") {
|
|
892
|
+
printTable(["Name", "Created", "Expires"], (dataSource) => {
|
|
893
|
+
const now = new Date().getTime();
|
|
894
|
+
function isExpired(key) {
|
|
895
|
+
return now >= key.expires;
|
|
896
|
+
}
|
|
897
|
+
function keyToTableRow(key, dim) {
|
|
898
|
+
const row = [key.name, key.createdTime ? formatDate(key.createdTime) : "", formatDate(key.expires)];
|
|
899
|
+
if (dim) {
|
|
900
|
+
row.forEach((col, index) => {
|
|
901
|
+
row[index] = chalk.dim(col);
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
return row;
|
|
905
|
+
}
|
|
906
|
+
keys.forEach((key) => !isExpired(key) && dataSource.push(keyToTableRow(key, /*dim*/ false)));
|
|
907
|
+
keys.forEach((key) => isExpired(key) && dataSource.push(keyToTableRow(key, /*dim*/ true)));
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
function printSessions(format, sessions) {
|
|
912
|
+
if (format === "json") {
|
|
913
|
+
printJson(sessions);
|
|
914
|
+
}
|
|
915
|
+
else if (format === "table") {
|
|
916
|
+
printTable(["Machine", "Logged in"], (dataSource) => {
|
|
917
|
+
sessions.forEach((session) => dataSource.push([session.machineName, formatDate(session.loggedInTime)]));
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
function printTable(columnNames, readData) {
|
|
922
|
+
const table = new Table({
|
|
923
|
+
head: columnNames,
|
|
924
|
+
style: { head: ["cyan"] },
|
|
925
|
+
});
|
|
926
|
+
readData(table);
|
|
927
|
+
(0, exports.log)(table.toString());
|
|
928
|
+
}
|
|
929
|
+
function register(command) {
|
|
930
|
+
return loginWithExternalAuthentication("register", command.serverUrl);
|
|
931
|
+
}
|
|
932
|
+
function promote(command) {
|
|
933
|
+
const packageInfo = {
|
|
934
|
+
appVersion: command.appStoreVersion,
|
|
935
|
+
description: command.description,
|
|
936
|
+
label: command.label,
|
|
937
|
+
isDisabled: command.disabled,
|
|
938
|
+
isMandatory: command.mandatory,
|
|
939
|
+
rollout: command.rollout,
|
|
940
|
+
};
|
|
941
|
+
return exports.sdk
|
|
942
|
+
.promote(command.appName, command.sourceDeploymentName, command.destDeploymentName, packageInfo)
|
|
943
|
+
.then(() => {
|
|
944
|
+
(0, exports.log)("Successfully promoted " +
|
|
945
|
+
(command.label !== null ? '"' + command.label + '" of ' : "") +
|
|
946
|
+
'the "' +
|
|
947
|
+
command.sourceDeploymentName +
|
|
948
|
+
'" deployment of the "' +
|
|
949
|
+
command.appName +
|
|
950
|
+
'" app to the "' +
|
|
951
|
+
command.destDeploymentName +
|
|
952
|
+
'" deployment.');
|
|
953
|
+
})
|
|
954
|
+
.catch((err) => releaseErrorHandler(err, command));
|
|
955
|
+
}
|
|
956
|
+
function patch(command) {
|
|
957
|
+
const packageInfo = {
|
|
958
|
+
appVersion: command.appStoreVersion,
|
|
959
|
+
description: command.description,
|
|
960
|
+
isMandatory: command.mandatory,
|
|
961
|
+
isDisabled: command.disabled,
|
|
962
|
+
rollout: command.rollout,
|
|
963
|
+
};
|
|
964
|
+
for (const updateProperty in packageInfo) {
|
|
965
|
+
if (packageInfo[updateProperty] !== null) {
|
|
966
|
+
return exports.sdk.patchRelease(command.appName, command.deploymentName, command.label, packageInfo).then(() => {
|
|
967
|
+
(0, exports.log)(`Successfully updated the "${command.label ? command.label : `latest`}" release of "${command.appName}" app's "${command.deploymentName}" deployment.`);
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
throw new Error("At least one property must be specified to patch a release.");
|
|
972
|
+
}
|
|
973
|
+
const release = (command) => {
|
|
974
|
+
if ((0, file_utils_1.isBinaryOrZip)(command.package)) {
|
|
975
|
+
throw new Error("It is unnecessary to package releases in a .zip or binary file. Please specify the direct path to the update content's directory (e.g. /platforms/ios/www) or file (e.g. main.jsbundle).");
|
|
976
|
+
}
|
|
977
|
+
throwForInvalidSemverRange(command.appStoreVersion);
|
|
978
|
+
const filePath = command.package;
|
|
979
|
+
let isSingleFilePackage = true;
|
|
980
|
+
if (fs.lstatSync(filePath).isDirectory()) {
|
|
981
|
+
isSingleFilePackage = false;
|
|
982
|
+
}
|
|
983
|
+
let lastTotalProgress = 0;
|
|
984
|
+
const progressBar = new progress("Upload progress:[:bar] :percent :etas", {
|
|
985
|
+
complete: "=",
|
|
986
|
+
incomplete: " ",
|
|
987
|
+
width: 50,
|
|
988
|
+
total: 100,
|
|
989
|
+
});
|
|
990
|
+
const uploadProgress = (currentProgress) => {
|
|
991
|
+
progressBar.tick(currentProgress - lastTotalProgress);
|
|
992
|
+
lastTotalProgress = currentProgress;
|
|
993
|
+
};
|
|
994
|
+
const updateMetadata = {
|
|
995
|
+
description: command.description,
|
|
996
|
+
isDisabled: command.disabled,
|
|
997
|
+
isMandatory: command.mandatory,
|
|
998
|
+
rollout: command.rollout,
|
|
999
|
+
};
|
|
1000
|
+
return exports.sdk
|
|
1001
|
+
.isAuthenticated(true)
|
|
1002
|
+
.then((isAuth) => {
|
|
1003
|
+
return exports.sdk.release(command.appName, command.deploymentName, filePath, command.appStoreVersion, updateMetadata, uploadProgress);
|
|
1004
|
+
})
|
|
1005
|
+
.then(() => {
|
|
1006
|
+
(0, exports.log)('Successfully released an update containing the "' +
|
|
1007
|
+
command.package +
|
|
1008
|
+
'" ' +
|
|
1009
|
+
(isSingleFilePackage ? "file" : "directory") +
|
|
1010
|
+
' to the "' +
|
|
1011
|
+
command.deploymentName +
|
|
1012
|
+
'" deployment of the "' +
|
|
1013
|
+
command.appName +
|
|
1014
|
+
'" app.');
|
|
1015
|
+
})
|
|
1016
|
+
.catch((err) => releaseErrorHandler(err, command));
|
|
1017
|
+
};
|
|
1018
|
+
exports.release = release;
|
|
1019
|
+
const releaseReact = (command) => {
|
|
1020
|
+
let bundleName = command.bundleName;
|
|
1021
|
+
let entryFile = command.entryFile;
|
|
1022
|
+
const outputFolder = command.outputDir || path.join(os.tmpdir(), "CodePush");
|
|
1023
|
+
const platform = (command.platform = command.platform.toLowerCase());
|
|
1024
|
+
const releaseCommand = command;
|
|
1025
|
+
// Check for app and deployment exist before releasing an update.
|
|
1026
|
+
// This validation helps to save about 1 minute or more in case user has typed wrong app or deployment name.
|
|
1027
|
+
return (exports.sdk
|
|
1028
|
+
.getDeployment(command.appName, command.deploymentName)
|
|
1029
|
+
.then(() => {
|
|
1030
|
+
releaseCommand.package = outputFolder;
|
|
1031
|
+
switch (platform) {
|
|
1032
|
+
case "android":
|
|
1033
|
+
case "ios":
|
|
1034
|
+
case "windows":
|
|
1035
|
+
if (!bundleName) {
|
|
1036
|
+
bundleName = platform === "ios" ? "main.jsbundle" : `index.${platform}.bundle`;
|
|
1037
|
+
}
|
|
1038
|
+
break;
|
|
1039
|
+
default:
|
|
1040
|
+
throw new Error('Platform must be either "android", "ios" or "windows".');
|
|
1041
|
+
}
|
|
1042
|
+
let projectName;
|
|
1043
|
+
try {
|
|
1044
|
+
const projectPackageJson = require(path.join(process.cwd(), "package.json"));
|
|
1045
|
+
projectName = projectPackageJson.name;
|
|
1046
|
+
if (!projectName) {
|
|
1047
|
+
throw new Error('The "package.json" file in the CWD does not have the "name" field set.');
|
|
1048
|
+
}
|
|
1049
|
+
if (!projectPackageJson.dependencies["react-native"]) {
|
|
1050
|
+
throw new Error("The project in the CWD is not a React Native project.");
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
catch (error) {
|
|
1054
|
+
throw new Error('Unable to find or read "package.json" in the CWD. The "release-react" command must be executed in a React Native project folder.');
|
|
1055
|
+
}
|
|
1056
|
+
if (!entryFile) {
|
|
1057
|
+
entryFile = `index.${platform}.js`;
|
|
1058
|
+
if ((0, file_utils_1.fileDoesNotExistOrIsDirectory)(entryFile)) {
|
|
1059
|
+
entryFile = "index.js";
|
|
1060
|
+
}
|
|
1061
|
+
if ((0, file_utils_1.fileDoesNotExistOrIsDirectory)(entryFile)) {
|
|
1062
|
+
throw new Error(`Entry file "index.${platform}.js" or "index.js" does not exist.`);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
else {
|
|
1066
|
+
if ((0, file_utils_1.fileDoesNotExistOrIsDirectory)(entryFile)) {
|
|
1067
|
+
throw new Error(`Entry file "${entryFile}" does not exist.`);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
const appVersionPromise = command.appStoreVersion
|
|
1071
|
+
? Q(command.appStoreVersion)
|
|
1072
|
+
: getReactNativeProjectAppVersion(command, projectName);
|
|
1073
|
+
if (command.sourcemapOutput && !command.sourcemapOutput.endsWith(".map")) {
|
|
1074
|
+
command.sourcemapOutput = path.join(command.sourcemapOutput, bundleName + ".map");
|
|
1075
|
+
}
|
|
1076
|
+
return appVersionPromise;
|
|
1077
|
+
})
|
|
1078
|
+
.then((appVersion) => {
|
|
1079
|
+
throwForInvalidSemverRange(appVersion);
|
|
1080
|
+
releaseCommand.appStoreVersion = appVersion;
|
|
1081
|
+
return (0, exports.createEmptyTempReleaseFolder)(outputFolder);
|
|
1082
|
+
})
|
|
1083
|
+
// This is needed to clear the react native bundler cache:
|
|
1084
|
+
// https://github.com/facebook/react-native/issues/4289
|
|
1085
|
+
.then(() => deleteFolder(`${os.tmpdir()}/react-*`))
|
|
1086
|
+
.then(() => (0, exports.runReactNativeBundleCommand)(bundleName, command.development || false, entryFile, outputFolder, platform, command.sourcemapOutput))
|
|
1087
|
+
.then(async () => {
|
|
1088
|
+
const isHermesEnabled = command.useHermes ||
|
|
1089
|
+
(platform === "android" && (await (0, react_native_utils_1.getAndroidHermesEnabled)(command.gradleFile))) || // Check if we have to run hermes to compile JS to Byte Code if Hermes is enabled in build.gradle and we're releasing an Android build
|
|
1090
|
+
(platform === "ios" && (await (0, react_native_utils_1.getiOSHermesEnabled)(command.podFile))); // Check if we have to run hermes to compile JS to Byte Code if Hermes is enabled in Podfile and we're releasing an iOS build
|
|
1091
|
+
if (isHermesEnabled) {
|
|
1092
|
+
(0, exports.log)(chalk.cyan("\nRunning hermes compiler...\n"));
|
|
1093
|
+
await (0, react_native_utils_1.runHermesEmitBinaryCommand)(bundleName, outputFolder, command.sourcemapOutput, command.extraHermesFlags, command.gradleFile);
|
|
1094
|
+
}
|
|
1095
|
+
})
|
|
1096
|
+
.then(async () => {
|
|
1097
|
+
if (command.privateKeyPath) {
|
|
1098
|
+
(0, exports.log)(chalk.cyan("\nSigning the bundle:\n"));
|
|
1099
|
+
await (0, sign_1.default)(command.privateKeyPath, outputFolder);
|
|
1100
|
+
}
|
|
1101
|
+
else {
|
|
1102
|
+
console.log("private key was not provided");
|
|
1103
|
+
}
|
|
1104
|
+
})
|
|
1105
|
+
.then(() => {
|
|
1106
|
+
(0, exports.log)(chalk.cyan("\nReleasing update contents to CodePush:\n"));
|
|
1107
|
+
return (0, exports.release)(releaseCommand);
|
|
1108
|
+
})
|
|
1109
|
+
.then(() => {
|
|
1110
|
+
if (!command.outputDir) {
|
|
1111
|
+
deleteFolder(outputFolder);
|
|
1112
|
+
}
|
|
1113
|
+
})
|
|
1114
|
+
.catch((err) => {
|
|
1115
|
+
deleteFolder(outputFolder);
|
|
1116
|
+
throw err;
|
|
1117
|
+
}));
|
|
1118
|
+
};
|
|
1119
|
+
exports.releaseReact = releaseReact;
|
|
1120
|
+
function rollback(command) {
|
|
1121
|
+
return (0, exports.confirm)().then((wasConfirmed) => {
|
|
1122
|
+
if (!wasConfirmed) {
|
|
1123
|
+
(0, exports.log)("Rollback cancelled.");
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
return exports.sdk.rollback(command.appName, command.deploymentName, command.targetRelease || undefined).then(() => {
|
|
1127
|
+
(0, exports.log)('Successfully performed a rollback on the "' + command.deploymentName + '" deployment of the "' + command.appName + '" app.');
|
|
1128
|
+
});
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
function requestAccessKey() {
|
|
1132
|
+
return Promise((resolve, reject, notify) => {
|
|
1133
|
+
prompt.message = "";
|
|
1134
|
+
prompt.delimiter = "";
|
|
1135
|
+
prompt.start();
|
|
1136
|
+
prompt.get({
|
|
1137
|
+
properties: {
|
|
1138
|
+
response: {
|
|
1139
|
+
description: chalk.cyan("Enter your access key: "),
|
|
1140
|
+
},
|
|
1141
|
+
},
|
|
1142
|
+
}, (err, result) => {
|
|
1143
|
+
if (err) {
|
|
1144
|
+
resolve(null);
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
resolve(result.response.trim());
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
const runReactNativeBundleCommand = (bundleName, development, entryFile, outputFolder, platform, sourcemapOutput) => {
|
|
1153
|
+
const reactNativeBundleArgs = [];
|
|
1154
|
+
const envNodeArgs = process.env.CODE_PUSH_NODE_ARGS;
|
|
1155
|
+
if (typeof envNodeArgs !== "undefined") {
|
|
1156
|
+
Array.prototype.push.apply(reactNativeBundleArgs, envNodeArgs.trim().split(/\s+/));
|
|
1157
|
+
}
|
|
1158
|
+
const isOldCLI = fs.existsSync(path.join("node_modules", "react-native", "local-cli", "cli.js"));
|
|
1159
|
+
Array.prototype.push.apply(reactNativeBundleArgs, [
|
|
1160
|
+
isOldCLI ? path.join("node_modules", "react-native", "local-cli", "cli.js") : path.join("node_modules", "react-native", "cli.js"),
|
|
1161
|
+
"bundle",
|
|
1162
|
+
"--assets-dest",
|
|
1163
|
+
outputFolder,
|
|
1164
|
+
"--bundle-output",
|
|
1165
|
+
path.join(outputFolder, bundleName),
|
|
1166
|
+
"--dev",
|
|
1167
|
+
development,
|
|
1168
|
+
"--entry-file",
|
|
1169
|
+
entryFile,
|
|
1170
|
+
"--platform",
|
|
1171
|
+
platform,
|
|
1172
|
+
]);
|
|
1173
|
+
if (sourcemapOutput) {
|
|
1174
|
+
reactNativeBundleArgs.push("--sourcemap-output", sourcemapOutput);
|
|
1175
|
+
}
|
|
1176
|
+
(0, exports.log)(chalk.cyan('Running "react-native bundle" command:\n'));
|
|
1177
|
+
const reactNativeBundleProcess = (0, exports.spawn)("node", reactNativeBundleArgs);
|
|
1178
|
+
(0, exports.log)(`node ${reactNativeBundleArgs.join(" ")}`);
|
|
1179
|
+
return Promise((resolve, reject, notify) => {
|
|
1180
|
+
reactNativeBundleProcess.stdout.on("data", (data) => {
|
|
1181
|
+
(0, exports.log)(data.toString().trim());
|
|
1182
|
+
});
|
|
1183
|
+
reactNativeBundleProcess.stderr.on("data", (data) => {
|
|
1184
|
+
console.error(data.toString().trim());
|
|
1185
|
+
});
|
|
1186
|
+
reactNativeBundleProcess.on("close", (exitCode) => {
|
|
1187
|
+
if (exitCode) {
|
|
1188
|
+
reject(new Error(`"react-native bundle" command exited with code ${exitCode}.`));
|
|
1189
|
+
}
|
|
1190
|
+
resolve(null);
|
|
1191
|
+
});
|
|
1192
|
+
});
|
|
1193
|
+
};
|
|
1194
|
+
exports.runReactNativeBundleCommand = runReactNativeBundleCommand;
|
|
1195
|
+
function serializeConnectionInfo(accessKey, preserveAccessKeyOnLogout, customServerUrl) {
|
|
1196
|
+
const connectionInfo = {
|
|
1197
|
+
accessKey: accessKey,
|
|
1198
|
+
preserveAccessKeyOnLogout: preserveAccessKeyOnLogout,
|
|
1199
|
+
};
|
|
1200
|
+
if (customServerUrl) {
|
|
1201
|
+
connectionInfo.customServerUrl = customServerUrl;
|
|
1202
|
+
}
|
|
1203
|
+
const json = JSON.stringify(connectionInfo);
|
|
1204
|
+
fs.writeFileSync(configFilePath, json, { encoding: "utf8" });
|
|
1205
|
+
(0, exports.log)(`\r\nSuccessfully logged-in. Your session file was written to ${chalk.cyan(configFilePath)}. You can run the ${chalk.cyan("code-push logout")} command at any time to delete this file and terminate your session.\r\n`);
|
|
1206
|
+
}
|
|
1207
|
+
function sessionList(command) {
|
|
1208
|
+
throwForInvalidOutputFormat(command.format);
|
|
1209
|
+
return exports.sdk.getSessions().then((sessions) => {
|
|
1210
|
+
printSessions(command.format, sessions);
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
function sessionRemove(command) {
|
|
1214
|
+
if (os.hostname() === command.machineName) {
|
|
1215
|
+
throw new Error("Cannot remove the current login session via this command. Please run 'code-push-standalone logout' instead.");
|
|
1216
|
+
}
|
|
1217
|
+
else {
|
|
1218
|
+
return (0, exports.confirm)().then((wasConfirmed) => {
|
|
1219
|
+
if (wasConfirmed) {
|
|
1220
|
+
return exports.sdk.removeSession(command.machineName).then(() => {
|
|
1221
|
+
(0, exports.log)(`Successfully removed the login session for "${command.machineName}".`);
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
(0, exports.log)("Session removal cancelled.");
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
function releaseErrorHandler(error, command) {
|
|
1229
|
+
if (command.noDuplicateReleaseError && error.statusCode === AccountManager.ERROR_CONFLICT) {
|
|
1230
|
+
console.warn(chalk.yellow("[Warning] " + error.message));
|
|
1231
|
+
}
|
|
1232
|
+
else {
|
|
1233
|
+
throw error;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
function throwForInvalidEmail(email) {
|
|
1237
|
+
if (!emailValidator.validate(email)) {
|
|
1238
|
+
throw new Error('"' + email + '" is an invalid e-mail address.');
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
function throwForInvalidSemverRange(semverRange) {
|
|
1242
|
+
if (semver.validRange(semverRange) === null) {
|
|
1243
|
+
throw new Error('Please use a semver-compliant target binary version range, for example "1.0.0", "*" or "^1.2.3".');
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
function throwForInvalidOutputFormat(format) {
|
|
1247
|
+
switch (format) {
|
|
1248
|
+
case "json":
|
|
1249
|
+
case "table":
|
|
1250
|
+
break;
|
|
1251
|
+
default:
|
|
1252
|
+
throw new Error("Invalid format: " + format + ".");
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
function whoami(command) {
|
|
1256
|
+
return exports.sdk.getAccountInfo().then((account) => {
|
|
1257
|
+
const accountInfo = `${account.email} (${account.linkedProviders.join(", ")})`;
|
|
1258
|
+
(0, exports.log)(accountInfo);
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
function isCommandOptionSpecified(option) {
|
|
1262
|
+
return option !== undefined && option !== null;
|
|
1263
|
+
}
|
|
1264
|
+
function getSdk(accessKey, headers, customServerUrl) {
|
|
1265
|
+
const sdk = new AccountManager(accessKey, CLI_HEADERS, customServerUrl);
|
|
1266
|
+
/*
|
|
1267
|
+
* If the server returns `Unauthorized`, it must be due to an invalid
|
|
1268
|
+
* (or expired) access key. For convenience, we patch every SDK call
|
|
1269
|
+
* to delete the cached connection so the user can simply
|
|
1270
|
+
* login again instead of having to log out first.
|
|
1271
|
+
*/
|
|
1272
|
+
Object.getOwnPropertyNames(AccountManager.prototype).forEach((functionName) => {
|
|
1273
|
+
if (typeof sdk[functionName] === "function") {
|
|
1274
|
+
const originalFunction = sdk[functionName];
|
|
1275
|
+
sdk[functionName] = function () {
|
|
1276
|
+
let maybePromise = originalFunction.apply(sdk, arguments);
|
|
1277
|
+
if (maybePromise && maybePromise.then !== undefined) {
|
|
1278
|
+
maybePromise = maybePromise.catch((error) => {
|
|
1279
|
+
if (error.statusCode && error.statusCode === AccountManager.ERROR_UNAUTHORIZED) {
|
|
1280
|
+
deleteConnectionInfoCache(/* printMessage */ false);
|
|
1281
|
+
}
|
|
1282
|
+
throw error;
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
return maybePromise;
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
return sdk;
|
|
1290
|
+
}
|