@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.
- package/bin/script/binary-utils.js +176 -0
- package/bin/script/command-executor.js +377 -44
- package/bin/script/command-parser.js +265 -1
- package/bin/script/expo-utils.js +13 -0
- package/bin/script/management-sdk.js +50 -3
- package/bin/script/react-native-utils.js +23 -2
- package/bin/script/types/cli.js +2 -0
- package/bin/script/utils/file-utils.js +33 -2
- package/bin/test/management-sdk.js +7 -0
- package/package.json +8 -3
- package/script/binary-utils.ts +209 -0
- package/script/command-executor.ts +458 -51
- package/script/command-parser.ts +295 -2
- package/script/expo-utils.ts +14 -0
- package/script/management-sdk.ts +65 -3
- package/script/react-native-utils.ts +34 -3
- package/script/types/cli.ts +13 -0
- package/script/types/rest-definitions.ts +12 -0
- package/script/types.ts +1 -0
- package/script/utils/file-utils.ts +36 -1
- package/test/management-sdk.ts +9 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
973
|
-
|
|
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
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
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
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
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
|
-
.
|
|
1000
|
-
.then((
|
|
1001
|
-
|
|
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)(
|
|
1005
|
-
|
|
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
|
-
.
|
|
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.
|
|
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 (
|
|
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("
|
|
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);
|