appcenter-cli
Version:
Command line tool for Visual Studio App Center
181 lines (148 loc) • 7.54 kB
text/typescript
import { CommandResult, ErrorCodes, failure, hasArg, help, longName, shortName, defaultValue } from "../../util/commandline";
import CodePushReleaseCommandSkeleton from "./lib/release-command-skeleton";
import { AppCenterClient, models, clientRequest } from "../../util/apis";
import { out } from "../../util/interaction";
import { inspect } from "util";
import * as pfs from "../../util/misc/promisfied-fs";
import * as chalk from "chalk";
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, isValidOS, isValidPlatform, isReactNativeProject } from "./lib/react-native-utils";
const debug = require("debug")("appcenter-cli:commands:codepush:release-react");
export default class CodePushReleaseReactCommand extends CodePushReleaseCommandSkeleton {
public bundleName: string;
public development: boolean;
public entryFile: string;
public gradleFile: string;
public plistFile: string;
public plistFilePrefix: string;
public sourcemapOutput: string;
public sourcemapOutputDir: string;
public outputDir: string;
public specifiedTargetBinaryVersion: string;
public extraBundlerOptions: string | string[];
private os: string;
private platform: string;
public async run(client: AppCenterClient): Promise<CommandResult> {
if (!isReactNativeProject()) {
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...", clientRequest<models.AppResponse>(
(cb) => client.apps.get(this.app.ownerName, this.app.appName, cb)))).result;
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.outputDir || this.sourcemapOutputDir) && !this.sourcemapOutput) {
const sourcemapDir = this.sourcemapOutputDir || this.updateContentsPath;
this.sourcemapOutput = path.join(sourcemapDir, 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
} as VersionSearchParams;
this.targetBinaryVersion = await getReactNativeProjectAppVersion(versionSearchParams);
}
if (typeof this.extraBundlerOptions === "string") {
this.extraBundlerOptions = [this.extraBundlerOptions];
}
try {
createEmptyTmpReleaseFolder(this.updateContentsPath);
removeReactTmpDir();
await runReactNativeBundleCommand(this.bundleName, this.development, this.entryFile, this.updateContentsPath, this.os, this.sourcemapOutput, this.extraBundlerOptions);
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);
}
}
}
}