From 95f79f1feea93c5448c70488497cf7ebb5cdd475 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 3 Mar 2016 15:37:10 -0800 Subject: [PATCH 01/19] target binary range --- cli/README.md | 28 ++++++--- cli/definitions/cli.ts | 2 +- cli/script/command-executor.ts | 27 +++++++-- cli/script/command-parser.ts | 17 +++--- cli/test/cli.ts | 103 +++++++++++++++++++++++++++++++-- 5 files changed, 150 insertions(+), 27 deletions(-) diff --git a/cli/README.md b/cli/README.md index 1f2b339d..05897502 100644 --- a/cli/README.md +++ b/cli/README.md @@ -206,12 +206,12 @@ When the metrics cell reports `No installs recorded`, that indicates that the se ## Releasing app updates -*NOTE: If your app is built using React Native, we have a different command that automates generating the update contents and inferring some of the parameters (e.g. `targetBinaryVersion`) from the project's metadata. Check out the section: [Releasing updates to a React Native app](#releasing-updates-to-a-react-native-app).* +*NOTE: If your app is built using React Native, we have a different command that automates generating the update contents and inferring some of the parameters (e.g. `targetBinaryRange`) from the project's metadata. Check out the section: [Releasing updates to a React Native app](#releasing-updates-to-a-react-native-app).* Once your app has been configured to query for updates against the CodePush service--using your desired deployment--you can begin pushing updates to it using the following command: ``` -code-push release +code-push release [--deploymentName ] [--description ] [--mandatory] @@ -232,15 +232,25 @@ It's important that the path you specify refers to the platform-specific, prepar | React Native wo/assets (iOS) | `react-native bundle --platform ios --entry-file --bundle-output --dev false` | Value of the `--bundle-output` option | | React Native w/assets (iOS) | `react-native bundle --platform ios --entry-file --bundle-output / --assets-dest --dev false` | Value of the `--assets-dest` option, which should represent a newly created directory that includes your assets and JS bundle | -### Target binary version parameter +### Target binary range 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 a [semver range expression](https://github.com/npm/node-semver#advanced-range-syntax) that covers all the store/binary versions that this update applies to. 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` | Devices running the binary app store version `1.2.3` of your app | +| `1.2.*` | Devices running major version 1, minor version 2 and any patch version app | +| `>=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 <2.0.0` | +| `*` | Any device configured to consume updates from your CodePush app | + +You would want to restrict updates to target specific binary versions of your app, 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: +The following table outlines the semver value that CodePush expects your range to satisfy for each respective app type: | Platform | Source of app store version | |------------------------|------------------------------------------------------------------------------| @@ -299,9 +309,9 @@ code-push release-react 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 [`targetBinaryRange` 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 a wrong `targetBinaryRange` parameter. ### Platform parameter @@ -331,6 +341,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 range parameter + +This is the same parameter as the one described in the [above section](#target-binary-range-parameter). + ## 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: diff --git a/cli/definitions/cli.ts b/cli/definitions/cli.ts index e7f54397..63b7aa01 100644 --- a/cli/definitions/cli.ts +++ b/cli/definitions/cli.ts @@ -128,13 +128,13 @@ 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; } diff --git a/cli/script/command-executor.ts b/cli/script/command-executor.ts index 34fe0df7..820ff35a 100644 --- a/cli/script/command-executor.ts +++ b/cli/script/command-executor.ts @@ -741,6 +741,7 @@ function getPackageMetricsString(packageObject: PackageWithMetrics): string { } function getReactNativeProjectAppVersion(platform: string, projectName: string): Promise { + var missingPatchVersionRegex = /^\d+\.\d+$/; if (platform === "ios") { try { var infoPlistContainingFolder: string = path.join("iOS", projectName); @@ -761,6 +762,12 @@ function getReactNativeProjectAppVersion(platform: string, projectName: string): } if (parsedInfoPlist && parsedInfoPlist.CFBundleShortVersionString) { + if (semver.valid(parsedInfoPlist.CFBundleShortVersionString) || missingPatchVersionRegex.test(parsedInfoPlist.CFBundleShortVersionString)) { + return Q(parsedInfoPlist.CFBundleShortVersionString); + } else { + throw new Error("Please update \"" + infoPlistContainingFolder + "/Info.plist\" to use a semver-compliant \"CFBundleShortVersionString\", for example \"1.0.3\"."); + } + 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 { @@ -782,10 +789,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("Please update \"android/app/build.gradle\" to use a semver-compliant \"android.defaultConfig.versionName\", for example \"1.0.3\"."); } } else { throw new Error("The \"android/app/build.gradle\" file does not include a value for android.defaultConfig.versionName."); @@ -840,8 +847,8 @@ function promote(command: cli.IPromoteCommand): Promise { export var release = (command: cli.IReleaseCommand): Promise => { 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\"."); + } else if (semver.validRange(command.appStoreVersion) === null) { + throw new Error("Please use a semver-compliant target binary version range, for example \"^1.0.3\"."); } var filePath: string = command.package; @@ -965,8 +972,16 @@ export var releaseReact = (command: cli.IReleaseReactCommand): Promise => throw new Error(`Entry file "${entryFile}" does not exist.`); } } + + if (command.appStoreVersion && semver.validRange(command.appStoreVersion) === null) { + throw new Error("Please use a semver-compliant target binary version range, for example \"^1.0.3\"."); + } + + var appVersionPromise: Promise = command.appStoreVersion + ? Q(command.appStoreVersion) + : getReactNativeProjectAppVersion(platform, projectName); - return getReactNativeProjectAppVersion(platform, projectName) + return appVersionPromise .then((appVersion: string) => { releaseCommand.appStoreVersion = appVersion; return createEmptyTempReleaseFolder(outputFolder); diff --git a/cli/script/command-parser.ts b/cli/script/command-parser.ts index ae023d87..0ac9f7d4 100644 --- a/cli/script/command-parser.ts +++ b/cli/script/command-parser.ts @@ -286,10 +286,10 @@ var argv = yargs.usage(USAGE_PREFIX + " ") addCommonConfiguration(yargs); }) .command("release", "Release a new version of your app to a specific deployment", (yargs: yargs.Argv) => { - yargs.usage(USAGE_PREFIX + " release [--deploymentName ] [--description ] [--mandatory]") + yargs.usage(USAGE_PREFIX + " release [--deploymentName ] [--description ] [--mandatory]") .demand(/*count*/ 4, /*max*/ 4) // Require exactly four non-option arguments. - .example("release MyApp app.js 1.0.3", "Release the \"app.js\" file to the \"MyApp\" app's \"Staging\" deployment, targeting the 1.0.3 binary version") - .example("release MyApp ./platforms/ios/www 1.0.3 -d Production", "Release the \"./platforms/ios/www\" folder and all its contents to the \"MyApp\" app's \"Production\" deployment, targeting the 1.0.3 binary version") + .example("release MyApp app.js \"*\"", "Release the \"app.js\" file to the \"MyApp\" app's \"Staging\" deployment, targeting any binary version using the \"*\" wildcard range syntax.") + .example("release MyApp ./platforms/ios/www 1.0.3 -d Production", "Release the \"./platforms/ios/www\" folder and all its contents to the \"MyApp\" app's \"Production\" deployment, targeting only the 1.0.3 binary version") .option("deploymentName", { alias: "d", default: "Staging", demand: false, description: "The deployment to publish the update to", type: "string" }) .option("description", { alias: "des", default: null, demand: false, description: "The description of changes made to the app with this update", type: "string" }) .option("mandatory", { alias: "m", default: false, demand: false, description: "Whether this update should be considered mandatory to the client", type: "boolean" }); @@ -297,7 +297,7 @@ var argv = yargs.usage(USAGE_PREFIX + " ") addCommonConfiguration(yargs); }) .command("release-react", "Release a new version of your React Native app to a specific deployment", (yargs: yargs.Argv) => { - yargs.usage(USAGE_PREFIX + " release-react [--deploymentName ] [--description ] [--entryFile ] [--mandatory] [--sourcemapOutput ]") + yargs.usage(USAGE_PREFIX + " release-react [--deploymentName ] [--description ] [--entryFile ] [--mandatory] [--sourcemapOutput ] [--targetBinaryRange ]") .demand(/*count*/ 3, /*max*/ 3) // Require exactly three non-option arguments. .example("release-react MyApp ios", "Release the React Native iOS project in the current working directory to the \"MyApp\" app's \"Staging\" deployment") .example("release-react MyApp android -d Production", "Release the React Native Android project in the current working directory to the \"MyApp\" app's \"Production\" deployment") @@ -306,7 +306,8 @@ var argv = yargs.usage(USAGE_PREFIX + " ") .option("description", { alias: "des", default: null, demand: false, description: "The description of changes made to the app with this update", type: "string" }) .option("entryFile", { alias: "e", default: null, demand: false, description: "The path to the root JS file. If unspecified, \"index..js\" and then \"index.js\" will be tried and used if they exist.", type: "string" }) .option("mandatory", { alias: "m", default: false, demand: false, description: "Whether this update should be considered mandatory to the client", type: "boolean" }) - .option("sourcemapOutput", { alias: "s", default: null, demand: false, description: "The path to where the sourcemap for the resulting bundle should be stored. If unspecified, sourcemaps will not be generated.", type: "string" }); + .option("sourcemapOutput", { alias: "s", default: null, demand: false, description: "The path to where the sourcemap for the resulting bundle should be stored. If unspecified, sourcemaps will not be generated.", type: "string" }) + .option("targetBinaryRange", { alias: "t", default: null, demand: false, description: "The semver range expression spanning all the binary app store versions that should get this update. If omitted, the update will default to target only the same version as the current binary version specified in \"Info.plist\" (iOS) or \"build.gradle\" (Android)", type: "string" }); addCommonConfiguration(yargs); }) @@ -326,7 +327,7 @@ var argv = yargs.usage(USAGE_PREFIX + " ") .check((argv: any, aliases: { [aliases: string]: string }): any => isValidCommandCategory) // Report unrecognized, non-hyphenated command category. .fail((msg: string) => showHelp(/*showRootDescription*/ true)) // Suppress the default error message. .argv; - + function createCommand(): cli.ICommand { var cmd: cli.ICommand; @@ -560,7 +561,8 @@ function createCommand(): cli.ICommand { releaseCommand.appName = arg1; releaseCommand.package = arg2; - releaseCommand.appStoreVersion = arg3; + // Floating points e.g. "1.2" gets parsed as a number by default, but semver requires strings. + releaseCommand.appStoreVersion = arg3.toString(); releaseCommand.deploymentName = argv["deploymentName"]; releaseCommand.description = argv["description"] ? backslash(argv["description"]) : ""; releaseCommand.mandatory = argv["mandatory"]; @@ -582,6 +584,7 @@ function createCommand(): cli.ICommand { releaseReactCommand.entryFile = argv["entryFile"]; releaseReactCommand.mandatory = argv["mandatory"]; releaseReactCommand.sourcemapOutput = argv["sourcemapOutput"]; + releaseReactCommand.appStoreVersion = argv["targetBinaryRange"]; } break; diff --git a/cli/test/cli.ts b/cli/test/cli.ts index 2311deed..b38a16f1 100644 --- a/cli/test/cli.ts +++ b/cli/test/cli.ts @@ -187,7 +187,7 @@ describe("CLI", () => { var sandbox: Sinon.SinonSandbox; var spawn: Sinon.SinonStub; var wasConfirmed = true; - const RELEASE_FAILED_ERROR_MESSAGE: string = "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)."; + const INVALID_RELEASE_FILE_ERROR_MESSAGE: string = "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)."; beforeEach((): void => { wasConfirmed = true; @@ -684,6 +684,20 @@ describe("CLI", () => { }); }); + it("release doesn't allow non valid semver ranges", (done: MochaDone): void => { + var command: cli.IReleaseCommand = { + type: cli.CommandType.release, + appName: "a", + deploymentName: "Staging", + description: "test releasing zip file", + mandatory: false, + appStoreVersion: "not semver", + package: "./resources" + }; + + releaseHelperFunction(command, done, "Please use a semver-compliant target binary version range, for example \"^1.0.3\"."); + }); + it("release doesn't allow releasing .zip file", (done: MochaDone): void => { var command: cli.IReleaseCommand = { type: cli.CommandType.release, @@ -695,7 +709,7 @@ describe("CLI", () => { package: "/fake/path/test/file.zip" }; - releaseHelperFunction(command, done); + releaseHelperFunction(command, done, INVALID_RELEASE_FILE_ERROR_MESSAGE); }); it("release doesn't allow releasing .ipa file", (done: MochaDone): void => { @@ -709,7 +723,7 @@ describe("CLI", () => { package: "/fake/path/test/file.ipa" }; - releaseHelperFunction(command, done); + releaseHelperFunction(command, done, INVALID_RELEASE_FILE_ERROR_MESSAGE); }); it("release doesn't allow releasing .apk file", (done: MochaDone): void => { @@ -723,13 +737,14 @@ describe("CLI", () => { package: "/fake/path/test/file.apk" }; - releaseHelperFunction(command, done); + releaseHelperFunction(command, done, INVALID_RELEASE_FILE_ERROR_MESSAGE); }); it("release-react fails if CWD does not contain package.json", (done: MochaDone): void => { var command: cli.IReleaseReactCommand = { type: cli.CommandType.releaseReact, appName: "a", + appStoreVersion: null, deploymentName: "Staging", description: "Test invalid folder", mandatory: false, @@ -757,6 +772,7 @@ describe("CLI", () => { var command: cli.IReleaseReactCommand = { type: cli.CommandType.releaseReact, appName: "a", + appStoreVersion: null, deploymentName: "Staging", description: "Test invalid entryFile", entryFile: "doesntexist.js", @@ -787,6 +803,7 @@ describe("CLI", () => { var command: cli.IReleaseReactCommand = { type: cli.CommandType.releaseReact, appName: "a", + appStoreVersion: null, deploymentName: "Staging", description: "Test invalid platform", mandatory: false, @@ -812,11 +829,45 @@ describe("CLI", () => { .done(); }); + it("release-react fails if targetBinaryRange is not a valid semver range expression", (done: MochaDone): void => { + var bundleName = "bundle.js"; + var command: cli.IReleaseReactCommand = { + type: cli.CommandType.releaseReact, + appName: "a", + appStoreVersion: "notsemver", + bundleName: bundleName, + deploymentName: "Staging", + description: "Test uses targetBinaryRange", + mandatory: false, + platform: "android", + sourcemapOutput: "index.android.js.map" + }; + + ensureInTestAppDirectory(); + + var release: Sinon.SinonSpy = sandbox.stub(cmdexec, "release", () => { return Q(null) }); + var releaseReact: Sinon.SinonSpy = sandbox.spy(cmdexec, "releaseReact"); + + cmdexec.execute(command) + .then(() => { + done(new Error("Did not throw error.")); + }) + .catch((err) => { + assert.equal(err.message, "Please use a semver-compliant target binary version range, for example \"^1.0.3\"."); + sinon.assert.notCalled(release); + sinon.assert.threw(releaseReact, "Error"); + sinon.assert.notCalled(spawn); + done(); + }) + .done(); + }); + it("release-react defaults entry file to index.{platform}.js if not provided", (done: MochaDone): void => { var bundleName = "bundle.js"; var command: cli.IReleaseReactCommand = { type: cli.CommandType.releaseReact, appName: "a", + appStoreVersion: null, bundleName: bundleName, deploymentName: "Staging", description: "Test default entry file", @@ -852,6 +903,7 @@ describe("CLI", () => { var command: cli.IReleaseReactCommand = { type: cli.CommandType.releaseReact, appName: "a", + appStoreVersion: null, deploymentName: "Staging", description: "Test default entry file", mandatory: false, @@ -886,6 +938,7 @@ describe("CLI", () => { var command: cli.IReleaseReactCommand = { type: cli.CommandType.releaseReact, appName: "a", + appStoreVersion: null, deploymentName: "Staging", description: "Test default entry file", mandatory: false, @@ -921,6 +974,7 @@ describe("CLI", () => { var command: cli.IReleaseReactCommand = { type: cli.CommandType.releaseReact, appName: "a", + appStoreVersion: null, bundleName: bundleName, deploymentName: "Staging", description: "Test generates sourcemaps", @@ -953,14 +1007,51 @@ describe("CLI", () => { .done(); }); - function releaseHelperFunction(command: cli.IReleaseCommand, done: MochaDone): void { + it("release-react uses specified targetBinaryRange option", (done: MochaDone): void => { + var bundleName = "bundle.js"; + var command: cli.IReleaseReactCommand = { + type: cli.CommandType.releaseReact, + appName: "a", + appStoreVersion: ">=1.0.0 <1.0.5", + bundleName: bundleName, + deploymentName: "Staging", + description: "Test uses targetBinaryRange", + mandatory: false, + platform: "android", + sourcemapOutput: "index.android.js.map" + }; + + ensureInTestAppDirectory(); + + var release: Sinon.SinonSpy = sandbox.stub(cmdexec, "release", () => { return Q(null) }); + + cmdexec.execute(command) + .then(() => { + var releaseCommand: cli.IReleaseCommand = command; + releaseCommand.package = path.join(os.tmpdir(), "CodePush"); + + sinon.assert.calledOnce(spawn); + var spawnCommand: string = spawn.args[0][0]; + var spawnCommandArgs: string = spawn.args[0][1].join(" "); + assert.equal(spawnCommand, "node"); + assert.equal( + spawnCommandArgs, + `${path.join("node_modules", "react-native", "local-cli", "cli.js")} bundle --assets-dest ${path.join(os.tmpdir(), "CodePush")} --bundle-output ${path.join(os.tmpdir(), "CodePush", bundleName)} --dev false --entry-file index.android.js --platform android --sourcemap-output index.android.js.map` + ); + assertJsonDescribesObject(JSON.stringify(release.args[0][0], /*replacer=*/ null, /*spacing=*/ 2), releaseCommand); + done(); + }) + .done(); + }); + + function releaseHelperFunction(command: cli.IReleaseCommand, done: MochaDone, expectedError: string): void { var release: Sinon.SinonSpy = sandbox.spy(cmdexec.sdk, "release"); cmdexec.execute(command) .done((): void => { throw "Error Expected"; }, (error: any): void => { assert (!!error); - assert.equal(error.message, RELEASE_FAILED_ERROR_MESSAGE); + assert.equal(error.message, expectedError); done(); }); } From 8de6af13a58d7ae2a9df7e910241a2b14a4ae6d4 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 3 Mar 2016 15:39:46 -0800 Subject: [PATCH 02/19] target binary range --- cli/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/README.md b/cli/README.md index 05897502..343f4145 100644 --- a/cli/README.md +++ b/cli/README.md @@ -238,8 +238,8 @@ This specifies a [semver range expression](https://github.com/npm/node-semver#ad | Range Expression | Who gets the update | |------------------|----------------------------------------------------------------------------------------| -| `1.2.3` | Devices running the binary app store version `1.2.3` of your app | -| `1.2.*` | Devices running major version 1, minor version 2 and any patch version app | +| `1.2.3` | Only devices running the specific binary app store version `1.2.3` of your app | +| `1.2.*` | 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` (exclusive) | | `^1.2.3` | Equivalent to `>=1.2.3 <2.0.0` | | `*` | Any device configured to consume updates from your CodePush app | From 61b053d2990ed3c09f206ab78fa38e29f2c681a1 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 3 Mar 2016 15:54:09 -0800 Subject: [PATCH 03/19] add note on special shell chars --- cli/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cli/README.md b/cli/README.md index 343f4145..95689de9 100644 --- a/cli/README.md +++ b/cli/README.md @@ -244,13 +244,15 @@ This specifies a [semver range expression](https://github.com/npm/node-semver#ad | `^1.2.3` | Equivalent to `>=1.2.3 <2.0.0` | | `*` | Any device configured to consume updates from your CodePush app | +*NOTE: If your semver expression starts with a special shell character, 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 `targetBinaryRange` parameter in double quotes when calling the `release` command, e.g. `code-push release MyApp updateContents ">1.2.3"`. + You would want to restrict updates to target specific binary versions of your app, 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 semver value that CodePush expects your range to satisfy for each respective app type: +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 | |------------------------|------------------------------------------------------------------------------| @@ -258,6 +260,8 @@ The following table outlines the semver value that CodePush expects your range t | 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. From 1a70d013220ac763823548982c71f6c841c173da Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 3 Mar 2016 15:58:21 -0800 Subject: [PATCH 04/19] add more examples in error message --- cli/README.md | 2 +- cli/script/command-executor.ts | 4 ++-- cli/test/cli.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/README.md b/cli/README.md index 95689de9..e7330a1b 100644 --- a/cli/README.md +++ b/cli/README.md @@ -244,7 +244,7 @@ This specifies a [semver range expression](https://github.com/npm/node-semver#ad | `^1.2.3` | Equivalent to `>=1.2.3 <2.0.0` | | `*` | Any device configured to consume updates from your CodePush app | -*NOTE: If your semver expression starts with a special shell character, 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 `targetBinaryRange` parameter in double quotes when calling the `release` command, e.g. `code-push release MyApp updateContents ">1.2.3"`. +*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 `targetBinaryRange` parameter in double quotes when calling the `release` command, e.g. `code-push release MyApp updateContents ">1.2.3"`. You would want to restrict updates to target specific binary versions of your app, for the following reasons: diff --git a/cli/script/command-executor.ts b/cli/script/command-executor.ts index 820ff35a..16633e10 100644 --- a/cli/script/command-executor.ts +++ b/cli/script/command-executor.ts @@ -848,7 +848,7 @@ export var release = (command: cli.IReleaseCommand): Promise => { 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.validRange(command.appStoreVersion) === null) { - throw new Error("Please use a semver-compliant target binary version range, for example \"^1.0.3\"."); + throw new Error("Please use a semver-compliant target binary version range, for example \"1.0.0\", \"*\" or \"^1.2.3\"."); } var filePath: string = command.package; @@ -974,7 +974,7 @@ export var releaseReact = (command: cli.IReleaseReactCommand): Promise => } if (command.appStoreVersion && semver.validRange(command.appStoreVersion) === null) { - throw new Error("Please use a semver-compliant target binary version range, for example \"^1.0.3\"."); + throw new Error("Please use a semver-compliant target binary version range, for example \"1.0.0\", \"*\" or \"^1.2.3\"."); } var appVersionPromise: Promise = command.appStoreVersion diff --git a/cli/test/cli.ts b/cli/test/cli.ts index b38a16f1..95d31a73 100644 --- a/cli/test/cli.ts +++ b/cli/test/cli.ts @@ -695,7 +695,7 @@ describe("CLI", () => { package: "./resources" }; - releaseHelperFunction(command, done, "Please use a semver-compliant target binary version range, for example \"^1.0.3\"."); + releaseHelperFunction(command, done, "Please use a semver-compliant target binary version range, for example \"1.0.0\", \"*\" or \"^1.2.3\"."); }); it("release doesn't allow releasing .zip file", (done: MochaDone): void => { @@ -853,7 +853,7 @@ describe("CLI", () => { done(new Error("Did not throw error.")); }) .catch((err) => { - assert.equal(err.message, "Please use a semver-compliant target binary version range, for example \"^1.0.3\"."); + assert.equal(err.message, "Please use a semver-compliant target binary version range, for example \"1.0.0\", \"*\" or \"^1.2.3\"."); sinon.assert.notCalled(release); sinon.assert.threw(releaseReact, "Error"); sinon.assert.notCalled(spawn); From 10744492a3c1f904c61da64196968154174a4190 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 3 Mar 2016 16:00:32 -0800 Subject: [PATCH 05/19] add default to docs --- cli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/README.md b/cli/README.md index e7330a1b..17d921bc 100644 --- a/cli/README.md +++ b/cli/README.md @@ -347,7 +347,7 @@ This specifies the relative path to where the sourcemap file for resulting updat ### Target binary range parameter -This is the same parameter as the one described in the [above section](#target-binary-range-parameter). +This is the same parameter as the one described in the [above section](#target-binary-range-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 From 92793cc9c8b9fabbb66782e6eee584d227f0447c Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 3 Mar 2016 16:08:58 -0800 Subject: [PATCH 06/19] add more examples to docs --- cli/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cli/README.md b/cli/README.md index 17d921bc..0177a0d9 100644 --- a/cli/README.md +++ b/cli/README.md @@ -239,12 +239,14 @@ This specifies a [semver range expression](https://github.com/npm/node-semver#ad | Range Expression | Who gets the update | |------------------|----------------------------------------------------------------------------------------| | `1.2.3` | Only devices running the specific binary app store version `1.2.3` of your app | -| `1.2.*` | Devices running major version 1, minor version 2 and any patch version 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` | -| `*` | Any device configured to consume updates from your CodePush app | -*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 `targetBinaryRange` parameter in double quotes when calling the `release` command, e.g. `code-push release MyApp updateContents ">1.2.3"`. +*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 `targetBinaryRange` parameter in double quotes when calling the `release` command, e.g. `code-push release MyApp updateContents ">1.2.3"`. You would want to restrict updates to target specific binary versions of your app, for the following reasons: From 1852e60f344f94457f06f0a9e063f458b15a99ab Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 3 Mar 2016 16:10:16 -0800 Subject: [PATCH 07/19] * --- cli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/README.md b/cli/README.md index 0177a0d9..cdb2ffcd 100644 --- a/cli/README.md +++ b/cli/README.md @@ -246,7 +246,7 @@ This specifies a [semver range expression](https://github.com/npm/node-semver#ad | `~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 `targetBinaryRange` parameter in double quotes when calling the `release` command, e.g. `code-push release MyApp updateContents ">1.2.3"`. +*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 `targetBinaryRange` parameter in double quotes when calling the `release` command, e.g. `code-push release MyApp updateContents ">1.2.3"`. You would want to restrict updates to target specific binary versions of your app, for the following reasons: From 94ae3f0b5f003b1f8d8663c30d2aa4498ba3aa09 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 3 Mar 2016 16:10:48 -0800 Subject: [PATCH 08/19] * --- cli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/README.md b/cli/README.md index cdb2ffcd..a0bfb66d 100644 --- a/cli/README.md +++ b/cli/README.md @@ -246,7 +246,7 @@ This specifies a [semver range expression](https://github.com/npm/node-semver#ad | `~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 `targetBinaryRange` parameter in double quotes when calling the `release` command, e.g. `code-push release MyApp updateContents ">1.2.3"`. +*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 `targetBinaryRange` parameter in double quotes when calling the `release` command, e.g. `code-push release MyApp updateContents ">1.2.3"`.* You would want to restrict updates to target specific binary versions of your app, for the following reasons: From da9de530165a16c9d9297f0e77c2233729870573 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 3 Mar 2016 16:25:39 -0800 Subject: [PATCH 09/19] that annoying star --- cli/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/README.md b/cli/README.md index a0bfb66d..d869ee80 100644 --- a/cli/README.md +++ b/cli/README.md @@ -246,7 +246,8 @@ This specifies a [semver range expression](https://github.com/npm/node-semver#ad | `~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 `targetBinaryRange` parameter in double quotes when calling the `release` command, e.g. `code-push release MyApp updateContents ">1.2.3"`.* +*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 `targetBinaryRange` parameter in double quotes when calling the `release` command, e.g. `code-push release MyApp updateContents ">1.2.3"`.* You would want to restrict updates to target specific binary versions of your app, for the following reasons: From d448e58feb395737554223045563b11e5d498a86 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 3 Mar 2016 16:48:51 -0800 Subject: [PATCH 10/19] update error message for invalid plist/gradle version --- cli/script/command-executor.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/cli/script/command-executor.ts b/cli/script/command-executor.ts index 16633e10..e0c468d7 100644 --- a/cli/script/command-executor.ts +++ b/cli/script/command-executor.ts @@ -765,13 +765,7 @@ function getReactNativeProjectAppVersion(platform: string, projectName: string): if (semver.valid(parsedInfoPlist.CFBundleShortVersionString) || missingPatchVersionRegex.test(parsedInfoPlist.CFBundleShortVersionString)) { return Q(parsedInfoPlist.CFBundleShortVersionString); } else { - throw new Error("Please update \"" + infoPlistContainingFolder + "/Info.plist\" to use a semver-compliant \"CFBundleShortVersionString\", for example \"1.0.3\"."); - } - - 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 { - return Q(parsedInfoPlist.CFBundleShortVersionString); + 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".`); @@ -792,7 +786,7 @@ function getReactNativeProjectAppVersion(platform: string, projectName: string): if (semver.valid(appVersion) || missingPatchVersionRegex.test(appVersion)) { return appVersion; } else { - throw new Error("Please update \"android/app/build.gradle\" to use a semver-compliant \"android.defaultConfig.versionName\", for example \"1.0.3\"."); + 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."); From b53a8fdb849df86c389fe8816acd8d5dc2ab0c6e Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Fri, 4 Mar 2016 15:35:40 -0800 Subject: [PATCH 11/19] feedback --- cli/script/command-executor.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/cli/script/command-executor.ts b/cli/script/command-executor.ts index e0c468d7..cbf9423f 100644 --- a/cli/script/command-executor.ts +++ b/cli/script/command-executor.ts @@ -741,7 +741,7 @@ function getPackageMetricsString(packageObject: PackageWithMetrics): string { } function getReactNativeProjectAppVersion(platform: string, projectName: string): Promise { - var missingPatchVersionRegex = /^\d+\.\d+$/; + var missingPatchVersionRegex: RegExp = /^\d+\.\d+$/; if (platform === "ios") { try { var infoPlistContainingFolder: string = path.join("iOS", projectName); @@ -841,10 +841,9 @@ function promote(command: cli.IPromoteCommand): Promise { export var release = (command: cli.IReleaseCommand): Promise => { 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.validRange(command.appStoreVersion) === null) { - throw new Error("Please use a semver-compliant target binary version range, for example \"1.0.0\", \"*\" or \"^1.2.3\"."); - } - + } + + throwForInvalidSemverRange(command.appStoreVersion); var filePath: string = command.package; var getPackageFilePromise: Promise; var isSingleFilePackage: boolean = true; @@ -967,8 +966,8 @@ export var releaseReact = (command: cli.IReleaseReactCommand): Promise => } } - if (command.appStoreVersion && semver.validRange(command.appStoreVersion) === null) { - throw new Error("Please use a semver-compliant target binary version range, for example \"1.0.0\", \"*\" or \"^1.2.3\"."); + if (command.appStoreVersion) { + throwForInvalidSemverRange(command.appStoreVersion); } var appVersionPromise: Promise = command.appStoreVersion @@ -1093,6 +1092,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": From dd80e150bb56e4b850e7222c0eeb791e55d3220a Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Fri, 4 Mar 2016 16:03:22 -0800 Subject: [PATCH 12/19] targetBinaryRange -> targetBinaryVersion --- cli/README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/cli/README.md b/cli/README.md index d869ee80..6b6e4b1e 100644 --- a/cli/README.md +++ b/cli/README.md @@ -206,12 +206,12 @@ When the metrics cell reports `No installs recorded`, that indicates that the se ## Releasing app updates -*NOTE: If your app is built using React Native, we have a different command that automates generating the update contents and inferring some of the parameters (e.g. `targetBinaryRange`) from the project's metadata. Check out the section: [Releasing updates to a React Native app](#releasing-updates-to-a-react-native-app).* +*NOTE: If your app is built using React Native, we have a different command that automates generating the update contents and inferring some of the parameters (e.g. `targetBinaryVersion`) from the project's metadata. Check out the section: [Releasing updates to a React Native app](#releasing-updates-to-a-react-native-app).* Once your app has been configured to query for updates against the CodePush service--using your desired deployment--you can begin pushing updates to it using the following command: ``` -code-push release +code-push release [--deploymentName ] [--description ] [--mandatory] @@ -232,9 +232,15 @@ It's important that the path you specify refers to the platform-specific, prepar | React Native wo/assets (iOS) | `react-native bundle --platform ios --entry-file --bundle-output --dev false` | Value of the `--bundle-output` option | | React Native w/assets (iOS) | `react-native bundle --platform ios --entry-file --bundle-output / --assets-dest --dev false` | Value of the `--assets-dest` option, which should represent a newly created directory that includes your assets and JS bundle | -### Target binary range parameter +### Target binary version parameter + +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. -This specifies a [semver range expression](https://github.com/npm/node-semver#advanced-range-syntax) that covers all the store/binary versions that this update applies to. 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: +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 | |------------------|----------------------------------------------------------------------------------------| @@ -247,13 +253,7 @@ This specifies a [semver range expression](https://github.com/npm/node-semver#ad | `^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 `targetBinaryRange` parameter in double quotes when calling the `release` command, e.g. `code-push release MyApp updateContents ">1.2.3"`.* - -You would want to restrict updates to target specific binary versions of your app, 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 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: @@ -316,9 +316,9 @@ code-push release-react 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 [`targetBinaryRange` 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. +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 `targetBinaryRange` 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 From 6f80933513fb88f69666b0e93bf8757febefe81e Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Fri, 4 Mar 2016 17:52:47 -0800 Subject: [PATCH 13/19] range -> version --- cli/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/README.md b/cli/README.md index 64bbc78c..a37d4e80 100644 --- a/cli/README.md +++ b/cli/README.md @@ -344,9 +344,9 @@ 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 range parameter +### Target binary version parameter -This is the same parameter as the one described in the [above section](#target-binary-range-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). +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 From b3bbb44d1eaa79e29a79f2e3aab364b1446d8627 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Fri, 4 Mar 2016 19:05:03 -0800 Subject: [PATCH 14/19] range -> version --- cli/script/command-parser.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/script/command-parser.ts b/cli/script/command-parser.ts index c8c09bda..e408b7b4 100644 --- a/cli/script/command-parser.ts +++ b/cli/script/command-parser.ts @@ -284,7 +284,7 @@ var argv = yargs.usage(USAGE_PREFIX + " ") addCommonConfiguration(yargs); }) .command("release", "Release a new version of your app to a specific deployment", (yargs: yargs.Argv) => { - yargs.usage(USAGE_PREFIX + " release [--deploymentName ] [--description ] [--mandatory]") + yargs.usage(USAGE_PREFIX + " release [--deploymentName ] [--description ] [--mandatory]") .demand(/*count*/ 4, /*max*/ 4) // Require exactly four non-option arguments. .example("release MyApp app.js \"*\"", "Release the \"app.js\" file to the \"MyApp\" app's \"Staging\" deployment, targeting any binary version using the \"*\" wildcard range syntax.") .example("release MyApp ./platforms/ios/www 1.0.3 -d Production", "Release the \"./platforms/ios/www\" folder and all its contents to the \"MyApp\" app's \"Production\" deployment, targeting only the 1.0.3 binary version") @@ -295,7 +295,7 @@ var argv = yargs.usage(USAGE_PREFIX + " ") addCommonConfiguration(yargs); }) .command("release-react", "Release a new version of your React Native app to a specific deployment", (yargs: yargs.Argv) => { - yargs.usage(USAGE_PREFIX + " release-react [--deploymentName ] [--description ] [--entryFile ] [--mandatory] [--sourcemapOutput ] [--targetBinaryRange ]") + yargs.usage(USAGE_PREFIX + " release-react [--deploymentName ] [--description ] [--entryFile ] [--mandatory] [--sourcemapOutput ] [--targetBinaryVersion ]") .demand(/*count*/ 3, /*max*/ 3) // Require exactly three non-option arguments. .example("release-react MyApp ios", "Release the React Native iOS project in the current working directory to the \"MyApp\" app's \"Staging\" deployment") .example("release-react MyApp android -d Production", "Release the React Native Android project in the current working directory to the \"MyApp\" app's \"Production\" deployment") @@ -305,7 +305,7 @@ var argv = yargs.usage(USAGE_PREFIX + " ") .option("entryFile", { alias: "e", default: null, demand: false, description: "The path to the root JS file. If unspecified, \"index..js\" and then \"index.js\" will be tried and used if they exist.", type: "string" }) .option("mandatory", { alias: "m", default: false, demand: false, description: "Whether this update should be considered mandatory to the client", type: "boolean" }) .option("sourcemapOutput", { alias: "s", default: null, demand: false, description: "The path to where the sourcemap for the resulting bundle should be stored. If unspecified, sourcemaps will not be generated.", type: "string" }) - .option("targetBinaryRange", { alias: "t", default: null, demand: false, description: "The semver range expression spanning all the binary app store versions that should get this update. If omitted, the update will default to target only the same version as the current binary version specified in \"Info.plist\" (iOS) or \"build.gradle\" (Android)", type: "string" }); + .option("targetBinaryVersion", { alias: "t", default: null, demand: false, description: "The semver range expression spanning all the binary app store versions that should get this update. If omitted, the update will default to target only the same version as the current binary version specified in \"Info.plist\" (iOS) or \"build.gradle\" (Android)", type: "string" }); addCommonConfiguration(yargs); }) @@ -578,7 +578,7 @@ function createCommand(): cli.ICommand { releaseReactCommand.entryFile = argv["entryFile"]; releaseReactCommand.mandatory = argv["mandatory"]; releaseReactCommand.sourcemapOutput = argv["sourcemapOutput"]; - releaseReactCommand.appStoreVersion = argv["targetBinaryRange"]; + releaseReactCommand.appStoreVersion = argv["targetBinaryVersion"]; } break; From 82fec6c4cad9f045fc9e5b001af8a07cbeeb97a6 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Sun, 6 Mar 2016 16:54:28 -0800 Subject: [PATCH 15/19] dev --- cli/definitions/cli.ts | 1 + cli/script/command-executor.ts | 6 +++--- cli/script/command-parser.ts | 2 ++ cli/test/cli.ts | 39 ++++++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/cli/definitions/cli.ts b/cli/definitions/cli.ts index 663c5528..9636a1a9 100644 --- a/cli/definitions/cli.ts +++ b/cli/definitions/cli.ts @@ -136,6 +136,7 @@ export interface IReleaseCommand extends IReleaseBaseCommand { export interface IReleaseReactCommand extends IReleaseBaseCommand { bundleName?: string; + development?: boolean; entryFile?: string; platform: string; sourcemapOutput?: string; diff --git a/cli/script/command-executor.ts b/cli/script/command-executor.ts index 5e76c69c..6d057e24 100644 --- a/cli/script/command-executor.ts +++ b/cli/script/command-executor.ts @@ -983,7 +983,7 @@ export var releaseReact = (command: cli.IReleaseReactCommand): Promise => // 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); @@ -1033,12 +1033,12 @@ function requestAccessKey(): Promise { }); } -export var runReactNativeBundleCommand = (bundleName: string, entryFile: string, outputFolder: string, platform: string, sourcemapOutput: string): Promise => { +export var runReactNativeBundleCommand = (bundleName: string, development: boolean, entryFile: string, outputFolder: string, platform: string, sourcemapOutput: string): Promise => { 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, ]; diff --git a/cli/script/command-parser.ts b/cli/script/command-parser.ts index e408b7b4..5975a45c 100644 --- a/cli/script/command-parser.ts +++ b/cli/script/command-parser.ts @@ -302,6 +302,7 @@ var argv = yargs.usage(USAGE_PREFIX + " ") .option("bundleName", { alias: "b", default: null, demand: false, description: "The name of the output JS bundle. If omitted, the standard bundle name will be used for the specified platform: \"main.jsbundle\" (iOS) and \"index.android.bundle\" (Android)", type: "string" }) .option("deploymentName", { alias: "d", default: "Staging", demand: false, description: "The deployment to publish the update to", type: "string" }) .option("description", { alias: "des", default: null, demand: false, description: "The description of changes made to the app with this update", type: "string" }) + .option("development", { alias: "dev", default: false, demand: false, description: "Whether to generate a unminified, development JS bundle.", type: "boolean" }) .option("entryFile", { alias: "e", default: null, demand: false, description: "The path to the root JS file. If unspecified, \"index..js\" and then \"index.js\" will be tried and used if they exist.", type: "string" }) .option("mandatory", { alias: "m", default: false, demand: false, description: "Whether this update should be considered mandatory to the client", type: "boolean" }) .option("sourcemapOutput", { alias: "s", default: null, demand: false, description: "The path to where the sourcemap for the resulting bundle should be stored. If unspecified, sourcemaps will not be generated.", type: "string" }) @@ -575,6 +576,7 @@ function createCommand(): cli.ICommand { releaseReactCommand.bundleName = argv["bundleName"]; releaseReactCommand.deploymentName = argv["deploymentName"]; releaseReactCommand.description = argv["description"] ? backslash(argv["description"]) : ""; + releaseReactCommand.development = argv["development"]; releaseReactCommand.entryFile = argv["entryFile"]; releaseReactCommand.mandatory = argv["mandatory"]; releaseReactCommand.sourcemapOutput = argv["sourcemapOutput"]; diff --git a/cli/test/cli.ts b/cli/test/cli.ts index 95d31a73..e7f3850d 100644 --- a/cli/test/cli.ts +++ b/cli/test/cli.ts @@ -969,6 +969,45 @@ describe("CLI", () => { .done(); }); + it("release-react generates dev bundle", (done: MochaDone): void => { + var bundleName = "bundle.js"; + var command: cli.IReleaseReactCommand = { + type: cli.CommandType.releaseReact, + appName: "a", + appStoreVersion: null, + bundleName: bundleName, + deploymentName: "Staging", + development: true, + description: "Test generates sourcemaps", + mandatory: false, + platform: "android", + sourcemapOutput: "index.android.js.map" + }; + + ensureInTestAppDirectory(); + + var release: Sinon.SinonSpy = sandbox.stub(cmdexec, "release", () => { return Q(null) }); + + cmdexec.execute(command) + .then(() => { + var releaseCommand: cli.IReleaseCommand = command; + releaseCommand.package = path.join(os.tmpdir(), "CodePush"); + releaseCommand.appStoreVersion = "1.2.3"; + + sinon.assert.calledOnce(spawn); + var spawnCommand: string = spawn.args[0][0]; + var spawnCommandArgs: string = spawn.args[0][1].join(" "); + assert.equal(spawnCommand, "node"); + assert.equal( + spawnCommandArgs, + `${path.join("node_modules", "react-native", "local-cli", "cli.js")} bundle --assets-dest ${path.join(os.tmpdir(), "CodePush")} --bundle-output ${path.join(os.tmpdir(), "CodePush", bundleName)} --dev true --entry-file index.android.js --platform android --sourcemap-output index.android.js.map` + ); + assertJsonDescribesObject(JSON.stringify(release.args[0][0], /*replacer=*/ null, /*spacing=*/ 2), releaseCommand); + done(); + }) + .done(); + }); + it("release-react generates sourcemaps", (done: MochaDone): void => { var bundleName = "bundle.js"; var command: cli.IReleaseReactCommand = { From 4c94bee91d5984ac2b28d240c5c021972565a5ff Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Sun, 6 Mar 2016 16:56:53 -0800 Subject: [PATCH 16/19] dev --- cli/test/cli.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/test/cli.ts b/cli/test/cli.ts index e7f3850d..26fadf0b 100644 --- a/cli/test/cli.ts +++ b/cli/test/cli.ts @@ -978,7 +978,7 @@ describe("CLI", () => { bundleName: bundleName, deploymentName: "Staging", development: true, - description: "Test generates sourcemaps", + description: "Test generates dev bundle", mandatory: false, platform: "android", sourcemapOutput: "index.android.js.map" From 41e4d44f0dc3e1bc467aa7fa6c0ec475f4bf0af2 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Sun, 6 Mar 2016 17:02:50 -0800 Subject: [PATCH 17/19] doc change --- cli/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cli/README.md b/cli/README.md index a37d4e80..c3c26e37 100644 --- a/cli/README.md +++ b/cli/README.md @@ -304,6 +304,7 @@ code-push release-react [--bundleName ] [--deploymentName ] [--description ] +[--development ] [--entryFile ] [--mandatory] [--sourcemapOutput ] @@ -332,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. + ### 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. From 7fecb3c383efd2ac081954336eed5ffc0ea6f122 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Sun, 6 Mar 2016 17:06:59 -0800 Subject: [PATCH 18/19] Update README.md --- cli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/README.md b/cli/README.md index c3c26e37..27d2c194 100644 --- a/cli/README.md +++ b/cli/README.md @@ -335,7 +335,7 @@ This is the same parameter as the one described in the [above section](#descript ### Development parameter -This specifies whether to generate a unminified, development JS bundle. +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 From 343c9ec17bd478ce527e12bbe6308d53dd572466 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Mon, 7 Mar 2016 11:20:46 -0800 Subject: [PATCH 19/19] Version bump --- cli/package.json | 4 ++-- sdk/package.json | 2 +- sdk/plugin.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/package.json b/cli/package.json index cb1ecc44..b5dafe1f 100644 --- a/cli/package.json +++ b/cli/package.json @@ -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": { @@ -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", diff --git a/sdk/package.json b/sdk/package.json index d3d781cc..3f50681a 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,6 +1,6 @@ { "name": "code-push", - "version": "1.6.0-beta", + "version": "1.7.0-beta", "description": "Management SDK for the CodePush service", "main": "script/index.js", "scripts": { diff --git a/sdk/plugin.xml b/sdk/plugin.xml index 12627f75..8a5c9ae9 100644 --- a/sdk/plugin.xml +++ b/sdk/plugin.xml @@ -1,5 +1,5 @@ - + CodePushAcquisition CodePush Acquisition Plugin for Apache Cordova MIT