@revopush/code-push-cli 0.0.5 → 0.0.8-rc.0

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.
@@ -3,35 +3,34 @@
3
3
  // Licensed under the MIT License.
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
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
6
  const childProcess = require("child_process");
8
7
  const debug_1 = require("./commands/debug");
9
8
  const fs = require("fs");
10
9
  const chalk = require("chalk");
11
- const g2js = require("gradle-to-js/lib/parser");
12
10
  const moment = require("moment");
13
- const opener = require("opener");
14
11
  const os = require("os");
15
12
  const path = require("path");
16
- const plist = require("plist");
17
- const progress = require("progress");
18
- const prompt = require("prompt");
19
13
  const Q = require("q");
20
- const rimraf = require("rimraf");
21
14
  const semver = require("semver");
22
- const Table = require("cli-table");
23
- const which = require("which");
24
- const wordwrap = require("wordwrap");
25
15
  const cli = require("../script/types/cli");
26
16
  const sign_1 = require("./sign");
27
- const xcode = require("xcode");
28
17
  const react_native_utils_1 = require("./react-native-utils");
29
18
  const file_utils_1 = require("./utils/file-utils");
19
+ const AccountManager = require("./management-sdk");
20
+ const wordwrap = require("wordwrap");
21
+ var Promise = Q.Promise;
22
+ const g2js = require("gradle-to-js/lib/parser");
23
+ const opener = require("opener");
24
+ const plist = require("plist");
25
+ const progress = require("progress");
26
+ const prompt = require("prompt");
27
+ const rimraf = require("rimraf");
28
+ const Table = require("cli-table");
29
+ const xcode = require("xcode");
30
30
  const configFilePath = path.join(process.env.LOCALAPPDATA || process.env.HOME, ".revopush.config");
31
31
  const emailValidator = require("email-validator");
32
32
  const packageJson = require("../../package.json");
33
33
  const parseXml = Q.denodeify(require("xml2js").parseString);
34
- var Promise = Q.Promise;
35
34
  const properties = require("properties");
36
35
  const CLI_HEADERS = {
37
36
  "X-CodePush-CLI-Version": packageJson.version,
@@ -271,7 +270,10 @@ const deploymentList = (command, showPackage = true) => {
271
270
  };
272
271
  exports.deploymentList = deploymentList;
273
272
  function deploymentRemove(command) {
274
- return (0, exports.confirm)("Are you sure you want to remove this deployment? Note that its deployment key will be PERMANENTLY unrecoverable.").then((wasConfirmed) => {
273
+ const confirmation = command.isForce
274
+ ? Q.resolve(true)
275
+ : (0, exports.confirm)("Are you sure you want to remove this deployment? Note that its deployment key will be PERMANENTLY unrecoverable.");
276
+ return confirmation.then((wasConfirmed) => {
275
277
  if (wasConfirmed) {
276
278
  return exports.sdk.removeDeployment(command.appName, command.deploymentName).then(() => {
277
279
  (0, exports.log)('Successfully removed the "' + command.deploymentName + '" deployment from the "' + command.appName + '" app.');
@@ -971,63 +973,31 @@ function patch(command) {
971
973
  throw new Error("At least one property must be specified to patch a release.");
972
974
  }
973
975
  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
- };
976
+ // for initial release we explicitly define release as optional, disabled, without rollout, with a special description
994
977
  const updateMetadata = {
995
- description: command.description,
996
- isDisabled: command.disabled,
997
- isMandatory: command.mandatory,
998
- rollout: command.rollout,
978
+ description: command.initial ? `Zero release for v${command.appStoreVersion}` : command.description,
979
+ isDisabled: command.initial ? true : command.disabled,
980
+ isMandatory: command.initial ? false : command.mandatory,
981
+ isInitial: command.initial,
982
+ rollout: command.initial ? undefined : command.rollout,
983
+ appVersion: command.appStoreVersion,
999
984
  };
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));
985
+ return doRelease(command, updateMetadata);
1017
986
  };
1018
987
  exports.release = release;
1019
988
  const releaseReact = (command) => {
1020
989
  let bundleName = command.bundleName;
1021
990
  let entryFile = command.entryFile;
1022
991
  const outputFolder = command.outputDir || path.join(os.tmpdir(), "CodePush");
992
+ const sourcemapOutputFolder = command.sourcemapOutput || path.join(os.tmpdir(), "CodePushSourceMap");
993
+ const baseReleaseTmpFolder = path.join(os.tmpdir(), "CodePushBaseRelease");
1023
994
  const platform = (command.platform = command.platform.toLowerCase());
1024
995
  const releaseCommand = command;
1025
996
  // Check for app and deployment exist before releasing an update.
1026
997
  // This validation helps to save about 1 minute or more in case user has typed wrong app or deployment name.
1027
998
  return (exports.sdk
1028
999
  .getDeployment(command.appName, command.deploymentName)
1029
- .then(() => {
1030
- releaseCommand.package = outputFolder;
1000
+ .then(async () => {
1031
1001
  switch (platform) {
1032
1002
  case "android":
1033
1003
  case "ios":
@@ -1039,6 +1009,9 @@ const releaseReact = (command) => {
1039
1009
  default:
1040
1010
  throw new Error('Platform must be either "android", "ios" or "windows".');
1041
1011
  }
1012
+ releaseCommand.package = outputFolder;
1013
+ releaseCommand.outputDir = outputFolder;
1014
+ releaseCommand.bundleName = bundleName;
1042
1015
  let projectName;
1043
1016
  try {
1044
1017
  const projectPackageJson = require(path.join(process.cwd(), "package.json"));
@@ -1070,8 +1043,9 @@ const releaseReact = (command) => {
1070
1043
  const appVersionPromise = command.appStoreVersion
1071
1044
  ? Q(command.appStoreVersion)
1072
1045
  : getReactNativeProjectAppVersion(command, projectName);
1073
- if (command.sourcemapOutput && !command.sourcemapOutput.endsWith(".map")) {
1074
- command.sourcemapOutput = path.join(command.sourcemapOutput, bundleName + ".map");
1046
+ if (!sourcemapOutputFolder.endsWith(".map") && !command.sourcemapOutput) {
1047
+ // create tmp dir only if no dir was given by user. User must crete a directory if --sourcemapOutput is passes
1048
+ await (0, exports.createEmptyTempReleaseFolder)(sourcemapOutputFolder);
1075
1049
  }
1076
1050
  return appVersionPromise;
1077
1051
  })
@@ -1083,14 +1057,16 @@ const releaseReact = (command) => {
1083
1057
  // This is needed to clear the react native bundler cache:
1084
1058
  // https://github.com/facebook/react-native/issues/4289
1085
1059
  .then(() => deleteFolder(`${os.tmpdir()}/react-*`))
1086
- .then(() => (0, exports.runReactNativeBundleCommand)(bundleName, command.development || false, entryFile, outputFolder, platform, command.sourcemapOutput, command.extraBundlerOptions))
1087
1060
  .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) {
1061
+ await (0, exports.runReactNativeBundleCommand)(command, bundleName, command.development || false, entryFile, outputFolder, sourcemapOutputFolder, platform, command.extraBundlerOptions);
1062
+ })
1063
+ .then(async () => {
1064
+ const isHermes = await (0, react_native_utils_1.isHermesEnabled)(command, platform);
1065
+ if (isHermes) {
1066
+ await (0, exports.createEmptyTempReleaseFolder)(baseReleaseTmpFolder);
1067
+ const baseBytecode = await (0, react_native_utils_1.takeHermesBaseBytecode)(command, baseReleaseTmpFolder, outputFolder, bundleName);
1092
1068
  (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);
1069
+ await (0, react_native_utils_1.runHermesEmitBinaryCommand)(command, bundleName, outputFolder, sourcemapOutputFolder, command.extraHermesFlags, command.gradleFile, baseBytecode);
1094
1070
  }
1095
1071
  })
1096
1072
  .then(async () => {
@@ -1104,19 +1080,75 @@ const releaseReact = (command) => {
1104
1080
  })
1105
1081
  .then(() => {
1106
1082
  (0, exports.log)(chalk.cyan("\nReleasing update contents to CodePush:\n"));
1107
- return (0, exports.release)(releaseCommand);
1083
+ return releaseReactNative(releaseCommand);
1108
1084
  })
1109
- .then(() => {
1085
+ .then(async () => {
1110
1086
  if (!command.outputDir) {
1111
- deleteFolder(outputFolder);
1087
+ await deleteFolder(outputFolder);
1088
+ }
1089
+ if (!command.sourcemapOutput) {
1090
+ await deleteFolder(sourcemapOutputFolder);
1112
1091
  }
1092
+ await deleteFolder(baseReleaseTmpFolder);
1113
1093
  })
1114
- .catch((err) => {
1115
- deleteFolder(outputFolder);
1094
+ .catch(async (err) => {
1116
1095
  throw err;
1117
1096
  }));
1118
1097
  };
1119
1098
  exports.releaseReact = releaseReact;
1099
+ const releaseReactNative = (command) => {
1100
+ // for initial release we explicitly define release as optional, disabled, without rollout, with a special description
1101
+ const updateMetadata = {
1102
+ description: command.initial ? `Zero release for v${command.appStoreVersion}` : command.description,
1103
+ isDisabled: command.initial ? true : command.disabled,
1104
+ isMandatory: command.initial ? false : command.mandatory,
1105
+ isInitial: command.initial,
1106
+ bundleName: command.bundleName,
1107
+ outputDir: command.outputDir,
1108
+ rollout: command.initial ? undefined : command.rollout,
1109
+ appVersion: command.appStoreVersion,
1110
+ };
1111
+ return doRelease(command, updateMetadata);
1112
+ };
1113
+ const doRelease = (command, updateMetadata) => {
1114
+ if ((0, file_utils_1.isBinaryOrZip)(command.package)) {
1115
+ 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).");
1116
+ }
1117
+ throwForInvalidSemverRange(command.appStoreVersion);
1118
+ const filePath = command.package;
1119
+ let isSingleFilePackage = true;
1120
+ if (fs.lstatSync(filePath).isDirectory()) {
1121
+ isSingleFilePackage = false;
1122
+ }
1123
+ let lastTotalProgress = 0;
1124
+ const progressBar = new progress("Upload progress:[:bar] :percent :etas", {
1125
+ complete: "=",
1126
+ incomplete: " ",
1127
+ width: 50,
1128
+ total: 100,
1129
+ });
1130
+ const uploadProgress = (currentProgress) => {
1131
+ progressBar.tick(currentProgress - lastTotalProgress);
1132
+ lastTotalProgress = currentProgress;
1133
+ };
1134
+ return exports.sdk
1135
+ .isAuthenticated(true)
1136
+ .then((isAuth) => {
1137
+ return exports.sdk.release(command.appName, command.deploymentName, filePath, updateMetadata, uploadProgress);
1138
+ })
1139
+ .then(() => {
1140
+ (0, exports.log)('Successfully released an update containing the "' +
1141
+ command.package +
1142
+ '" ' +
1143
+ (isSingleFilePackage ? "file" : "directory") +
1144
+ ' to the "' +
1145
+ command.deploymentName +
1146
+ '" deployment of the "' +
1147
+ command.appName +
1148
+ '" app.');
1149
+ })
1150
+ .catch((err) => releaseErrorHandler(err, command));
1151
+ };
1120
1152
  function rollback(command) {
1121
1153
  return (0, exports.confirm)().then((wasConfirmed) => {
1122
1154
  if (!wasConfirmed) {
@@ -1149,15 +1181,17 @@ function requestAccessKey() {
1149
1181
  });
1150
1182
  });
1151
1183
  }
1152
- const runReactNativeBundleCommand = (bundleName, development, entryFile, outputFolder, platform, sourcemapOutput, extraBundlerOptions) => {
1184
+ const runReactNativeBundleCommand = async (command, bundleName, development, entryFile, outputFolder, sourcemapOutputFolder, platform, extraBundlerOptions) => {
1153
1185
  const reactNativeBundleArgs = [];
1154
1186
  const envNodeArgs = process.env.CODE_PUSH_NODE_ARGS;
1155
1187
  if (typeof envNodeArgs !== "undefined") {
1156
1188
  Array.prototype.push.apply(reactNativeBundleArgs, envNodeArgs.trim().split(/\s+/));
1157
1189
  }
1158
- const isOldCLI = fs.existsSync(path.join("node_modules", "react-native", "local-cli", "cli.js"));
1190
+ const reactNativePackagePath = (0, react_native_utils_1.getReactNativePackagePath)();
1191
+ const oldCliPath = path.join(reactNativePackagePath, "local-cli", "cli.js");
1192
+ const cliPath = fs.existsSync(oldCliPath) ? oldCliPath : path.join(reactNativePackagePath, "cli.js");
1159
1193
  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"),
1194
+ cliPath,
1161
1195
  "bundle",
1162
1196
  "--assets-dest",
1163
1197
  outputFolder,
@@ -1169,10 +1203,18 @@ const runReactNativeBundleCommand = (bundleName, development, entryFile, outputF
1169
1203
  entryFile,
1170
1204
  "--platform",
1171
1205
  platform,
1206
+ "--reset-cache",
1172
1207
  ]);
1173
- if (sourcemapOutput) {
1174
- reactNativeBundleArgs.push("--sourcemap-output", sourcemapOutput);
1208
+ if (sourcemapOutputFolder) {
1209
+ let bundleSourceMapOutput = sourcemapOutputFolder;
1210
+ if (!sourcemapOutputFolder.endsWith(".map")) {
1211
+ // user defined full path to source map. let's use that instead
1212
+ bundleSourceMapOutput = await (0, react_native_utils_1.getBundleSourceMapOutput)(command, bundleName, sourcemapOutputFolder);
1213
+ }
1214
+ reactNativeBundleArgs.push("--sourcemap-output", bundleSourceMapOutput);
1175
1215
  }
1216
+ const minifyValue = await (0, react_native_utils_1.getMinifyParams)(command);
1217
+ Array.prototype.push.apply(reactNativeBundleArgs, minifyValue);
1176
1218
  if (extraBundlerOptions.length > 0) {
1177
1219
  reactNativeBundleArgs.push(...extraBundlerOptions);
1178
1220
  }
@@ -203,7 +203,13 @@ function deploymentRemove(commandName, yargs) {
203
203
  yargs
204
204
  .usage(USAGE_PREFIX + " deployment " + commandName + " <appName> <deploymentName>")
205
205
  .demand(/*count*/ 2, /*max*/ 2) // Require exactly two non-option arguments
206
- .example("deployment " + commandName + " MyApp MyDeployment", 'Removes deployment "MyDeployment" from app "MyApp"');
206
+ .example("deployment " + commandName + " MyApp MyDeployment", 'Removes deployment "MyDeployment" from app "MyApp"')
207
+ .option("force", {
208
+ default: false,
209
+ demand: false,
210
+ description: "Bypass confirmation when removing deployments",
211
+ type: "boolean",
212
+ });
207
213
  addCommonConfiguration(yargs);
208
214
  }
209
215
  function deploymentHistory(commandName, yargs) {
@@ -616,6 +622,13 @@ yargs
616
622
  default: null,
617
623
  demand: false,
618
624
  description: "Path to the gradle file which specifies the binary version you want to target this release at (android only).",
625
+ })
626
+ .option("initial", {
627
+ alias: "i",
628
+ default: false,
629
+ demand: false,
630
+ description: "Specifies whether release is initial (base) for given targetBinaryVersion.",
631
+ type: "boolean",
619
632
  })
620
633
  .option("mandatory", {
621
634
  alias: "m",
@@ -933,6 +946,7 @@ function createCommand() {
933
946
  const deploymentRemoveCommand = cmd;
934
947
  deploymentRemoveCommand.appName = arg2;
935
948
  deploymentRemoveCommand.deploymentName = arg3;
949
+ deploymentRemoveCommand.isForce = argv["force"];
936
950
  }
937
951
  break;
938
952
  case "rename":
@@ -1039,6 +1053,7 @@ function createCommand() {
1039
1053
  releaseReactCommand.entryFile = argv["entryFile"];
1040
1054
  releaseReactCommand.gradleFile = argv["gradleFile"];
1041
1055
  releaseReactCommand.mandatory = argv["mandatory"];
1056
+ releaseReactCommand.initial = argv["initial"];
1042
1057
  releaseReactCommand.noDuplicateReleaseError = argv["noDuplicateReleaseError"];
1043
1058
  releaseReactCommand.plistFile = argv["plistFile"];
1044
1059
  releaseReactCommand.plistFilePrefix = argv["plistFilePrefix"];
@@ -195,6 +195,9 @@ class AccountManager {
195
195
  getDeployment(appName, deploymentName) {
196
196
  return this.get(urlEncode([`/apps/${appName}/deployments/${deploymentName}`])).then((res) => res.body.deployment);
197
197
  }
198
+ getBaseRelease(appName, deploymentName, appVerison) {
199
+ return this.get(urlEncode([`/apps/${appName}/deployments/${deploymentName}/basebundle?appVersion=${appVerison}`])).then((res) => res.body.basebundle);
200
+ }
198
201
  renameDeployment(appName, oldDeploymentName, newDeploymentName) {
199
202
  return this.patch(urlEncode([`/apps/${appName}/deployments/${oldDeploymentName}`]), JSON.stringify({ name: newDeploymentName })).then(() => null);
200
203
  }
@@ -207,9 +210,8 @@ class AccountManager {
207
210
  getDeploymentHistory(appName, deploymentName) {
208
211
  return this.get(urlEncode([`/apps/${appName}/deployments/${deploymentName}/history`])).then((res) => res.body.history);
209
212
  }
210
- release(appName, deploymentName, filePath, targetBinaryVersion, updateMetadata, uploadProgressCallback) {
213
+ release(appName, deploymentName, filePath, updateMetadata, uploadProgressCallback) {
211
214
  return Promise((resolve, reject, notify) => {
212
- updateMetadata.appVersion = targetBinaryVersion;
213
215
  const request = superagent.post(this._serverUrl + urlEncode([`/apps/${appName}/deployments/${deploymentName}/release`]));
214
216
  this.attachCredentials(request);
215
217
  const getPackageFilePromise = Q.Promise((resolve, reject) => {
@@ -414,7 +416,7 @@ class AccountManager {
414
416
  }
415
417
  request.set("Accept", `application/vnd.code-push.v${AccountManager.API_VERSION}+json`);
416
418
  request.set("Authorization", `Bearer ${this._accessKey}`);
417
- request.set("X-CodePush-SDK-Version", packageJson.version);
419
+ request.set("X-CodePush-SDK-Version", packageJson.version); // TODO get version differently without require("../../package.json");
418
420
  }
419
421
  }
420
422
  module.exports = AccountManager;