@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.
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getIosVersion = exports.extractMetadataFromIOS = exports.extractMetadataFromAndroid = void 0;
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+ const chalk = require("chalk");
7
+ const command_executor_1 = require("./command-executor");
8
+ const hash_utils_1 = require("./hash-utils");
9
+ const os = require("os");
10
+ const Q = require("q");
11
+ const yazl = require("yazl");
12
+ const promises_1 = require("node:fs/promises");
13
+ const plist_1 = require("plist");
14
+ const bplist = require("bplist-parser");
15
+ async function extractMetadataFromAndroid(extractFolder, outputFolder) {
16
+ const assetsFolder = path.join(extractFolder, "assets");
17
+ if (!fs.existsSync(assetsFolder)) {
18
+ throw new Error("Invalid APK structure: assets folder not found.");
19
+ }
20
+ const codepushMetadata = path.join(assetsFolder, "CodePushMetadata");
21
+ let fileHashes = {};
22
+ if (fs.existsSync(codepushMetadata)) {
23
+ fileHashes = await takeHashesFromMetadata(codepushMetadata);
24
+ }
25
+ else {
26
+ (0, command_executor_1.log)(chalk.yellow(`\nWarning: CodepushMetadata file not found in APK. Check used version of SDK\n`));
27
+ }
28
+ // Get index.android.bundle from root of app folder
29
+ const mainJsBundlePath = path.join(assetsFolder, "index.android.bundle");
30
+ if (fs.existsSync(mainJsBundlePath)) {
31
+ // Copy bundle to output folder
32
+ const outputCodePushFolder = path.join(outputFolder, "CodePush");
33
+ fs.mkdirSync(outputCodePushFolder, { recursive: true });
34
+ const outputBundlePath = path.join(outputCodePushFolder, "index.android.bundle");
35
+ fs.copyFileSync(mainJsBundlePath, outputBundlePath);
36
+ }
37
+ else {
38
+ throw new Error("index.android.bundle not found in APK root folder.");
39
+ }
40
+ // Save packageManifest.json
41
+ const manifestPath = path.join(outputFolder, "packageManifest.json");
42
+ fs.writeFileSync(manifestPath, JSON.stringify(fileHashes, null, 2));
43
+ (0, command_executor_1.log)(chalk.cyan(`\nSaved packageManifest.json with ${Object.keys(fileHashes).length} entries.\n`));
44
+ // Create zip archive with packageManifest.json and bundle file
45
+ const zipPath = path.join(os.tmpdir(), `CodePushBinary-${Date.now()}.zip`);
46
+ await createZipArchive(outputFolder, zipPath, ["packageManifest.json", "CodePush/index.android.bundle"]);
47
+ return zipPath;
48
+ }
49
+ exports.extractMetadataFromAndroid = extractMetadataFromAndroid;
50
+ async function extractMetadataFromIOS(extractFolder, outputFolder) {
51
+ const payloadFolder = path.join(extractFolder, "Payload");
52
+ if (!fs.existsSync(payloadFolder)) {
53
+ throw new Error("Invalid IPA structure: Payload folder not found.");
54
+ }
55
+ const appFolders = fs.readdirSync(payloadFolder).filter((item) => {
56
+ const itemPath = path.join(payloadFolder, item);
57
+ return fs.statSync(itemPath).isDirectory() && item.endsWith(".app");
58
+ });
59
+ if (appFolders.length === 0) {
60
+ throw new Error("Invalid IPA structure: No .app folder found in Payload.");
61
+ }
62
+ const appFolder = path.join(payloadFolder, appFolders[0]);
63
+ const codePushFolder = path.join(appFolder, "assets");
64
+ const fileHashes = {};
65
+ if (fs.existsSync(codePushFolder)) {
66
+ await calculateHashesForDirectory(codePushFolder, appFolder, fileHashes);
67
+ }
68
+ else {
69
+ (0, command_executor_1.log)(chalk.yellow(`\nWarning: CodePush folder not found in IPA.\n`));
70
+ }
71
+ const mainJsBundlePath = path.join(appFolder, "main.jsbundle");
72
+ if (fs.existsSync(mainJsBundlePath)) {
73
+ (0, command_executor_1.log)(chalk.cyan(`\nFound main.jsbundle, calculating hash:\n`));
74
+ const bundleHash = await (0, hash_utils_1.hashFile)(mainJsBundlePath);
75
+ fileHashes["CodePush/main.jsbundle"] = bundleHash;
76
+ // Copy bundle to output folder
77
+ const outputCodePushFolder = path.join(outputFolder, "CodePush");
78
+ fs.mkdirSync(outputCodePushFolder, { recursive: true });
79
+ const outputBundlePath = path.join(outputCodePushFolder, "main.jsbundle");
80
+ fs.copyFileSync(mainJsBundlePath, outputBundlePath);
81
+ }
82
+ else {
83
+ throw new Error("main.jsbundle not found in IPA root folder.");
84
+ }
85
+ // Save packageManifest.json
86
+ const manifestPath = path.join(outputFolder, "packageManifest.json");
87
+ fs.writeFileSync(manifestPath, JSON.stringify(fileHashes, null, 2));
88
+ (0, command_executor_1.log)(chalk.cyan(`\nSaved packageManifest.json with ${Object.keys(fileHashes).length} entries.\n`));
89
+ // Create zip archive with packageManifest.json and bundle file
90
+ const zipPath = path.join(os.tmpdir(), `CodePushBinary-${Date.now()}.zip`);
91
+ await createZipArchive(outputFolder, zipPath, ["packageManifest.json", "CodePush/main.jsbundle"]);
92
+ return zipPath;
93
+ }
94
+ exports.extractMetadataFromIOS = extractMetadataFromIOS;
95
+ async function calculateHashesForDirectory(directoryPath, basePath, fileHashes) {
96
+ const items = fs.readdirSync(directoryPath);
97
+ for (const item of items) {
98
+ const itemPath = path.join(directoryPath, item);
99
+ const stat = fs.statSync(itemPath);
100
+ if (stat.isDirectory()) {
101
+ await calculateHashesForDirectory(itemPath, basePath, fileHashes);
102
+ }
103
+ else {
104
+ // Calculate relative path from basePath (app folder) to the file
105
+ const relativePath = path.relative(basePath, itemPath).replace(/\\/g, "/");
106
+ const hash = await (0, hash_utils_1.hashFile)(itemPath);
107
+ const hashKey = `CodePush/${relativePath}`;
108
+ fileHashes[hashKey] = hash;
109
+ (0, command_executor_1.log)(chalk.gray(` ${relativePath}:${hash.substring(0, 8)}...\n`));
110
+ }
111
+ }
112
+ }
113
+ async function takeHashesFromMetadata(metadataPath) {
114
+ const content = await (0, promises_1.readFile)(metadataPath, "utf-8");
115
+ const metadata = JSON.parse(content);
116
+ if (!metadata || !metadata.manifest) {
117
+ throw new Error("Failed to take manifest from metadata file of APK");
118
+ }
119
+ return Object.fromEntries(metadata.manifest.map((item) => item.split(":")));
120
+ }
121
+ function createZipArchive(sourceFolder, zipPath, filesToInclude) {
122
+ return Q.Promise((resolve, reject) => {
123
+ const zipFile = new yazl.ZipFile();
124
+ const writeStream = fs.createWriteStream(zipPath);
125
+ zipFile.outputStream
126
+ .pipe(writeStream)
127
+ .on("error", (error) => {
128
+ reject(error);
129
+ })
130
+ .on("close", () => {
131
+ resolve();
132
+ });
133
+ for (const file of filesToInclude) {
134
+ const filePath = path.join(sourceFolder, file);
135
+ if (fs.existsSync(filePath)) {
136
+ zipFile.addFile(filePath, file);
137
+ }
138
+ }
139
+ zipFile.end();
140
+ });
141
+ }
142
+ function parseAnyPlistFile(plistPath) {
143
+ const buf = fs.readFileSync(plistPath);
144
+ if (buf.slice(0, 6).toString("ascii") === "bplist") {
145
+ const arr = bplist.parseBuffer(buf);
146
+ if (!arr?.length)
147
+ throw new Error("Empty binary plist");
148
+ return arr[0];
149
+ }
150
+ const xml = buf.toString("utf8");
151
+ return plist_1.default.parse(xml);
152
+ }
153
+ async function getIosVersion(extractFolder) {
154
+ const payloadFolder = path.join(extractFolder, "Payload");
155
+ if (!fs.existsSync(payloadFolder)) {
156
+ throw new Error("Invalid IPA structure: Payload folder not found.");
157
+ }
158
+ const appFolders = fs.readdirSync(payloadFolder).filter((item) => {
159
+ const itemPath = path.join(payloadFolder, item);
160
+ return fs.statSync(itemPath).isDirectory() && item.endsWith(".app");
161
+ });
162
+ if (appFolders.length === 0) {
163
+ throw new Error("Invalid IPA structure: No .app folder found in Payload.");
164
+ }
165
+ const appFolder = path.join(payloadFolder, appFolders[0]);
166
+ const plistPath = path.join(appFolder, "Info.plist");
167
+ const data = parseAnyPlistFile(plistPath);
168
+ console.log('App Version (Short):', data.CFBundleShortVersionString);
169
+ console.log('Build Number:', data.CFBundleVersion);
170
+ console.log('Bundle ID:', data.CFBundleIdentifier);
171
+ return {
172
+ version: data.CFBundleShortVersionString,
173
+ build: data.CFBundleVersion
174
+ };
175
+ }
176
+ exports.getIosVersion = getIosVersion;
@@ -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");
@@ -413,6 +416,10 @@ function execute(command) {
413
416
  return (0, exports.release)(command);
414
417
  case cli.CommandType.releaseReact:
415
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);
416
423
  case cli.CommandType.rollback:
417
424
  return rollback(command);
418
425
  case cli.CommandType.sessionList:
@@ -985,6 +992,173 @@ const release = (command) => {
985
992
  return doRelease(command, updateMetadata);
986
993
  };
987
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+/));
1002
+ }
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);
1026
+ }
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
+ });
1051
+ });
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;
1062
+ return exports.sdk
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
+ }
1143
+ })
1144
+ .then(() => {
1145
+ (0, exports.log)(chalk.cyan("\nReleasing update contents to CodePush:\n"));
1146
+ return releaseReactNative(releaseCommand);
1147
+ })
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
+ });
1160
+ };
1161
+ exports.releaseExpo = releaseExpo;
988
1162
  const releaseReact = (command) => {
989
1163
  let bundleName = command.bundleName;
990
1164
  let entryFile = command.entryFile;
@@ -1026,6 +1200,7 @@ const releaseReact = (command) => {
1026
1200
  catch (error) {
1027
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.');
1028
1202
  }
1203
+ // TODO: check entry file detection
1029
1204
  if (!entryFile) {
1030
1205
  entryFile = `index.${platform}.js`;
1031
1206
  if ((0, file_utils_1.fileDoesNotExistOrIsDirectory)(entryFile)) {
@@ -1096,6 +1271,90 @@ const releaseReact = (command) => {
1096
1271
  }));
1097
1272
  };
1098
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;
1099
1358
  const releaseReactNative = (command) => {
1100
1359
  // for initial release we explicitly define release as optional, disabled, without rollout, with a special description
1101
1360
  const updateMetadata = {
@@ -1134,6 +1393,8 @@ const doRelease = (command, updateMetadata) => {
1134
1393
  return exports.sdk
1135
1394
  .isAuthenticated(true)
1136
1395
  .then((isAuth) => {
1396
+ (0, exports.log)("Release file path: " + filePath);
1397
+ (0, exports.log)("Metadata: " + JSON.stringify(updateMetadata));
1137
1398
  return exports.sdk.release(command.appName, command.deploymentName, filePath, updateMetadata, uploadProgress);
1138
1399
  })
1139
1400
  .then(() => {
@@ -1149,6 +1410,48 @@ const doRelease = (command, updateMetadata) => {
1149
1410
  })
1150
1411
  .catch((err) => releaseErrorHandler(err, command));
1151
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
+ };
1152
1455
  function rollback(command) {
1153
1456
  return (0, exports.confirm)().then((wasConfirmed) => {
1154
1457
  if (!wasConfirmed) {
@@ -1247,7 +1550,7 @@ function serializeConnectionInfo(accessKey, preserveAccessKeyOnLogout, customSer
1247
1550
  }
1248
1551
  const json = JSON.stringify(connectionInfo);
1249
1552
  fs.writeFileSync(configFilePath, json, { encoding: "utf8" });
1250
- (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`);
1251
1554
  }
1252
1555
  function sessionList(command) {
1253
1556
  throwForInvalidOutputFormat(command.format);