Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into productize-sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
Richard Hua committed Mar 8, 2016
2 parents 6bee851 + 8eac552 commit 608a804
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 35 deletions.
34 changes: 30 additions & 4 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,20 +230,37 @@ It's important that the path you specify refers to the platform-specific, prepar

### Target binary version parameter

This specifies the semver-compliant (e.g. `1.0.0` not `1.0`) store/binary version of the application you are releasing the update for. Only users running this **exact version** will receive the update. Users running an older and/or newer version of the app binary will not receive this update, for the following reasons:
This specifies the store/binary version of the application you are releasing the update for, so that only users running that version will receive the update, while users running an older and/or newer version of the app binary will not. This is useful for the following reasons:

1. If a user is running an older binary version, it's possible that there are breaking changes in the CodePush update that wouldn't be compatible with what they're running.

2. If a user is running a newer binary version, then it's presumed that what they are running is newer (and potentially incompatible) with the CodePush update.

The following table outlines the value that CodePush expects you to provide for each respective app type:
If you ever want an update to target multiple versions of the app store binary, we also allow you to specify the parameter as a [semver range expression](https://github.com/npm/node-semver#advanced-range-syntax). That way, any client device running a version of the binary that satisfies the range expression (i.e. `semver.satisfies(version, range)` returns `true`) will get the update. Examples of valid semver range expressions are as follows:

| Range Expression | Who gets the update |
|------------------|----------------------------------------------------------------------------------------|
| `1.2.3` | Only devices running the specific binary app store version `1.2.3` of your app |
| `*` | Any device configured to consume updates from your CodePush app |
| `1.2.x` | Devices running major version 1, minor version 2 and any patch version of your app |
| `1.2.3 - 1.2.7` | Devices running any binary version between `1.2.3` (inclusive) and `1.2.7` (inclusive) |
| `>=1.2.3 <1.2.7` | Devices running any binary version between `1.2.3` (inclusive) and `1.2.7` (exclusive) |
| `~1.2.3` | Equivalent to `>=1.2.3 <1.3.0` |
| `^1.2.3` | Equivalent to `>=1.2.3 <2.0.0` |

*NOTE: If your semver expression starts with a special shell character or operator such as `>`, `^`, or **
*, the command may not execute correctly if you do not wrap the value in quotes as the shell will not supply the right values to our CLI process. Therefore, it is best to wrap your `targetBinaryVersion` parameter in double quotes when calling the `release` command, e.g. `code-push release MyApp updateContents ">1.2.3"`.*

The following table outlines the version value that CodePush expects your update's semver range to satisfy for each respective app type:

| Platform | Source of app store version |
|------------------------|------------------------------------------------------------------------------|
| Cordova | The `<widget version>` attribute in the `config.xml` file |
| React Native (Android) | The `android.defaultConfig.versionName` property in your `build.gradle` file |
| React Native (iOS) | The `CFBundleShortVersionString` key in the `Info.plist` file |

*NOTE: If the app store version in the metadata files are missing a patch version, e.g. `2.0`, it will be treated as having a patch version of `0`, i.e. `2.0 -> 2.0.0`.

### Deployment name parameter

This specifies which deployment you want to release the update to. This defaults to `Staging`, but when you're ready to deploy to `Production`, or one of your own custom deployments, just explicitly set this argument.
Expand Down Expand Up @@ -287,6 +304,7 @@ code-push release-react <appName> <platform>
[--bundleName <bundleName>]
[--deploymentName <deploymentName>]
[--description <description>]
[--development <development>]
[--entryFile <entryFile>]
[--mandatory]
[--sourcemapOutput <sourcemapOutput>]
Expand All @@ -295,9 +313,9 @@ code-push release-react <appName> <platform>
This `release-react` command does two things in addition to running the vanilla `release` command described in the [previous section](#releasing-app-updates):

1. It runs the [`react-native bundle` command](#update-contents-parameter) to generate the update contents in a temporary folder
2. It infers the [`targetBinaryVersion` of this release](#target-binary-version-parameter) by reading the contents of the project's metadata (`Info.plist` if this update is for iOS clients, and `build.gradle` for Android clients).
2. It infers the [`targetBinaryVersion` of this release](#target-binary-range-parameter) by reading the contents of the project's metadata (`Info.plist` if this update is for iOS clients, and `build.gradle` for Android clients), and defaults to target only the specified version in the metadata.

It then calls the vanilla `release` command by supplying the values for the required parameters using the above information. Doing this helps you avoid the manual step of generating the update contents yourself using the `react-native bundle` command and also avoid common pitfalls such as supplying a wrong `targetBinaryVersion` parameter.
It then calls the vanilla `release` command by supplying the values for the required parameters using the above information. Doing this helps you avoid the manual step of generating the update contents yourself using the `react-native bundle` command and also avoid common pitfalls such as supplying an invalid `targetBinaryVersion` parameter.

### Platform parameter

Expand All @@ -315,6 +333,10 @@ This is the same parameter as the one described in the [above section](#deployme

This is the same parameter as the one described in the [above section](#description-parameter).

### Development parameter

This specifies whether to generate a unminified, development JS bundle. If left unspecified, this defaults to `false` where warnings are disabled and the bundle is minified.

### Entry file parameter

This specifies the relative path to the root JavaScript file of the app. If left unspecified, the command will first assume the entry file to be `index.ios.js` or `index.android.js` depending on the `platform` parameter supplied, following which it will use `index.js` if the previous file does not exist.
Expand All @@ -327,6 +349,10 @@ This is the same parameter as the one described in the [above section](#mandator

This specifies the relative path to where the sourcemap file for resulting update's JS bundle should be generated. If left unspecified, sourcemaps will not be generated.

### Target binary version parameter

This is the same parameter as the one described in the [above section](#target-binary-version-parameter). If left unspecified, the command defaults to targeting only the specified version in the project's metadata (`Info.plist` if this update is for iOS clients, and `build.gradle` for Android clients).

## Promoting updates across deployments

Once you've tested an update against a specific deployment (e.g. `Staging`), and you want to promote it "downstream" (e.g. dev->staging, staging->production), you can simply use the following command to copy the release from one deployment to another:
Expand Down
3 changes: 2 additions & 1 deletion cli/definitions/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,18 +124,19 @@ export interface IRegisterCommand extends ICommand {

export interface IReleaseBaseCommand extends ICommand {
appName: string;
appStoreVersion: string;
deploymentName: string;
description: string;
mandatory: boolean;
}

export interface IReleaseCommand extends IReleaseBaseCommand {
appStoreVersion: string;
package: string;
}

export interface IReleaseReactCommand extends IReleaseBaseCommand {
bundleName?: string;
development?: boolean;
entryFile?: string;
platform: string;
sourcemapOutput?: string;
Expand Down
4 changes: 2 additions & 2 deletions cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "code-push-cli",
"version": "1.6.1-beta",
"version": "1.7.0-beta",
"description": "Management CLI for the CodePush service",
"main": "script/cli.js",
"scripts": {
Expand Down Expand Up @@ -28,7 +28,7 @@
"base-64": "^0.1.0",
"chalk": "^1.1.0",
"cli-table": "^0.3.1",
"code-push": "1.6.0-beta",
"code-push": "1.7.0-beta",
"email-validator": "^1.0.3",
"gradle-to-js": "0.0.2",
"moment": "^2.10.6",
Expand Down
42 changes: 28 additions & 14 deletions cli/script/command-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,7 @@ function getPackageMetricsString(packageObject: PackageWithMetrics): string {
}

function getReactNativeProjectAppVersion(platform: string, projectName: string): Promise<string> {
var missingPatchVersionRegex: RegExp = /^\d+\.\d+$/;
if (platform === "ios") {
try {
var infoPlistContainingFolder: string = path.join("iOS", projectName);
Expand All @@ -764,10 +765,10 @@ function getReactNativeProjectAppVersion(platform: string, projectName: string):
}

if (parsedInfoPlist && parsedInfoPlist.CFBundleShortVersionString) {
if (semver.valid(parsedInfoPlist.CFBundleShortVersionString) === null) {
throw new Error(`Please update "${infoPlistContainingFolder}/Info.plist" to use a semver-compliant \"CFBundleShortVersionString\", for example "1.0.3".`);
} else {
if (semver.valid(parsedInfoPlist.CFBundleShortVersionString) || missingPatchVersionRegex.test(parsedInfoPlist.CFBundleShortVersionString)) {
return Q(parsedInfoPlist.CFBundleShortVersionString);
} else {
throw new Error(`The "CFBundleShortVersionString" key in "${infoPlistContainingFolder}/Info.plist" needs to have at least a major and minor version, for example "2.0" or "1.0.3".`);
}
} else {
throw new Error(`The "CFBundleShortVersionString" key does not exist in "${infoPlistContainingFolder}/Info.plist".`);
Expand All @@ -785,10 +786,10 @@ function getReactNativeProjectAppVersion(platform: string, projectName: string):
.then((buildGradle: any) => {
if (buildGradle.android && buildGradle.android.defaultConfig && buildGradle.android.defaultConfig.versionName) {
var appVersion: string = buildGradle.android.defaultConfig.versionName.replace(/"/g, "").trim();
if (semver.valid(appVersion) === null) {
throw new Error("Please update \"android/app/build.gradle\" to use a semver-compliant \"android.defaultConfig.versionName\", for example \"1.0.3\".");
} else {
if (semver.valid(appVersion) || missingPatchVersionRegex.test(appVersion)) {
return appVersion;
} else {
throw new Error("The \"android.defaultConfig.versionName\" property in \"android/app/build.gradle\" needs to have at least a major and minor version, for example \"2.0\" or \"1.0.3\".");
}
} else {
throw new Error("The \"android/app/build.gradle\" file does not include a value for android.defaultConfig.versionName.");
Expand Down Expand Up @@ -843,10 +844,9 @@ function promote(command: cli.IPromoteCommand): Promise<void> {
export var release = (command: cli.IReleaseCommand): Promise<void> => {
if (isBinaryOrZip(command.package)) {
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).");
} else if (semver.valid(command.appStoreVersion) === null) {
throw new Error("Please use a semver-compliant app store version, for example \"1.0.3\".");
}

}

throwForInvalidSemverRange(command.appStoreVersion);
var filePath: string = command.package;
var getPackageFilePromise: Promise<IPackageFile>;
var isSingleFilePackage: boolean = true;
Expand Down Expand Up @@ -968,16 +968,24 @@ export var releaseReact = (command: cli.IReleaseReactCommand): Promise<void> =>
throw new Error(`Entry file "${entryFile}" does not exist.`);
}
}

if (command.appStoreVersion) {
throwForInvalidSemverRange(command.appStoreVersion);
}

var appVersionPromise: Promise<string> = command.appStoreVersion
? Q(command.appStoreVersion)
: getReactNativeProjectAppVersion(platform, projectName);

return getReactNativeProjectAppVersion(platform, projectName)
return appVersionPromise
.then((appVersion: string) => {
releaseCommand.appStoreVersion = appVersion;
return createEmptyTempReleaseFolder(outputFolder);
})
// This is needed to clear the react native bundler cache:
// https://github.com/facebook/react-native/issues/4289
.then(() => deleteFolder(`${os.tmpdir()}/react-*`))
.then(() => runReactNativeBundleCommand(bundleName, entryFile, outputFolder, platform, command.sourcemapOutput))
.then(() => runReactNativeBundleCommand(bundleName, command.development || false, entryFile, outputFolder, platform, command.sourcemapOutput))
.then(() => {
log(chalk.cyan("\nReleasing update contents to CodePush:\n"));
return release(releaseCommand);
Expand Down Expand Up @@ -1027,12 +1035,12 @@ function requestAccessKey(): Promise<string> {
});
}

export var runReactNativeBundleCommand = (bundleName: string, entryFile: string, outputFolder: string, platform: string, sourcemapOutput: string): Promise<void> => {
export var runReactNativeBundleCommand = (bundleName: string, development: boolean, entryFile: string, outputFolder: string, platform: string, sourcemapOutput: string): Promise<void> => {
var reactNativeBundleArgs = [
path.join("node_modules", "react-native", "local-cli", "cli.js"), "bundle",
"--assets-dest", outputFolder,
"--bundle-output", path.join(outputFolder, bundleName),
"--dev", false,
"--dev", development,
"--entry-file", entryFile,
"--platform", platform,
];
Expand Down Expand Up @@ -1088,6 +1096,12 @@ function throwForInvalidEmail(email: string): void {
}
}

function throwForInvalidSemverRange(semverRange: string): void {
if (semver.validRange(semverRange) === null) {
throw new Error("Please use a semver-compliant target binary version range, for example \"1.0.0\", \"*\" or \"^1.2.3\".");
}
}

function throwForInvalidOutputFormat(format: string): void {
switch (format) {
case "json":
Expand Down
Loading

0 comments on commit 608a804

Please sign in to comment.