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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,8 @@
2
2
  // Copyright (c) Microsoft Corporation.
3
3
  // Licensed under the MIT License.
4
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;
5
+ exports.runReactNativeBundleCommand = exports.releaseNative = exports.releaseReact = exports.releaseExpo = exports.runExpoExportEmbedCommand = exports.release = exports.execute = exports.deploymentList = exports.createEmptyTempReleaseFolder = exports.confirm = exports.execSync = exports.spawn = exports.sdk = exports.log = void 0;
6
+ const binary_utils_1 = require("./binary-utils");
6
7
  const childProcess = require("child_process");
7
8
  const debug_1 = require("./commands/debug");
8
9
  const fs = require("fs");
@@ -14,11 +15,13 @@ const Q = require("q");
14
15
  const semver = require("semver");
15
16
  const cli = require("../script/types/cli");
16
17
  const sign_1 = require("./sign");
18
+ const ApkReader = require("@devicefarmer/adbkit-apkreader");
17
19
  const react_native_utils_1 = require("./react-native-utils");
18
20
  const file_utils_1 = require("./utils/file-utils");
19
21
  const AccountManager = require("./management-sdk");
20
22
  const wordwrap = require("wordwrap");
21
23
  var Promise = Q.Promise;
24
+ const expo_utils_1 = require("./expo-utils");
22
25
  const g2js = require("gradle-to-js/lib/parser");
23
26
  const opener = require("opener");
24
27
  const plist = require("plist");
@@ -236,11 +239,12 @@ function deploymentHistoryClear(command) {
236
239
  const deploymentList = (command, showPackage = true) => {
237
240
  throwForInvalidOutputFormat(command.format);
238
241
  let deployments;
242
+ const DEPLOYMENTS_MAX_LENGTH = 10; // do not take metrics if number of deployment higher than this
239
243
  return exports.sdk
240
244
  .getDeployments(command.appName)
241
245
  .then((retrievedDeployments) => {
242
246
  deployments = retrievedDeployments;
243
- if (showPackage) {
247
+ if (showPackage && deployments.length < DEPLOYMENTS_MAX_LENGTH) {
244
248
  const metricsPromises = deployments.map((deployment) => {
245
249
  if (deployment.package) {
246
250
  return exports.sdk.getDeploymentMetrics(command.appName, deployment.name).then((metrics) => {
@@ -269,7 +273,10 @@ const deploymentList = (command, showPackage = true) => {
269
273
  };
270
274
  exports.deploymentList = deploymentList;
271
275
  function deploymentRemove(command) {
272
- return (0, exports.confirm)("Are you sure you want to remove this deployment? Note that its deployment key will be PERMANENTLY unrecoverable.").then((wasConfirmed) => {
276
+ const confirmation = command.isForce
277
+ ? Q.resolve(true)
278
+ : (0, exports.confirm)("Are you sure you want to remove this deployment? Note that its deployment key will be PERMANENTLY unrecoverable.");
279
+ return confirmation.then((wasConfirmed) => {
273
280
  if (wasConfirmed) {
274
281
  return exports.sdk.removeDeployment(command.appName, command.deploymentName).then(() => {
275
282
  (0, exports.log)('Successfully removed the "' + command.deploymentName + '" deployment from the "' + command.appName + '" app.');
@@ -409,6 +416,10 @@ function execute(command) {
409
416
  return (0, exports.release)(command);
410
417
  case cli.CommandType.releaseReact:
411
418
  return (0, exports.releaseReact)(command);
419
+ case cli.CommandType.releaseExpo:
420
+ return (0, exports.releaseExpo)(command);
421
+ case cli.CommandType.releaseNative:
422
+ return (0, exports.releaseNative)(command);
412
423
  case cli.CommandType.rollback:
413
424
  return rollback(command);
414
425
  case cli.CommandType.sessionList:
@@ -969,56 +980,191 @@ function patch(command) {
969
980
  throw new Error("At least one property must be specified to patch a release.");
970
981
  }
971
982
  const release = (command) => {
972
- if ((0, file_utils_1.isBinaryOrZip)(command.package)) {
973
- 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).");
983
+ // for initial release we explicitly define release as optional, disabled, without rollout, with a special description
984
+ const updateMetadata = {
985
+ description: command.initial ? `Zero release for v${command.appStoreVersion}` : command.description,
986
+ isDisabled: command.initial ? true : command.disabled,
987
+ isMandatory: command.initial ? false : command.mandatory,
988
+ isInitial: command.initial,
989
+ rollout: command.initial ? undefined : command.rollout,
990
+ appVersion: command.appStoreVersion,
991
+ };
992
+ return doRelease(command, updateMetadata);
993
+ };
994
+ exports.release = release;
995
+ const runExpoExportEmbedCommand = async (command, bundleName, development,
996
+ // entryFile: string,
997
+ outputFolder, sourcemapOutputFolder, platform, extraBundlerOptions) => {
998
+ const expoBundleArgs = [];
999
+ const envNodeArgs = process.env.CODE_PUSH_NODE_ARGS;
1000
+ if (typeof envNodeArgs !== "undefined") {
1001
+ Array.prototype.push.apply(expoBundleArgs, envNodeArgs.trim().split(/\s+/));
974
1002
  }
975
- throwForInvalidSemverRange(command.appStoreVersion);
976
- const filePath = command.package;
977
- let isSingleFilePackage = true;
978
- if (fs.lstatSync(filePath).isDirectory()) {
979
- isSingleFilePackage = false;
1003
+ const expoCliPath = (0, expo_utils_1.getExpoCliPath)();
1004
+ Array.prototype.push.apply(expoBundleArgs, [
1005
+ expoCliPath,
1006
+ "export:embed",
1007
+ "--assets-dest",
1008
+ outputFolder,
1009
+ "--bundle-output",
1010
+ path.join(outputFolder, bundleName),
1011
+ "--dev",
1012
+ development,
1013
+ "--platform",
1014
+ platform,
1015
+ "--minify",
1016
+ false,
1017
+ "--reset-cache",
1018
+ ]);
1019
+ if (sourcemapOutputFolder) {
1020
+ let bundleSourceMapOutput = sourcemapOutputFolder;
1021
+ if (!sourcemapOutputFolder.endsWith(".map")) {
1022
+ // user defined directory, нужно вычислить полный путь
1023
+ bundleSourceMapOutput = await (0, react_native_utils_1.getBundleSourceMapOutput)(command, bundleName, sourcemapOutputFolder);
1024
+ }
1025
+ expoBundleArgs.push("--sourcemap-output", bundleSourceMapOutput);
980
1026
  }
981
- let lastTotalProgress = 0;
982
- const progressBar = new progress("Upload progress:[:bar] :percent :etas", {
983
- complete: "=",
984
- incomplete: " ",
985
- width: 50,
986
- total: 100,
1027
+ // const minifyValue = await getMinifyParams(command);
1028
+ // Array.prototype.push.apply(expoBundleArgs, minifyValue);
1029
+ if (extraBundlerOptions.length > 0) {
1030
+ expoBundleArgs.push(...extraBundlerOptions);
1031
+ }
1032
+ (0, exports.log)(chalk.cyan('Running "expo export:embed" command:\n'));
1033
+ const projectRoot = process.cwd();
1034
+ expoBundleArgs.push(projectRoot);
1035
+ (0, exports.log)("expoBundleArgs raw:" + JSON.stringify(expoBundleArgs, null, 2));
1036
+ const expoBundleProcess = (0, exports.spawn)("node", expoBundleArgs);
1037
+ (0, exports.log)(`node ${expoBundleArgs.join(" ")}`);
1038
+ return Promise((resolve, reject, notify) => {
1039
+ expoBundleProcess.stdout.on("data", (data) => {
1040
+ (0, exports.log)(data.toString().trim());
1041
+ });
1042
+ expoBundleProcess.stderr.on("data", (data) => {
1043
+ console.error(data.toString().trim());
1044
+ });
1045
+ expoBundleProcess.on("close", (exitCode) => {
1046
+ if (exitCode) {
1047
+ reject(new Error(`"expo export:embed" command exited with code ${exitCode}.`));
1048
+ }
1049
+ resolve(null);
1050
+ });
987
1051
  });
988
- const uploadProgress = (currentProgress) => {
989
- progressBar.tick(currentProgress - lastTotalProgress);
990
- lastTotalProgress = currentProgress;
991
- };
992
- const updateMetadata = {
993
- description: command.description,
994
- isDisabled: command.disabled,
995
- isMandatory: command.mandatory,
996
- rollout: command.rollout,
997
- };
1052
+ };
1053
+ exports.runExpoExportEmbedCommand = runExpoExportEmbedCommand;
1054
+ const releaseExpo = (command) => {
1055
+ let bundleName = command.bundleName;
1056
+ // let entryFile: string = command.entryFile;
1057
+ const outputFolder = command.outputDir || path.join(os.tmpdir(), "CodePush");
1058
+ const sourcemapOutputFolder = command.sourcemapOutput || path.join(os.tmpdir(), "CodePushSourceMap");
1059
+ const baseReleaseTmpFolder = path.join(os.tmpdir(), "CodePushBaseRelease");
1060
+ const platform = (command.platform = command.platform.toLowerCase());
1061
+ const releaseCommand = command;
998
1062
  return exports.sdk
999
- .isAuthenticated(true)
1000
- .then((isAuth) => {
1001
- return exports.sdk.release(command.appName, command.deploymentName, filePath, command.appStoreVersion, updateMetadata, uploadProgress);
1063
+ .getDeployment(command.appName, command.deploymentName)
1064
+ .then(async () => {
1065
+ switch (platform) {
1066
+ case "android":
1067
+ case "ios":
1068
+ if (!bundleName) {
1069
+ bundleName = platform === "ios" ? "main.jsbundle" : `index.${platform}.bundle`;
1070
+ }
1071
+ break;
1072
+ default:
1073
+ throw new Error('Platform must be either "android" or "ios" for the "release-expo" command.');
1074
+ }
1075
+ releaseCommand.package = outputFolder;
1076
+ releaseCommand.outputDir = outputFolder;
1077
+ releaseCommand.bundleName = bundleName;
1078
+ let projectName;
1079
+ try {
1080
+ const projectPackageJson = require(path.join(process.cwd(), "package.json"));
1081
+ projectName = projectPackageJson.name;
1082
+ if (!projectName) {
1083
+ throw new Error('The "package.json" file in the CWD does not have the "name" field set.');
1084
+ }
1085
+ if (!projectPackageJson.dependencies["react-native"]) {
1086
+ throw new Error("The project in the CWD is not a React Native project.");
1087
+ }
1088
+ }
1089
+ catch (error) {
1090
+ throw new Error('Unable to find or read "package.json" in the CWD. The "release-expo" command must be executed in a React Native project folder.');
1091
+ }
1092
+ // TODO: do we really need entryFile for expo?
1093
+ // if (!entryFile) {
1094
+ // entryFile = `index.${platform}.js`;
1095
+ // if (fileDoesNotExistOrIsDirectory(entryFile)) {
1096
+ // entryFile = "index.js";
1097
+ // }
1098
+ //
1099
+ // if (fileDoesNotExistOrIsDirectory(entryFile)) {
1100
+ // throw new Error(`Entry file "index.${platform}.js" or "index.js" does not exist.`);
1101
+ // }
1102
+ // } else {
1103
+ // if (fileDoesNotExistOrIsDirectory(entryFile)) {
1104
+ // throw new Error(`Entry file "${entryFile}" does not exist.`);
1105
+ // }
1106
+ // }
1107
+ const appVersionPromise = command.appStoreVersion
1108
+ ? Q(command.appStoreVersion)
1109
+ : getReactNativeProjectAppVersion(command, projectName);
1110
+ if (!sourcemapOutputFolder.endsWith(".map") && !command.sourcemapOutput) {
1111
+ await (0, exports.createEmptyTempReleaseFolder)(sourcemapOutputFolder);
1112
+ }
1113
+ return appVersionPromise;
1114
+ })
1115
+ .then((appVersion) => {
1116
+ throwForInvalidSemverRange(appVersion);
1117
+ releaseCommand.appStoreVersion = appVersion;
1118
+ return (0, exports.createEmptyTempReleaseFolder)(outputFolder);
1119
+ })
1120
+ .then(() => deleteFolder(`${os.tmpdir()}/react-*`))
1121
+ .then(async () => {
1122
+ await (0, exports.runExpoExportEmbedCommand)(command, bundleName, command.development || false,
1123
+ // entryFile,
1124
+ outputFolder, sourcemapOutputFolder, platform, command.extraBundlerOptions);
1125
+ })
1126
+ .then(async () => {
1127
+ const isHermes = await (0, react_native_utils_1.isHermesEnabled)(command, platform);
1128
+ if (isHermes) {
1129
+ await (0, exports.createEmptyTempReleaseFolder)(baseReleaseTmpFolder);
1130
+ const baseBytecode = await (0, react_native_utils_1.takeHermesBaseBytecode)(command, baseReleaseTmpFolder, outputFolder, bundleName);
1131
+ (0, exports.log)(chalk.cyan("\nRunning hermes compiler.\n"));
1132
+ await (0, react_native_utils_1.runHermesEmitBinaryCommand)(command, bundleName, outputFolder, sourcemapOutputFolder, command.extraHermesFlags, command.gradleFile, baseBytecode);
1133
+ }
1134
+ })
1135
+ .then(async () => {
1136
+ if (command.privateKeyPath) {
1137
+ (0, exports.log)(chalk.cyan("\nSigning the bundle:\n"));
1138
+ await (0, sign_1.default)(command.privateKeyPath, outputFolder);
1139
+ }
1140
+ else {
1141
+ console.log("private key was not provided");
1142
+ }
1002
1143
  })
1003
1144
  .then(() => {
1004
- (0, exports.log)('Successfully released an update containing the "' +
1005
- command.package +
1006
- '" ' +
1007
- (isSingleFilePackage ? "file" : "directory") +
1008
- ' to the "' +
1009
- command.deploymentName +
1010
- '" deployment of the "' +
1011
- command.appName +
1012
- '" app.');
1145
+ (0, exports.log)(chalk.cyan("\nReleasing update contents to CodePush:\n"));
1146
+ return releaseReactNative(releaseCommand);
1013
1147
  })
1014
- .catch((err) => releaseErrorHandler(err, command));
1148
+ .then(async () => {
1149
+ if (!command.outputDir) {
1150
+ await deleteFolder(outputFolder);
1151
+ }
1152
+ if (!command.sourcemapOutput) {
1153
+ await deleteFolder(sourcemapOutputFolder);
1154
+ }
1155
+ await deleteFolder(baseReleaseTmpFolder);
1156
+ })
1157
+ .catch(async (err) => {
1158
+ throw err;
1159
+ });
1015
1160
  };
1016
- exports.release = release;
1161
+ exports.releaseExpo = releaseExpo;
1017
1162
  const releaseReact = (command) => {
1018
1163
  let bundleName = command.bundleName;
1019
1164
  let entryFile = command.entryFile;
1020
1165
  const outputFolder = command.outputDir || path.join(os.tmpdir(), "CodePush");
1021
1166
  const sourcemapOutputFolder = command.sourcemapOutput || path.join(os.tmpdir(), "CodePushSourceMap");
1167
+ const baseReleaseTmpFolder = path.join(os.tmpdir(), "CodePushBaseRelease");
1022
1168
  const platform = (command.platform = command.platform.toLowerCase());
1023
1169
  const releaseCommand = command;
1024
1170
  // Check for app and deployment exist before releasing an update.
@@ -1026,7 +1172,6 @@ const releaseReact = (command) => {
1026
1172
  return (exports.sdk
1027
1173
  .getDeployment(command.appName, command.deploymentName)
1028
1174
  .then(async () => {
1029
- releaseCommand.package = outputFolder;
1030
1175
  switch (platform) {
1031
1176
  case "android":
1032
1177
  case "ios":
@@ -1038,6 +1183,9 @@ const releaseReact = (command) => {
1038
1183
  default:
1039
1184
  throw new Error('Platform must be either "android", "ios" or "windows".');
1040
1185
  }
1186
+ releaseCommand.package = outputFolder;
1187
+ releaseCommand.outputDir = outputFolder;
1188
+ releaseCommand.bundleName = bundleName;
1041
1189
  let projectName;
1042
1190
  try {
1043
1191
  const projectPackageJson = require(path.join(process.cwd(), "package.json"));
@@ -1052,6 +1200,7 @@ const releaseReact = (command) => {
1052
1200
  catch (error) {
1053
1201
  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.');
1054
1202
  }
1203
+ // TODO: check entry file detection
1055
1204
  if (!entryFile) {
1056
1205
  entryFile = `index.${platform}.js`;
1057
1206
  if ((0, file_utils_1.fileDoesNotExistOrIsDirectory)(entryFile)) {
@@ -1089,8 +1238,10 @@ const releaseReact = (command) => {
1089
1238
  .then(async () => {
1090
1239
  const isHermes = await (0, react_native_utils_1.isHermesEnabled)(command, platform);
1091
1240
  if (isHermes) {
1241
+ await (0, exports.createEmptyTempReleaseFolder)(baseReleaseTmpFolder);
1242
+ const baseBytecode = await (0, react_native_utils_1.takeHermesBaseBytecode)(command, baseReleaseTmpFolder, outputFolder, bundleName);
1092
1243
  (0, exports.log)(chalk.cyan("\nRunning hermes compiler...\n"));
1093
- await (0, react_native_utils_1.runHermesEmitBinaryCommand)(command, bundleName, outputFolder, sourcemapOutputFolder, command.extraHermesFlags, command.gradleFile);
1244
+ await (0, react_native_utils_1.runHermesEmitBinaryCommand)(command, bundleName, outputFolder, sourcemapOutputFolder, command.extraHermesFlags, command.gradleFile, baseBytecode);
1094
1245
  }
1095
1246
  })
1096
1247
  .then(async () => {
@@ -1104,7 +1255,7 @@ const releaseReact = (command) => {
1104
1255
  })
1105
1256
  .then(() => {
1106
1257
  (0, exports.log)(chalk.cyan("\nReleasing update contents to CodePush:\n"));
1107
- return (0, exports.release)(releaseCommand);
1258
+ return releaseReactNative(releaseCommand);
1108
1259
  })
1109
1260
  .then(async () => {
1110
1261
  if (!command.outputDir) {
@@ -1113,12 +1264,194 @@ const releaseReact = (command) => {
1113
1264
  if (!command.sourcemapOutput) {
1114
1265
  await deleteFolder(sourcemapOutputFolder);
1115
1266
  }
1267
+ await deleteFolder(baseReleaseTmpFolder);
1116
1268
  })
1117
1269
  .catch(async (err) => {
1118
1270
  throw err;
1119
1271
  }));
1120
1272
  };
1121
1273
  exports.releaseReact = releaseReact;
1274
+ const releaseNative = (command) => {
1275
+ const platform = command.platform.toLowerCase();
1276
+ let bundleName = command.bundleName;
1277
+ const targetBinaryPath = command.targetBinary;
1278
+ const outputFolder = command.outputDir || path.join(os.tmpdir(), "CodePush");
1279
+ const extractFolder = path.join(os.tmpdir(), "CodePushBinaryExtract");
1280
+ // Validate platform
1281
+ if (platform !== "ios" && platform !== "android") {
1282
+ throw new Error('Platform must be either "ios" or "android" for the "release-native" command.');
1283
+ }
1284
+ // Validate target binary file exists
1285
+ if (!(0, file_utils_1.fileExists)(targetBinaryPath)) {
1286
+ throw new Error(`Target binary file "${targetBinaryPath}" does not exist.`);
1287
+ }
1288
+ // Validate file extension matches platform
1289
+ if (platform === "ios" && !targetBinaryPath.toLowerCase().endsWith(".ipa")) {
1290
+ throw new Error("For iOS platform, target binary must be an .ipa file.");
1291
+ }
1292
+ if (platform === "android" && !targetBinaryPath.toLowerCase().endsWith(".apk")) {
1293
+ throw new Error("For Android platform, target binary must be an .apk file.");
1294
+ }
1295
+ return exports.sdk
1296
+ .getDeployment(command.appName, command.deploymentName)
1297
+ .then(async () => {
1298
+ try {
1299
+ await (0, exports.createEmptyTempReleaseFolder)(outputFolder);
1300
+ await (0, exports.createEmptyTempReleaseFolder)(extractFolder);
1301
+ if (!bundleName) {
1302
+ bundleName = platform === "ios" ? "main.jsbundle" : `index.android.bundle`;
1303
+ }
1304
+ let releaseCommandPartial;
1305
+ if (platform === "ios") {
1306
+ (0, exports.log)(chalk.cyan(`\nExtracting IPA file:\n`));
1307
+ await (0, file_utils_1.extractIPA)(targetBinaryPath, extractFolder);
1308
+ const metadataZip = await (0, binary_utils_1.extractMetadataFromIOS)(extractFolder, outputFolder);
1309
+ const buildVersion = await (0, binary_utils_1.getIosVersion)(extractFolder);
1310
+ releaseCommandPartial = { package: metadataZip, appStoreVersion: buildVersion?.version };
1311
+ }
1312
+ else {
1313
+ (0, exports.log)(chalk.cyan(`\nExtracting APK/ARR file:\n`));
1314
+ await (0, file_utils_1.extractAPK)(targetBinaryPath, extractFolder);
1315
+ const reader = await ApkReader.open(targetBinaryPath);
1316
+ const { versionName: appStoreVersion } = await reader.readManifest();
1317
+ const metadataZip = await (0, binary_utils_1.extractMetadataFromAndroid)(extractFolder, outputFolder);
1318
+ releaseCommandPartial = { package: metadataZip, appStoreVersion };
1319
+ }
1320
+ const { package: metadataZip, appStoreVersion } = releaseCommandPartial;
1321
+ // Use the zip file as package for release
1322
+ const releaseCommand = {
1323
+ type: cli.CommandType.release,
1324
+ appName: command.appName,
1325
+ deploymentName: command.deploymentName,
1326
+ appStoreVersion: command.appStoreVersion || appStoreVersion,
1327
+ description: command.description,
1328
+ disabled: command.disabled,
1329
+ mandatory: command.mandatory,
1330
+ rollout: command.rollout,
1331
+ initial: command.initial,
1332
+ noDuplicateReleaseError: command.noDuplicateReleaseError,
1333
+ platform: platform,
1334
+ outputDir: outputFolder,
1335
+ bundleName: bundleName,
1336
+ package: metadataZip,
1337
+ };
1338
+ return doNativeRelease(releaseCommand).then(async () => {
1339
+ // Clean up zip file
1340
+ if (fs.existsSync(releaseCommandPartial.package)) {
1341
+ fs.unlinkSync(releaseCommandPartial.package);
1342
+ }
1343
+ });
1344
+ }
1345
+ finally {
1346
+ try {
1347
+ await deleteFolder(extractFolder);
1348
+ await deleteFolder(outputFolder);
1349
+ }
1350
+ catch (ignored) { }
1351
+ }
1352
+ })
1353
+ .catch(async (err) => {
1354
+ throw err;
1355
+ });
1356
+ };
1357
+ exports.releaseNative = releaseNative;
1358
+ const releaseReactNative = (command) => {
1359
+ // for initial release we explicitly define release as optional, disabled, without rollout, with a special description
1360
+ const updateMetadata = {
1361
+ description: command.initial ? `Zero release for v${command.appStoreVersion}` : command.description,
1362
+ isDisabled: command.initial ? true : command.disabled,
1363
+ isMandatory: command.initial ? false : command.mandatory,
1364
+ isInitial: command.initial,
1365
+ bundleName: command.bundleName,
1366
+ outputDir: command.outputDir,
1367
+ rollout: command.initial ? undefined : command.rollout,
1368
+ appVersion: command.appStoreVersion,
1369
+ };
1370
+ return doRelease(command, updateMetadata);
1371
+ };
1372
+ const doRelease = (command, updateMetadata) => {
1373
+ if ((0, file_utils_1.isBinaryOrZip)(command.package)) {
1374
+ 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).");
1375
+ }
1376
+ throwForInvalidSemverRange(command.appStoreVersion);
1377
+ const filePath = command.package;
1378
+ let isSingleFilePackage = true;
1379
+ if (fs.lstatSync(filePath).isDirectory()) {
1380
+ isSingleFilePackage = false;
1381
+ }
1382
+ let lastTotalProgress = 0;
1383
+ const progressBar = new progress("Upload progress:[:bar] :percent :etas", {
1384
+ complete: "=",
1385
+ incomplete: " ",
1386
+ width: 50,
1387
+ total: 100,
1388
+ });
1389
+ const uploadProgress = (currentProgress) => {
1390
+ progressBar.tick(currentProgress - lastTotalProgress);
1391
+ lastTotalProgress = currentProgress;
1392
+ };
1393
+ return exports.sdk
1394
+ .isAuthenticated(true)
1395
+ .then((isAuth) => {
1396
+ (0, exports.log)("Release file path: " + filePath);
1397
+ (0, exports.log)("Metadata: " + JSON.stringify(updateMetadata));
1398
+ return exports.sdk.release(command.appName, command.deploymentName, filePath, updateMetadata, uploadProgress);
1399
+ })
1400
+ .then(() => {
1401
+ (0, exports.log)('Successfully released an update containing the "' +
1402
+ command.package +
1403
+ '" ' +
1404
+ (isSingleFilePackage ? "file" : "directory") +
1405
+ ' to the "' +
1406
+ command.deploymentName +
1407
+ '" deployment of the "' +
1408
+ command.appName +
1409
+ '" app.');
1410
+ })
1411
+ .catch((err) => releaseErrorHandler(err, command));
1412
+ };
1413
+ const doNativeRelease = (releaseCommand) => {
1414
+ throwForInvalidSemverRange(releaseCommand.appStoreVersion);
1415
+ const filePath = releaseCommand.package;
1416
+ const updateMetadata = {
1417
+ description: releaseCommand.initial ? `Zero release for v${releaseCommand.appStoreVersion}` : releaseCommand.description,
1418
+ isDisabled: releaseCommand.initial ? true : releaseCommand.disabled,
1419
+ isMandatory: releaseCommand.initial ? false : releaseCommand.mandatory,
1420
+ isInitial: releaseCommand.initial,
1421
+ bundleName: releaseCommand.bundleName,
1422
+ outputDir: releaseCommand.outputDir,
1423
+ rollout: releaseCommand.initial ? undefined : releaseCommand.rollout,
1424
+ appVersion: releaseCommand.appStoreVersion,
1425
+ };
1426
+ let lastTotalProgress = 0;
1427
+ const progressBar = new progress("Upload progress:[:bar] :percent :etas", {
1428
+ complete: "=",
1429
+ incomplete: " ",
1430
+ width: 50,
1431
+ total: 100,
1432
+ });
1433
+ const uploadProgress = (currentProgress) => {
1434
+ progressBar.tick(currentProgress - lastTotalProgress);
1435
+ lastTotalProgress = currentProgress;
1436
+ };
1437
+ return exports.sdk
1438
+ .isAuthenticated(true)
1439
+ .then(() => {
1440
+ return exports.sdk.releaseNative(releaseCommand.appName, releaseCommand.deploymentName, filePath, updateMetadata, uploadProgress);
1441
+ })
1442
+ .then(() => {
1443
+ (0, exports.log)('Successfully released an update containing the "' +
1444
+ releaseCommand.package +
1445
+ '" ' +
1446
+ "directory" +
1447
+ ' to the "' +
1448
+ releaseCommand.deploymentName +
1449
+ '" deployment of the "' +
1450
+ releaseCommand.appName +
1451
+ '" app.');
1452
+ })
1453
+ .catch((err) => releaseErrorHandler(err, releaseCommand));
1454
+ };
1122
1455
  function rollback(command) {
1123
1456
  return (0, exports.confirm)().then((wasConfirmed) => {
1124
1457
  if (!wasConfirmed) {
@@ -1217,7 +1550,7 @@ function serializeConnectionInfo(accessKey, preserveAccessKeyOnLogout, customSer
1217
1550
  }
1218
1551
  const json = JSON.stringify(connectionInfo);
1219
1552
  fs.writeFileSync(configFilePath, json, { encoding: "utf8" });
1220
- (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`);
1553
+ (0, exports.log)(`\r\nSuccessfully logged-in. Your session file was written to ${chalk.cyan(configFilePath)}. You can run the ${chalk.cyan("revopush logout")} command at any time to delete this file and terminate your session.\r\n`);
1221
1554
  }
1222
1555
  function sessionList(command) {
1223
1556
  throwForInvalidOutputFormat(command.format);