@revopush/code-push-cli 0.0.8-rc.0 → 0.0.8-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/script/binary-utils.js +176 -0
- package/bin/script/command-executor.js +305 -2
- package/bin/script/command-parser.js +249 -0
- package/bin/script/expo-utils.js +13 -0
- package/bin/script/management-sdk.js +45 -0
- package/bin/script/react-native-utils.js +3 -1
- package/bin/script/types/cli.js +2 -0
- package/bin/script/utils/file-utils.js +9 -3
- package/package.json +5 -2
- package/script/binary-utils.ts +209 -0
- package/script/command-executor.ts +373 -2
- package/script/command-parser.ts +279 -0
- package/script/expo-utils.ts +14 -0
- package/script/management-sdk.ts +57 -0
- package/script/react-native-utils.ts +4 -2
- package/script/types/cli.ts +10 -0
- package/script/utils/file-utils.ts +7 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
|
|
4
|
+
import { extractMetadataFromAndroid, extractMetadataFromIOS, getIosVersion } from "./binary-utils";
|
|
5
|
+
|
|
4
6
|
const childProcess = require("child_process");
|
|
5
7
|
import debugCommand from "./commands/debug";
|
|
6
8
|
import * as fs from "fs";
|
|
@@ -12,6 +14,7 @@ import * as Q from "q";
|
|
|
12
14
|
import * as semver from "semver";
|
|
13
15
|
import * as cli from "../script/types/cli";
|
|
14
16
|
import sign from "./sign";
|
|
17
|
+
const ApkReader = require("@devicefarmer/adbkit-apkreader");
|
|
15
18
|
import {
|
|
16
19
|
AccessKey,
|
|
17
20
|
Account,
|
|
@@ -36,12 +39,13 @@ import {
|
|
|
36
39
|
runHermesEmitBinaryCommand,
|
|
37
40
|
takeHermesBaseBytecode,
|
|
38
41
|
} from "./react-native-utils";
|
|
39
|
-
import { fileDoesNotExistOrIsDirectory, fileExists, isBinaryOrZip } from "./utils/file-utils";
|
|
42
|
+
import { fileDoesNotExistOrIsDirectory, fileExists, isBinaryOrZip, extractIPA, extractAPK } from "./utils/file-utils";
|
|
40
43
|
|
|
41
44
|
import AccountManager = require("./management-sdk");
|
|
42
45
|
import wordwrap = require("wordwrap");
|
|
43
46
|
import Promise = Q.Promise;
|
|
44
47
|
import { ReactNativePackageInfo } from "./types/rest-definitions";
|
|
48
|
+
import { getExpoCliPath } from "./expo-utils";
|
|
45
49
|
|
|
46
50
|
const g2js = require("gradle-to-js/lib/parser");
|
|
47
51
|
|
|
@@ -554,6 +558,12 @@ export function execute(command: cli.ICommand) {
|
|
|
554
558
|
case cli.CommandType.releaseReact:
|
|
555
559
|
return releaseReact(<cli.IReleaseReactCommand>command);
|
|
556
560
|
|
|
561
|
+
case cli.CommandType.releaseExpo:
|
|
562
|
+
return releaseExpo(<cli.IReleaseReactCommand>command);
|
|
563
|
+
|
|
564
|
+
case cli.CommandType.releaseNative:
|
|
565
|
+
return releaseNative(<cli.IReleaseNativeCommand>command);
|
|
566
|
+
|
|
557
567
|
case cli.CommandType.rollback:
|
|
558
568
|
return rollback(<cli.IRollbackCommand>command);
|
|
559
569
|
|
|
@@ -1223,6 +1233,226 @@ export const release = (command: cli.IReleaseCommand): Promise<void> => {
|
|
|
1223
1233
|
return doRelease(command, updateMetadata);
|
|
1224
1234
|
};
|
|
1225
1235
|
|
|
1236
|
+
export const runExpoExportEmbedCommand = async (
|
|
1237
|
+
command: cli.IReleaseReactCommand,
|
|
1238
|
+
bundleName: string,
|
|
1239
|
+
development: boolean,
|
|
1240
|
+
// entryFile: string,
|
|
1241
|
+
outputFolder: string,
|
|
1242
|
+
sourcemapOutputFolder: string,
|
|
1243
|
+
platform: string,
|
|
1244
|
+
extraBundlerOptions: string[]
|
|
1245
|
+
) => {
|
|
1246
|
+
const expoBundleArgs: string[] = [];
|
|
1247
|
+
const envNodeArgs: string = process.env.CODE_PUSH_NODE_ARGS;
|
|
1248
|
+
|
|
1249
|
+
if (typeof envNodeArgs !== "undefined") {
|
|
1250
|
+
Array.prototype.push.apply(expoBundleArgs, envNodeArgs.trim().split(/\s+/));
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
const expoCliPath = getExpoCliPath();
|
|
1254
|
+
|
|
1255
|
+
Array.prototype.push.apply(expoBundleArgs, [
|
|
1256
|
+
expoCliPath,
|
|
1257
|
+
"export:embed",
|
|
1258
|
+
"--assets-dest",
|
|
1259
|
+
outputFolder,
|
|
1260
|
+
"--bundle-output",
|
|
1261
|
+
path.join(outputFolder, bundleName),
|
|
1262
|
+
"--dev",
|
|
1263
|
+
development,
|
|
1264
|
+
"--platform",
|
|
1265
|
+
platform,
|
|
1266
|
+
"--minify",
|
|
1267
|
+
false,
|
|
1268
|
+
"--reset-cache",
|
|
1269
|
+
]);
|
|
1270
|
+
|
|
1271
|
+
if (sourcemapOutputFolder) {
|
|
1272
|
+
let bundleSourceMapOutput = sourcemapOutputFolder;
|
|
1273
|
+
if (!sourcemapOutputFolder.endsWith(".map")) {
|
|
1274
|
+
// user defined directory, нужно вычислить полный путь
|
|
1275
|
+
bundleSourceMapOutput = await getBundleSourceMapOutput(command, bundleName, sourcemapOutputFolder);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
expoBundleArgs.push("--sourcemap-output", bundleSourceMapOutput);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// const minifyValue = await getMinifyParams(command);
|
|
1282
|
+
// Array.prototype.push.apply(expoBundleArgs, minifyValue);
|
|
1283
|
+
|
|
1284
|
+
if (extraBundlerOptions.length > 0) {
|
|
1285
|
+
expoBundleArgs.push(...extraBundlerOptions);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
log(chalk.cyan('Running "expo export:embed" command:\n'));
|
|
1289
|
+
|
|
1290
|
+
const projectRoot = process.cwd();
|
|
1291
|
+
expoBundleArgs.push(projectRoot);
|
|
1292
|
+
|
|
1293
|
+
log("expoBundleArgs raw:" + JSON.stringify(expoBundleArgs, null, 2));
|
|
1294
|
+
|
|
1295
|
+
const expoBundleProcess = spawn("node", expoBundleArgs);
|
|
1296
|
+
log(`node ${expoBundleArgs.join(" ")}`);
|
|
1297
|
+
|
|
1298
|
+
return Promise<void>((resolve, reject, notify) => {
|
|
1299
|
+
expoBundleProcess.stdout.on("data", (data: Buffer) => {
|
|
1300
|
+
log(data.toString().trim());
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
expoBundleProcess.stderr.on("data", (data: Buffer) => {
|
|
1304
|
+
console.error(data.toString().trim());
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
expoBundleProcess.on("close", (exitCode: number) => {
|
|
1308
|
+
if (exitCode) {
|
|
1309
|
+
reject(new Error(`"expo export:embed" command exited with code ${exitCode}.`));
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
resolve(<void>null);
|
|
1313
|
+
});
|
|
1314
|
+
});
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
export const releaseExpo = (command: cli.IReleaseReactCommand): Promise<void> => {
|
|
1318
|
+
let bundleName: string = command.bundleName;
|
|
1319
|
+
// let entryFile: string = command.entryFile;
|
|
1320
|
+
const outputFolder: string = command.outputDir || path.join(os.tmpdir(), "CodePush");
|
|
1321
|
+
const sourcemapOutputFolder: string = command.sourcemapOutput || path.join(os.tmpdir(), "CodePushSourceMap");
|
|
1322
|
+
const baseReleaseTmpFolder: string = path.join(os.tmpdir(), "CodePushBaseRelease");
|
|
1323
|
+
const platform: string = (command.platform = command.platform.toLowerCase());
|
|
1324
|
+
const releaseCommand: cli.IReleaseReactCommand = <any>command;
|
|
1325
|
+
|
|
1326
|
+
return sdk
|
|
1327
|
+
.getDeployment(command.appName, command.deploymentName)
|
|
1328
|
+
.then(async () => {
|
|
1329
|
+
switch (platform) {
|
|
1330
|
+
case "android":
|
|
1331
|
+
case "ios":
|
|
1332
|
+
if (!bundleName) {
|
|
1333
|
+
bundleName = platform === "ios" ? "main.jsbundle" : `index.${platform}.bundle`;
|
|
1334
|
+
}
|
|
1335
|
+
break;
|
|
1336
|
+
|
|
1337
|
+
default:
|
|
1338
|
+
throw new Error('Platform must be either "android" or "ios" for the "release-expo" command.');
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
releaseCommand.package = outputFolder;
|
|
1342
|
+
releaseCommand.outputDir = outputFolder;
|
|
1343
|
+
releaseCommand.bundleName = bundleName;
|
|
1344
|
+
|
|
1345
|
+
let projectName: string;
|
|
1346
|
+
|
|
1347
|
+
try {
|
|
1348
|
+
const projectPackageJson: any = require(path.join(process.cwd(), "package.json"));
|
|
1349
|
+
projectName = projectPackageJson.name;
|
|
1350
|
+
if (!projectName) {
|
|
1351
|
+
throw new Error('The "package.json" file in the CWD does not have the "name" field set.');
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
if (!projectPackageJson.dependencies["react-native"]) {
|
|
1355
|
+
throw new Error("The project in the CWD is not a React Native project.");
|
|
1356
|
+
}
|
|
1357
|
+
} catch (error) {
|
|
1358
|
+
throw new Error(
|
|
1359
|
+
'Unable to find or read "package.json" in the CWD. The "release-expo" command must be executed in a React Native project folder.'
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// TODO: do we really need entryFile for expo?
|
|
1364
|
+
|
|
1365
|
+
// if (!entryFile) {
|
|
1366
|
+
// entryFile = `index.${platform}.js`;
|
|
1367
|
+
// if (fileDoesNotExistOrIsDirectory(entryFile)) {
|
|
1368
|
+
// entryFile = "index.js";
|
|
1369
|
+
// }
|
|
1370
|
+
//
|
|
1371
|
+
// if (fileDoesNotExistOrIsDirectory(entryFile)) {
|
|
1372
|
+
// throw new Error(`Entry file "index.${platform}.js" or "index.js" does not exist.`);
|
|
1373
|
+
// }
|
|
1374
|
+
// } else {
|
|
1375
|
+
// if (fileDoesNotExistOrIsDirectory(entryFile)) {
|
|
1376
|
+
// throw new Error(`Entry file "${entryFile}" does not exist.`);
|
|
1377
|
+
// }
|
|
1378
|
+
// }
|
|
1379
|
+
|
|
1380
|
+
const appVersionPromise: Promise<string> = command.appStoreVersion
|
|
1381
|
+
? Q(command.appStoreVersion)
|
|
1382
|
+
: getReactNativeProjectAppVersion(command, projectName);
|
|
1383
|
+
|
|
1384
|
+
if (!sourcemapOutputFolder.endsWith(".map") && !command.sourcemapOutput) {
|
|
1385
|
+
await createEmptyTempReleaseFolder(sourcemapOutputFolder);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
return appVersionPromise;
|
|
1389
|
+
})
|
|
1390
|
+
.then((appVersion: string) => {
|
|
1391
|
+
throwForInvalidSemverRange(appVersion);
|
|
1392
|
+
releaseCommand.appStoreVersion = appVersion;
|
|
1393
|
+
|
|
1394
|
+
return createEmptyTempReleaseFolder(outputFolder);
|
|
1395
|
+
})
|
|
1396
|
+
.then(() => deleteFolder(`${os.tmpdir()}/react-*`))
|
|
1397
|
+
.then(async () => {
|
|
1398
|
+
await runExpoExportEmbedCommand(
|
|
1399
|
+
command,
|
|
1400
|
+
bundleName,
|
|
1401
|
+
command.development || false,
|
|
1402
|
+
// entryFile,
|
|
1403
|
+
outputFolder,
|
|
1404
|
+
sourcemapOutputFolder,
|
|
1405
|
+
platform,
|
|
1406
|
+
command.extraBundlerOptions
|
|
1407
|
+
);
|
|
1408
|
+
})
|
|
1409
|
+
.then(async () => {
|
|
1410
|
+
const isHermes = await isHermesEnabled(command, platform);
|
|
1411
|
+
|
|
1412
|
+
if (isHermes) {
|
|
1413
|
+
await createEmptyTempReleaseFolder(baseReleaseTmpFolder);
|
|
1414
|
+
const baseBytecode = await takeHermesBaseBytecode(command, baseReleaseTmpFolder, outputFolder, bundleName);
|
|
1415
|
+
|
|
1416
|
+
log(chalk.cyan("\nRunning hermes compiler.\n"));
|
|
1417
|
+
await runHermesEmitBinaryCommand(
|
|
1418
|
+
command,
|
|
1419
|
+
bundleName,
|
|
1420
|
+
outputFolder,
|
|
1421
|
+
sourcemapOutputFolder,
|
|
1422
|
+
command.extraHermesFlags,
|
|
1423
|
+
command.gradleFile,
|
|
1424
|
+
baseBytecode
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
})
|
|
1428
|
+
.then(async () => {
|
|
1429
|
+
if (command.privateKeyPath) {
|
|
1430
|
+
log(chalk.cyan("\nSigning the bundle:\n"));
|
|
1431
|
+
await sign(command.privateKeyPath, outputFolder);
|
|
1432
|
+
} else {
|
|
1433
|
+
console.log("private key was not provided");
|
|
1434
|
+
}
|
|
1435
|
+
})
|
|
1436
|
+
.then(() => {
|
|
1437
|
+
log(chalk.cyan("\nReleasing update contents to CodePush:\n"));
|
|
1438
|
+
return releaseReactNative(releaseCommand);
|
|
1439
|
+
})
|
|
1440
|
+
.then(async () => {
|
|
1441
|
+
if (!command.outputDir) {
|
|
1442
|
+
await deleteFolder(outputFolder);
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
if (!command.sourcemapOutput) {
|
|
1446
|
+
await deleteFolder(sourcemapOutputFolder);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
await deleteFolder(baseReleaseTmpFolder);
|
|
1450
|
+
})
|
|
1451
|
+
.catch(async (err: Error) => {
|
|
1452
|
+
throw err;
|
|
1453
|
+
});
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1226
1456
|
export const releaseReact = (command: cli.IReleaseReactCommand): Promise<void> => {
|
|
1227
1457
|
let bundleName: string = command.bundleName;
|
|
1228
1458
|
let entryFile: string = command.entryFile;
|
|
@@ -1272,6 +1502,7 @@ export const releaseReact = (command: cli.IReleaseReactCommand): Promise<void> =
|
|
|
1272
1502
|
);
|
|
1273
1503
|
}
|
|
1274
1504
|
|
|
1505
|
+
// TODO: check entry file detection
|
|
1275
1506
|
if (!entryFile) {
|
|
1276
1507
|
entryFile = `index.${platform}.js`;
|
|
1277
1508
|
if (fileDoesNotExistOrIsDirectory(entryFile)) {
|
|
@@ -1367,6 +1598,93 @@ export const releaseReact = (command: cli.IReleaseReactCommand): Promise<void> =
|
|
|
1367
1598
|
);
|
|
1368
1599
|
};
|
|
1369
1600
|
|
|
1601
|
+
export const releaseNative = (command: cli.IReleaseNativeCommand): Promise<void> => {
|
|
1602
|
+
const platform: string = command.platform.toLowerCase();
|
|
1603
|
+
let bundleName: string = command.bundleName;
|
|
1604
|
+
const targetBinaryPath: string = command.targetBinary;
|
|
1605
|
+
const outputFolder: string = command.outputDir || path.join(os.tmpdir(), "CodePush");
|
|
1606
|
+
const extractFolder: string = path.join(os.tmpdir(), "CodePushBinaryExtract");
|
|
1607
|
+
// Validate platform
|
|
1608
|
+
if (platform !== "ios" && platform !== "android") {
|
|
1609
|
+
throw new Error('Platform must be either "ios" or "android" for the "release-native" command.');
|
|
1610
|
+
}
|
|
1611
|
+
// Validate target binary file exists
|
|
1612
|
+
if (!fileExists(targetBinaryPath)) {
|
|
1613
|
+
throw new Error(`Target binary file "${targetBinaryPath}" does not exist.`);
|
|
1614
|
+
}
|
|
1615
|
+
// Validate file extension matches platform
|
|
1616
|
+
if (platform === "ios" && !targetBinaryPath.toLowerCase().endsWith(".ipa")) {
|
|
1617
|
+
throw new Error("For iOS platform, target binary must be an .ipa file.");
|
|
1618
|
+
}
|
|
1619
|
+
if (platform === "android" && !targetBinaryPath.toLowerCase().endsWith(".apk")) {
|
|
1620
|
+
throw new Error("For Android platform, target binary must be an .apk file.");
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
return sdk
|
|
1624
|
+
.getDeployment(command.appName, command.deploymentName)
|
|
1625
|
+
.then(async () => {
|
|
1626
|
+
try {
|
|
1627
|
+
await createEmptyTempReleaseFolder(outputFolder);
|
|
1628
|
+
await createEmptyTempReleaseFolder(extractFolder);
|
|
1629
|
+
|
|
1630
|
+
if (!bundleName) {
|
|
1631
|
+
bundleName = platform === "ios" ? "main.jsbundle" : `index.android.bundle`;
|
|
1632
|
+
}
|
|
1633
|
+
let releaseCommandPartial: Partial<cli.IReleaseReactCommand>;
|
|
1634
|
+
|
|
1635
|
+
if (platform === "ios") {
|
|
1636
|
+
log(chalk.cyan(`\nExtracting IPA file:\n`));
|
|
1637
|
+
await extractIPA(targetBinaryPath, extractFolder);
|
|
1638
|
+
const metadataZip = await extractMetadataFromIOS(extractFolder, outputFolder);
|
|
1639
|
+
const buildVersion = await getIosVersion(extractFolder)
|
|
1640
|
+
releaseCommandPartial = { package: metadataZip, appStoreVersion: buildVersion?.version };
|
|
1641
|
+
} else {
|
|
1642
|
+
log(chalk.cyan(`\nExtracting APK/ARR file:\n`));
|
|
1643
|
+
await extractAPK(targetBinaryPath, extractFolder);
|
|
1644
|
+
|
|
1645
|
+
const reader = await ApkReader.open(targetBinaryPath);
|
|
1646
|
+
const { versionName: appStoreVersion } = await reader.readManifest();
|
|
1647
|
+
const metadataZip = await extractMetadataFromAndroid(extractFolder, outputFolder);
|
|
1648
|
+
releaseCommandPartial = { package: metadataZip, appStoreVersion };
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
const { package: metadataZip, appStoreVersion } = releaseCommandPartial;
|
|
1652
|
+
// Use the zip file as package for release
|
|
1653
|
+
const releaseCommand: cli.IReleaseReactCommand = {
|
|
1654
|
+
type: cli.CommandType.release,
|
|
1655
|
+
appName: command.appName,
|
|
1656
|
+
deploymentName: command.deploymentName,
|
|
1657
|
+
appStoreVersion: command.appStoreVersion || appStoreVersion,
|
|
1658
|
+
description: command.description,
|
|
1659
|
+
disabled: command.disabled,
|
|
1660
|
+
mandatory: command.mandatory,
|
|
1661
|
+
rollout: command.rollout,
|
|
1662
|
+
initial: command.initial,
|
|
1663
|
+
noDuplicateReleaseError: command.noDuplicateReleaseError,
|
|
1664
|
+
platform: platform,
|
|
1665
|
+
outputDir: outputFolder,
|
|
1666
|
+
bundleName: bundleName,
|
|
1667
|
+
package: metadataZip,
|
|
1668
|
+
};
|
|
1669
|
+
|
|
1670
|
+
return doNativeRelease(releaseCommand).then(async () => {
|
|
1671
|
+
// Clean up zip file
|
|
1672
|
+
if (fs.existsSync(releaseCommandPartial.package)) {
|
|
1673
|
+
fs.unlinkSync(releaseCommandPartial.package);
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1676
|
+
} finally {
|
|
1677
|
+
try {
|
|
1678
|
+
await deleteFolder(extractFolder);
|
|
1679
|
+
await deleteFolder(outputFolder);
|
|
1680
|
+
} catch (ignored) {}
|
|
1681
|
+
}
|
|
1682
|
+
})
|
|
1683
|
+
.catch(async (err: Error) => {
|
|
1684
|
+
throw err;
|
|
1685
|
+
});
|
|
1686
|
+
};
|
|
1687
|
+
|
|
1370
1688
|
const releaseReactNative = (command: cli.IReleaseReactCommand): Promise<void> => {
|
|
1371
1689
|
// for initial release we explicitly define release as optional, disabled, without rollout, with a special description
|
|
1372
1690
|
const updateMetadata: ReactNativePackageInfo = {
|
|
@@ -1414,6 +1732,8 @@ const doRelease = (command: cli.IReleaseCommand | cli.IReleaseReactCommand, upda
|
|
|
1414
1732
|
return sdk
|
|
1415
1733
|
.isAuthenticated(true)
|
|
1416
1734
|
.then((isAuth: boolean): Promise<void> => {
|
|
1735
|
+
log("Release file path: " + filePath);
|
|
1736
|
+
log("Metadata: " + JSON.stringify(updateMetadata));
|
|
1417
1737
|
return sdk.release(command.appName, command.deploymentName, filePath, updateMetadata, uploadProgress);
|
|
1418
1738
|
})
|
|
1419
1739
|
.then((): void => {
|
|
@@ -1432,6 +1752,57 @@ const doRelease = (command: cli.IReleaseCommand | cli.IReleaseReactCommand, upda
|
|
|
1432
1752
|
.catch((err: CodePushError) => releaseErrorHandler(err, command));
|
|
1433
1753
|
};
|
|
1434
1754
|
|
|
1755
|
+
const doNativeRelease = (releaseCommand: cli.IReleaseReactCommand): Promise<void> => {
|
|
1756
|
+
throwForInvalidSemverRange(releaseCommand.appStoreVersion);
|
|
1757
|
+
|
|
1758
|
+
const filePath: string = releaseCommand.package;
|
|
1759
|
+
|
|
1760
|
+
const updateMetadata: ReactNativePackageInfo = {
|
|
1761
|
+
description: releaseCommand.initial ? `Zero release for v${releaseCommand.appStoreVersion}` : releaseCommand.description,
|
|
1762
|
+
isDisabled: releaseCommand.initial ? true : releaseCommand.disabled,
|
|
1763
|
+
isMandatory: releaseCommand.initial ? false : releaseCommand.mandatory,
|
|
1764
|
+
isInitial: releaseCommand.initial,
|
|
1765
|
+
bundleName: releaseCommand.bundleName,
|
|
1766
|
+
outputDir: releaseCommand.outputDir,
|
|
1767
|
+
rollout: releaseCommand.initial ? undefined : releaseCommand.rollout,
|
|
1768
|
+
appVersion: releaseCommand.appStoreVersion,
|
|
1769
|
+
};
|
|
1770
|
+
|
|
1771
|
+
let lastTotalProgress = 0;
|
|
1772
|
+
|
|
1773
|
+
const progressBar = new progress("Upload progress:[:bar] :percent :etas", {
|
|
1774
|
+
complete: "=",
|
|
1775
|
+
incomplete: " ",
|
|
1776
|
+
width: 50,
|
|
1777
|
+
total: 100,
|
|
1778
|
+
});
|
|
1779
|
+
|
|
1780
|
+
const uploadProgress = (currentProgress: number): void => {
|
|
1781
|
+
progressBar.tick(currentProgress - lastTotalProgress);
|
|
1782
|
+
lastTotalProgress = currentProgress;
|
|
1783
|
+
};
|
|
1784
|
+
|
|
1785
|
+
return sdk
|
|
1786
|
+
.isAuthenticated(true)
|
|
1787
|
+
.then((): Promise<void> => {
|
|
1788
|
+
return sdk.releaseNative(releaseCommand.appName, releaseCommand.deploymentName, filePath, updateMetadata, uploadProgress);
|
|
1789
|
+
})
|
|
1790
|
+
.then((): void => {
|
|
1791
|
+
log(
|
|
1792
|
+
'Successfully released an update containing the "' +
|
|
1793
|
+
releaseCommand.package +
|
|
1794
|
+
'" ' +
|
|
1795
|
+
"directory" +
|
|
1796
|
+
' to the "' +
|
|
1797
|
+
releaseCommand.deploymentName +
|
|
1798
|
+
'" deployment of the "' +
|
|
1799
|
+
releaseCommand.appName +
|
|
1800
|
+
'" app.'
|
|
1801
|
+
);
|
|
1802
|
+
})
|
|
1803
|
+
.catch((err: CodePushError) => releaseErrorHandler(err, releaseCommand));
|
|
1804
|
+
};
|
|
1805
|
+
|
|
1435
1806
|
function rollback(command: cli.IRollbackCommand): Promise<void> {
|
|
1436
1807
|
return confirm().then((wasConfirmed: boolean) => {
|
|
1437
1808
|
if (!wasConfirmed) {
|
|
@@ -1564,7 +1935,7 @@ function serializeConnectionInfo(accessKey: string, preserveAccessKeyOnLogout: b
|
|
|
1564
1935
|
|
|
1565
1936
|
log(
|
|
1566
1937
|
`\r\nSuccessfully logged-in. Your session file was written to ${chalk.cyan(configFilePath)}. You can run the ${chalk.cyan(
|
|
1567
|
-
"
|
|
1938
|
+
"revopush logout"
|
|
1568
1939
|
)} command at any time to delete this file and terminate your session.\r\n`
|
|
1569
1940
|
);
|
|
1570
1941
|
}
|