@revopush/code-push-cli 0.0.8-rc.0 → 0.0.8-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/script/binary-utils.js +176 -0
- package/bin/script/command-executor.js +305 -2
- package/bin/script/command-parser.js +249 -0
- package/bin/script/expo-utils.js +13 -0
- package/bin/script/management-sdk.js +45 -0
- package/bin/script/react-native-utils.js +3 -1
- package/bin/script/types/cli.js +2 -0
- package/bin/script/utils/file-utils.js +9 -3
- package/package.json +5 -2
- package/script/binary-utils.ts +209 -0
- package/script/command-executor.ts +373 -2
- package/script/command-parser.ts +279 -0
- package/script/expo-utils.ts +14 -0
- package/script/management-sdk.ts +57 -0
- package/script/react-native-utils.ts +4 -2
- package/script/types/cli.ts +10 -0
- package/script/utils/file-utils.ts +7 -1
|
@@ -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("
|
|
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);
|