Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added expo strategy and updater #1646

Merged
merged 12 commits into from
Oct 12, 2022
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ Release Please automates releases for the following flavors of repositories:
| `krm-blueprint` | [A kpt package, with 1 or more KRM files and a CHANGELOG.md](https://github.com/GoogleCloudPlatform/blueprints/tree/main/catalog/project) |
| `maven` | [Strategy for Maven projects, generates SNAPSHOT version after each release and updates `pom.xml` automatically](docs/java.md) |
| `node` | [A Node.js repository, with a package.json and CHANGELOG.md](https://github.com/yargs/yargs) |
| `expo` | [An Expo based React Native repository, with a package.json, app.json and CHANGELOG.md](https://github.com/yargs/yargs) |
| `ocaml` | [An OCaml repository, containing 1 or more opam or esy files and a CHANGELOG.md](https://github.com/grain-lang/binaryen.ml) |
| `php` | A repository with a composer.json and a CHANGELOG.md |
| `python` | [A Python repository, with a setup.py, setup.cfg, CHANGELOG.md](https://github.com/googleapis/python-storage) and optionally a pyproject.toml and a <project>/\_\_init\_\_.py |
Expand Down
45 changes: 45 additions & 0 deletions __snapshots__/app-json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
exports['AppJson updateContent updates the app versions 1'] = `
{
"expo": {
"owner": "some-owner",
"name": "Some Name",
"slug": "some-slug",
"version": "3.2.1",
"orientation": "portrait",
"icon": "./assets/icon-inverse.png",
"scheme": "someschema",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "cover",
"backgroundColor": "#FFFFFF"
},
"updates": {
"fallbackToCacheTimeout": 0,
"url": "some-url-here"
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"bundleIdentifier": "com.somedomain",
"buildNumber": "3.2.1",
"supportsTablet": true,
"config": {
"usesNonExemptEncryption": false
}
},
"android": {
"package": "com.somedomain",
"versionCode": "440030201",
"adaptiveIcon": {
"foregroundImage": "./assets/icon-inverse.png",
"backgroundColor": "#FFFFFF"
}
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}

`
16 changes: 8 additions & 8 deletions __snapshots__/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ Options:
[string]
--release-type what type of repo is a release being created
for?
[choices: "dart", "dotnet-yoshi", "elixir", "go", "go-yoshi", "helm", "java",
"java-backport", "java-bom", "java-lts", "java-yoshi", "krm-blueprint",
"maven", "node", "ocaml", "php", "php-yoshi", "python", "ruby", "ruby-yoshi",
"rust", "simple", "terraform-module"]
[choices: "dart", "dotnet-yoshi", "elixir", "expo", "go", "go-yoshi", "helm",
"java", "java-backport", "java-bom", "java-lts", "java-yoshi",
"krm-blueprint", "maven", "node", "ocaml", "php", "php-yoshi", "python",
"ruby", "ruby-yoshi", "rust", "simple", "terraform-module"]
--config-file where can the config file be found in the
project? [default: "release-please-config.json"]
--manifest-file where can the manifest file be found in the
Expand Down Expand Up @@ -227,10 +227,10 @@ Options:
[string]
--release-type what type of repo is a release being created
for?
[choices: "dart", "dotnet-yoshi", "elixir", "go", "go-yoshi", "helm", "java",
"java-backport", "java-bom", "java-lts", "java-yoshi", "krm-blueprint",
"maven", "node", "ocaml", "php", "php-yoshi", "python", "ruby", "ruby-yoshi",
"rust", "simple", "terraform-module"]
[choices: "dart", "dotnet-yoshi", "elixir", "expo", "go", "go-yoshi", "helm",
"java", "java-backport", "java-bom", "java-lts", "java-yoshi",
"krm-blueprint", "maven", "node", "ocaml", "php", "php-yoshi", "python",
"ruby", "ruby-yoshi", "rust", "simple", "terraform-module"]
--config-file where can the config file be found in the
project?
[default: "release-please-config.json"]
Expand Down
1 change: 1 addition & 0 deletions docs/customizing.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Release Please automates releases for the following flavors of repositories:
| `krm-blueprint` | [A kpt package, with 1 or more KRM files and a CHANGELOG.md](https://github.com/GoogleCloudPlatform/blueprints/tree/main/catalog/project) |
| `maven` | [Strategy for Maven projects, generates SNAPSHOT version after each release and updates `pom.xml` automatically](java.md) |
| `node` | [A Node.js repository, with a package.json and CHANGELOG.md](https://github.com/yargs/yargs) |
| `expo` | [An Expo based React Native repository, with a package.json, app.json and CHANGELOG.md](https://github.com/yargs/yargs) |
| `ocaml` | [An OCaml repository, containing 1 or more opam or esy files and a CHANGELOG.md](https://github.com/grain-lang/binaryen.ml) |
| `php` | A repository with a composer.json and a CHANGELOG.md |
| `python` | [A Python repository, with a setup.py, setup.cfg, CHANGELOG.md](https://github.com/googleapis/python-storage) and optionally a pyproject.toml and a <project>/\_\_init\_\_.py |
Expand Down
2 changes: 2 additions & 0 deletions src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {Helm} from './strategies/helm';
import {Elixir} from './strategies/elixir';
import {Dart} from './strategies/dart';
import {Node} from './strategies/node';
import {Expo} from './strategies/expo';
import {GitHub} from './github';
import {ReleaserConfig} from './manifest';
import {AlwaysBumpPatch} from './versioning-strategies/always-bump-patch';
Expand Down Expand Up @@ -88,6 +89,7 @@ const releasers: Record<string, ReleaseBuilder> = {
}),
'krm-blueprint': options => new KRMBlueprint(options),
node: options => new Node(options),
expo: options => new Expo(options),
ocaml: options => new OCaml(options),
php: options => new PHP(options),
'php-yoshi': options => new PHPYoshi(options),
Expand Down
56 changes: 56 additions & 0 deletions src/strategies/expo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {BuildUpdatesOptions} from './base';
import {Node} from './node';
import {Update} from '../update';
import {AppJson} from '../updaters/expo/app-json';
import {Version} from '../version';

/**
* Strategy for building Expo based React Native projects. This strategy extends
* the Node strategy to additionally update the `app.json` file of a project.
*/
export class Expo extends Node {
protected async buildUpdates(
options: BuildUpdatesOptions
): Promise<Update[]> {
const version = options.newVersion;
const updates = await super.buildUpdates(options);
const expoSDKVersion = await this.getExpoSDKVersion();

updates.push({
path: this.addPath('app.json'),
createIfMissing: false,
updater: new AppJson({version, expoSDKVersion}),
});

return updates;
}

/**
* Determine the Expo SDK version by parsing the package.json dependencies.
*/
async getExpoSDKVersion(): Promise<Version> {
const pkgJsonContents = await this.getPkgJsonContents();
const pkg = JSON.parse(pkgJsonContents.parsedContent);
return Version.parse(
pkg.dependencies?.expo ||
pkg.devDependencies?.expo ||
pkg.peerDependencies?.expo ||
pkg.optionalDependencies?.expo ||
'0.0.0'
);
}
}
2 changes: 1 addition & 1 deletion src/strategies/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export class Node extends BaseStrategy {
return component.match(/^@[\w-]+\//) ? component.split('/')[1] : component;
}

private async getPkgJsonContents(): Promise<GitHubFileContents> {
protected async getPkgJsonContents(): Promise<GitHubFileContents> {
if (!this.pkgJsonContents) {
try {
this.pkgJsonContents = await this.github.getFileContentsOnBranch(
Expand Down
94 changes: 94 additions & 0 deletions src/updaters/expo/app-json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {jsonStringify} from '../../util/json-stringify';
import {logger as defaultLogger, Logger} from '../../util/logger';
import {DefaultUpdater, UpdateOptions} from '../default';
import {Version} from '../../version';

export interface AppJson {
expo: {
version: string;
ios?: {
buildNumber?: string;
};
android?: {
versionCode?: string;
};
};
}

export interface AppJsonOptions extends UpdateOptions {
expoSDKVersion: Version;
}

/**
* This updates a React Natve Expo project app.json file's main, ios and android
* versions. All values except the `android.versionCode` are standard semver
* version numbers. For the `android.versionCode`, the semver number is used as
* the basis for the `versionCode`.
*/
export class AppJson extends DefaultUpdater {
expoSDKVersion: Version;
constructor(options: AppJsonOptions) {
super(options);
this.expoSDKVersion = options.expoSDKVersion;
}
/**
* Given initial file contents, return updated contents.
*/
updateContent(content: string, logger: Logger = defaultLogger): string {
const parsed = JSON.parse(content) as AppJson;

logger.info(
`updating Expo version from ${parsed.expo.version} to ${this.version}`
);
parsed.expo.version = this.version.toString();

if (parsed.expo.ios?.buildNumber) {
logger.info(
`updating iOS version from ${parsed.expo.ios.buildNumber} to ${this.version}`
);
parsed.expo.ios.buildNumber = this.version.toString();
}

if (parsed.expo.android?.versionCode) {
// Android versionCode
// https://developer.android.com/studio/publish/versioning#appversioning
let expoMajorVersion = 0;
try {
expoMajorVersion = this.expoSDKVersion.major;
} catch (e) {
// Rethrow with a nice error message.
throw new Error(
'Unable to determine the Expo SDK version for this project. Make sure that the expo package is installed for your project.'
);
}

// Implements the `versionCode` strategy described by Maxi Rosson
// @see https://medium.com/@maxirosson/versioning-android-apps-d6ec171cfd82
const versionCode =
expoMajorVersion * 10000000 +
this.version.major * 10000 +
this.version.minor * 100 +
this.version.patch;
logger.info(
`updating Android version from ${parsed.expo.android.versionCode} to ${versionCode}`
);
parsed.expo.android.versionCode = versionCode.toString();
}

return jsonStringify(parsed, content);
}
}
10 changes: 10 additions & 0 deletions test/fixtures/strategies/expo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "node-test-repo",
"version": "0.123.4",
"repository": {
"url": "git@github.com:samples/node-test-repo.git"
},
"dependencies": {
"expo": "44.0.0"
}
}
Loading