-
Notifications
You must be signed in to change notification settings - Fork 235
/
release-react.ts
256 lines (215 loc) · 9.39 KB
/
release-react.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
import { CommandResult, ErrorCodes, failure, hasArg, help, longName, shortName, defaultValue } from "../../util/commandline";
import CodePushReleaseCommandBase from "./lib/codepush-release-command-base";
import { AppCenterClient } from "../../util/apis";
import { out } from "../../util/interaction";
import { inspect } from "util";
import * as pfs from "../../util/misc/promisfied-fs";
import * as path from "path";
import * as mkdirp from "mkdirp";
import { fileDoesNotExistOrIsDirectory, createEmptyTmpReleaseFolder, removeReactTmpDir } from "./lib/file-utils";
import { isValidRange, isValidDeployment } from "./lib/validation-utils";
import {
VersionSearchParams,
getReactNativeProjectAppVersion,
runReactNativeBundleCommand,
runHermesEmitBinaryCommand,
getAndroidHermesEnabled,
getiOSHermesEnabled,
isValidOS,
isValidPlatform,
getReactNativeVersion,
} from "./lib/react-native-utils";
import * as chalk from "chalk";
const debug = require("debug")("appcenter-cli:commands:codepush:release-react");
@help("Release a React Native update to an app deployment")
export default class CodePushReleaseReactCommand extends CodePushReleaseCommandBase {
@help(
'Name of the generated JS bundle file. If unspecified, the standard bundle name will be used, depending on the specified platform: "main.jsbundle" (iOS), "index.android.bundle" (Android) or "index.windows.bundle" (Windows)'
)
@shortName("b")
@longName("bundle-name")
@hasArg
public bundleName: string;
@help("Specifies whether to generate a dev or release build")
@longName("development")
public development: boolean;
@help('Path to the app\'s entry JavaScript file. If omitted, "index.<platform>.js" and then "index.js" will be used (if they exist)')
@shortName("e")
@longName("entry-file")
@hasArg
public entryFile: string;
@help("Path to the gradle file which specifies the binary version you want to target this release at (android only)")
@shortName("g")
@longName("gradle-file")
@hasArg
public gradleFile: string;
@help("Path to the cocopods config file (iOS only)")
@longName("pod-file")
@hasArg
public podFile: string;
@help("Path to the plist file which specifies the binary version you want to target this release at (iOS only)")
@shortName("p")
@hasArg
@longName("plist-file")
public plistFile: string;
@help("Path to the Xcode project or project.pbxproj file")
@shortName("xp")
@longName("xcode-project-file")
@hasArg
public xcodeProjectFile: string;
@help("Prefix to append to the file name when attempting to find your app's Info.plist file (iOS only)")
@longName("plist-file-prefix")
@hasArg
public plistFilePrefix: string;
@help(
'Name of build configuration which specifies the binary version you want to target this release at. For example, "Debug" or "Release" (iOS only)'
)
@shortName("c")
@hasArg
@longName("build-configuration-name")
@defaultValue("Release")
public buildConfigurationName: string;
@help("Name of target (PBXNativeTarget) which specifies the binary version you want to target this release at (iOS only)")
@shortName("xt")
@longName("xcode-target-name")
@hasArg
public xcodeTargetName: string;
@help("Path to where the sourcemap for the resulting bundle should be written. If omitted, a sourcemap will not be generated")
@shortName("s")
@longName("sourcemap-output")
@hasArg
public sourcemapOutput: string;
@help(
'Path to folder where the sourcemap for the resulting bundle should be written. Name of sourcemap file will be generated automatically. This argument will be ignored if "sourcemap-output" argument is provided. If omitted, a sourcemap will not be generated'
)
@longName("sourcemap-output-dir")
@hasArg
public sourcemapOutputDir: string;
@help("Path to where the bundle should be written. If omitted, the bundle will not be saved on your machine")
@shortName("o")
@longName("output-dir")
@hasArg
public outputDir: string;
@help("Semver expression that specifies the binary app version(s) this release is targeting (e.g. 1.1.0, ~1.2.3)")
@shortName("t")
@longName("target-binary-version")
@hasArg
public specifiedTargetBinaryVersion: string;
@help("Option that gets passed to react-native bundler. Can be specified multiple times")
@longName("extra-bundler-option")
@defaultValue([])
@hasArg
public extraBundlerOptions: string | string[];
@help("Flag that gets passed to Hermes, JavaScript to bytecode compiler. Can be specified multiple times")
@longName("extra-hermes-flag")
@defaultValue([])
@hasArg
public extraHermesFlags: string | string[];
@help("Enable hermes and bypass automatic checks")
@longName("use-hermes")
public useHermes: boolean;
private os: string;
private platform: string;
public async run(client: AppCenterClient): Promise<CommandResult> {
if (!getReactNativeVersion()) {
return failure(ErrorCodes.InvalidParameter, "The project in the CWD is not a React Native project.");
}
if (!(await isValidDeployment(client, this.app, this.specifiedDeploymentName))) {
return failure(ErrorCodes.InvalidParameter, `Deployment "${this.specifiedDeploymentName}" does not exist.`);
} else {
this.deploymentName = this.specifiedDeploymentName;
}
const appInfo = await out.progress("Getting app info...", client.apps.get(this.app.ownerName, this.app.appName));
this.os = appInfo.os.toLowerCase();
this.platform = appInfo.platform.toLowerCase();
this.updateContentsPath = this.outputDir || (await pfs.mkTempDir("code-push"));
// we have to add "CodePush" root folder to make update contents file structure
// to be compatible with React Native client SDK
this.updateContentsPath = path.join(this.updateContentsPath, "CodePush");
mkdirp.sync(this.updateContentsPath);
if (!isValidOS(this.os)) {
return failure(ErrorCodes.InvalidParameter, `OS must be "android", "ios", or "windows".`);
}
if (!isValidPlatform(this.platform)) {
return failure(ErrorCodes.Exception, `Platform must be "React Native".`);
}
if (!this.bundleName) {
this.bundleName = this.os === "ios" ? "main.jsbundle" : `index.${this.os}.bundle`;
}
if (!this.entryFile) {
this.entryFile = `index.${this.os}.js`;
if (fileDoesNotExistOrIsDirectory(this.entryFile)) {
this.entryFile = "index.js";
}
if (fileDoesNotExistOrIsDirectory(this.entryFile)) {
return failure(ErrorCodes.NotFound, `Entry file "index.${this.os}.js" or "index.js" does not exist.`);
}
} else {
if (fileDoesNotExistOrIsDirectory(this.entryFile)) {
return failure(ErrorCodes.NotFound, `Entry file "${this.entryFile}" does not exist.`);
}
}
if (this.sourcemapOutputDir && this.sourcemapOutput) {
out.text('\n"sourcemap-output-dir" argument will be ignored as "sourcemap-output" argument is provided.\n');
}
if (this.sourcemapOutputDir && !this.sourcemapOutput) {
this.sourcemapOutput = path.join(this.sourcemapOutputDir, this.bundleName + ".map");
}
this.targetBinaryVersion = this.specifiedTargetBinaryVersion;
if (this.targetBinaryVersion && !isValidRange(this.targetBinaryVersion)) {
return failure(ErrorCodes.InvalidParameter, "Invalid binary version(s) for a release.");
} else if (!this.targetBinaryVersion) {
const versionSearchParams: VersionSearchParams = {
os: this.os,
plistFile: this.plistFile,
plistFilePrefix: this.plistFilePrefix,
gradleFile: this.gradleFile,
buildConfigurationName: this.buildConfigurationName,
xcodeTargetName: this.xcodeTargetName,
projectFile: this.xcodeProjectFile,
} as VersionSearchParams;
this.targetBinaryVersion = await getReactNativeProjectAppVersion(versionSearchParams);
}
if (typeof this.extraBundlerOptions === "string") {
this.extraBundlerOptions = [this.extraBundlerOptions];
}
if (typeof this.extraHermesFlags === "string") {
this.extraHermesFlags = [this.extraHermesFlags];
}
try {
createEmptyTmpReleaseFolder(this.updateContentsPath);
removeReactTmpDir();
await runReactNativeBundleCommand(
this.bundleName,
this.development,
this.entryFile,
this.updateContentsPath,
this.os,
this.sourcemapOutput,
this.extraBundlerOptions
);
const isHermesEnabled =
this.useHermes ||
(this.os === "android" && (await getAndroidHermesEnabled(this.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
(this.os === "ios" && (await getiOSHermesEnabled(this.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
if (isHermesEnabled) {
await runHermesEmitBinaryCommand(
this.bundleName,
this.updateContentsPath,
this.sourcemapOutput,
this.extraHermesFlags,
this.gradleFile
);
}
out.text(chalk.cyan("\nReleasing update contents to CodePush:\n"));
return await this.release(client);
} catch (error) {
debug(`Failed to release a CodePush update - ${inspect(error)}`);
return failure(ErrorCodes.Exception, "Failed to release a CodePush update.");
} finally {
if (!this.outputDir) {
await pfs.rmDir(this.updateContentsPath);
}
}
}
}