Skip to content

Commit

Permalink
Merge pull request #203 from Microsoft/release-react-windows
Browse files Browse the repository at this point in the history
"release-react" command for windows
  • Loading branch information
geof90 committed Apr 13, 2016
2 parents 303cb30 + 2d14023 commit a173323
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 21 deletions.
45 changes: 34 additions & 11 deletions cli/script/command-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,7 @@ function getReactNativeProjectAppVersion(platform: string, projectName: string):
} else {
throw new Error(`The "CFBundleShortVersionString" key does not exist in "${infoPlistContainingFolder}/Info.plist".`);
}
} else {
} else if (platform === "android") {
var buildGradlePath: string = path.join("android", "app", "build.gradle");
if (fileDoesNotExistOrIsDirectory(buildGradlePath)) {
throw new Error("Unable to find or read \"build.gradle\" in the \"android/app\" folder.");
Expand All @@ -864,6 +864,26 @@ function getReactNativeProjectAppVersion(platform: string, projectName: string):
throw new Error("The \"android/app/build.gradle\" file does not include a value for android.defaultConfig.versionName.");
}
});
} else {
var appxManifestFileName: string = "Package.appxmanifest";
try {
var appxManifestContainingFolder: string = path.join("windows", projectName);
var appxManifestContents: string = fs.readFileSync(path.join(appxManifestContainingFolder, "Package.appxmanifest")).toString();
} catch (err) {
throw new Error(`Unable to find or read "${appxManifestFileName}" in the "${path.join("windows", projectName)}" folder.`);
}

return parseXml(appxManifestContents)
.catch((err: any) => {
throw new Error(`Unable to parse the "${path.join(appxManifestContainingFolder, appxManifestFileName)}" file, it could be malformed.`);
})
.then((parsedAppxManifest: any) => {
try {
return parsedAppxManifest.Package.Identity[0]["$"].Version.match(/^\d+\.\d+\.\d+/)[0];
} catch (e) {
throw new Error(`Unable to parse the package version from the "${path.join(appxManifestContainingFolder, appxManifestFileName)}" file.`);
}
});
}
}

Expand Down Expand Up @@ -1096,16 +1116,19 @@ export var releaseReact = (command: cli.IReleaseReactCommand): Promise<void> =>
var releaseCommand: cli.IReleaseCommand = <any>command;
releaseCommand.package = outputFolder;

if (platform === "ios") {
if (!bundleName) {
bundleName = "main.jsbundle";
}
} else if (platform === "android") {
if (!bundleName) {
bundleName = "index.android.bundle";
}
} else {
throw new Error("Platform must be either \"ios\" or \"android\".");
switch (platform) {
case "android":
case "ios":
case "windows":
if (!bundleName) {
bundleName = platform === "ios"
? "main.jsbundle"
: `index.${platform}.bundle`;
}

break;
default:
throw new Error("Platform must be either \"android\", \"ios\" or \"windows\".");
}

try {
Expand Down
5 changes: 3 additions & 2 deletions cli/script/command-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,8 @@ var argv = yargs.usage(USAGE_PREFIX + " <command>")
.demand(/*count*/ 3, /*max*/ 3) // Require exactly three non-option arguments.
.example("release-react MyApp ios", "Releases the React Native iOS project in the current working directory to the \"MyApp\" app's \"Staging\" deployment")
.example("release-react MyApp android -d Production", "Releases the React Native Android project in the current working directory to the \"MyApp\" app's \"Production\" deployment")
.option("bundleName", { alias: "b", default: null, demand: false, description: "Name of the generated JS bundle file. If unspecified, the standard bundle name will be used, depending on the specified platform: \"main.jsbundle\" (iOS) and \"index.android.bundle\" (Android)", type: "string" })
.example("release-react MyApp windows --dev", "Releases the development bundle of the React Native Windows project in the current working directory to the \"MyApp\" app's \"Staging\" deployment")
.option("bundleName", { alias: "b", default: null, demand: false, description: "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)", type: "string" })
.option("deploymentName", { alias: "d", default: "Staging", demand: false, description: "Deployment to release the update to", type: "string" })
.option("description", { alias: "des", default: null, demand: false, description: "Description of the changes made to the app with this release", type: "string" })
.option("development", { alias: "dev", default: false, demand: false, description: "Specifies whether to generate a dev or release build", type: "boolean" })
Expand All @@ -370,7 +371,7 @@ var argv = yargs.usage(USAGE_PREFIX + " <command>")
.option("mandatory", { alias: "m", default: false, demand: false, description: "Specifies whether this release should be considered mandatory", type: "boolean" })
.option("rollout", { alias: "r", default: "100%", demand: false, description: "Percentage of users this release should be immediately available to", type: "string" })
.option("sourcemapOutput", { alias: "s", default: null, demand: false, description: "Path to where the sourcemap for the resulting bundle should be written. If omitted, a sourcemap will not be generated.", type: "string" })
.option("targetBinaryVersion", { alias: "t", default: null, demand: false, description: "Semver expression that specifies the binary app version(s) this release is targeting (e.g. 1.1.0, ~1.2.3). If omitted, the release will target the exact version specified in the \"Info.plist\" (iOS) or \"build.gradle\" (Android) files.", type: "string" })
.option("targetBinaryVersion", { alias: "t", default: null, demand: false, description: "Semver expression that specifies the binary app version(s) this release is targeting (e.g. 1.1.0, ~1.2.3). If omitted, the release will target the exact version specified in the \"Info.plist\" (iOS), \"build.gradle\" (Android) or \"Package.appxmanifest\" (Windows) files.", type: "string" })
.check((argv: any, aliases: { [aliases: string]: string }): any => { return isValidRollout(argv); });

addCommonConfiguration(yargs);
Expand Down
59 changes: 51 additions & 8 deletions cli/test/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ function assertJsonDescribesObject(json: string, object: Object): void {
assert.equal(json, JSON.stringify(object, /*replacer=*/ null, /*spacing=*/ 2));
}

function clone<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}

function ensureInTestAppDirectory(): void {
if (!~__dirname.indexOf("/resources/TestApp")) {
process.chdir(__dirname + "/resources/TestApp");
Expand Down Expand Up @@ -1248,7 +1252,7 @@ describe("CLI", () => {
done(new Error("Did not throw error."));
})
.catch((err) => {
assert.equal(err.message, "Platform must be either \"ios\" or \"android\".");
assert.equal(err.message, "Platform must be either \"android\", \"ios\" or \"windows\".");
sinon.assert.notCalled(release);
sinon.assert.threw(releaseReact, "Error");
sinon.assert.notCalled(spawn);
Expand Down Expand Up @@ -1347,8 +1351,9 @@ describe("CLI", () => {

cmdexec.execute(command)
.then(() => {
var releaseCommand: cli.IReleaseCommand = <any>command;
releaseCommand.package = path.join(os.tmpdir(), "CodePush");
var releaseCommand: cli.IReleaseCommand = <any>clone(command);
var packagePath: string = path.join(os.tmpdir(), "CodePush");
releaseCommand.package = packagePath;
releaseCommand.appStoreVersion = "1.2.3";

sinon.assert.calledOnce(spawn);
Expand All @@ -1357,7 +1362,7 @@ describe("CLI", () => {
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", "main.jsbundle")} --dev false --entry-file index.ios.js --platform ios`
`${path.join("node_modules", "react-native", "local-cli", "cli.js")} bundle --assets-dest ${packagePath} --bundle-output ${path.join(packagePath, "main.jsbundle")} --dev false --entry-file index.ios.js --platform ios`
);
assertJsonDescribesObject(JSON.stringify(release.args[0][0], /*replacer=*/ null, /*spacing=*/ 2), releaseCommand);
done();
Expand All @@ -1383,17 +1388,55 @@ describe("CLI", () => {

cmdexec.execute(command)
.then(() => {
var releaseCommand: cli.IReleaseCommand = <any>command;
releaseCommand.package = path.join(os.tmpdir(), "CodePush");
releaseCommand.appStoreVersion = "1.2.3";
var releaseCommand: cli.IReleaseCommand = <any>clone(command);
var packagePath: string = path.join(os.tmpdir(), "CodePush");
releaseCommand.package = packagePath;
releaseCommand.appStoreVersion = "1.0.0";

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 ${packagePath} --bundle-output ${path.join(packagePath, "index.android.bundle")} --dev false --entry-file index.android.js --platform android`
);
assertJsonDescribesObject(JSON.stringify(release.args[0][0], /*replacer=*/ null, /*spacing=*/ 2), releaseCommand);
done();
})
.done();
});

it("release-react defaults bundle name to \"index.windows.bundle\" if not provided and platform is \"windows\"", (done: MochaDone): void => {
var command: cli.IReleaseReactCommand = {
type: cli.CommandType.releaseReact,
appName: "a",
appStoreVersion: null,
deploymentName: "Staging",
description: "Test default entry file",
mandatory: false,
rollout: null,
platform: "windows"
};

ensureInTestAppDirectory();

var release: Sinon.SinonSpy = sandbox.stub(cmdexec, "release", () => { return Q(<void>null) });

cmdexec.execute(command)
.then(() => {
var releaseCommand: cli.IReleaseCommand = <any>clone(command);
var packagePath = path.join(os.tmpdir(), "CodePush");
releaseCommand.package = packagePath;
releaseCommand.appStoreVersion = "1.0.0";

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", "index.android.bundle")} --dev false --entry-file index.android.js --platform android`
`${path.join("node_modules", "react-native", "local-cli", "cli.js")} bundle --assets-dest ${packagePath} --bundle-output ${path.join(packagePath, "index.windows.bundle")} --dev false --entry-file index.windows.js --platform windows`
);
assertJsonDescribesObject(JSON.stringify(release.args[0][0], /*replacer=*/ null, /*spacing=*/ 2), releaseCommand);
done();
Expand Down
Empty file.
46 changes: 46 additions & 0 deletions cli/test/resources/TestApp/windows/TestApp/Package.appxmanifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>

<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
IgnorableNamespaces="uap mp">

<Identity
Version="1.0.0.0" />

<mp:PhoneIdentity PhoneProductId="7cc2be53-6057-4824-ba24-1f73726e3357" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>

<Properties>
<DisplayName>TestApp</DisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>

<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
</Dependencies>

<Resources>
<Resource Language="x-generate"/>
</Resources>

<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="TestApp.App">
<uap:VisualElements
DisplayName="TestApp"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png"
Description="TestApp"
BackgroundColor="transparent">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png"/>
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>

<Capabilities>
<Capability Name="internetClient" />
</Capabilities>
</Package>

0 comments on commit a173323

Please sign in to comment.