Skip to content

Commit 37bd8ca

Browse files
committedApr 9, 2021
Implemented updates check
1 parent 7ec5772 commit 37bd8ca

File tree

15 files changed

+307
-9
lines changed

15 files changed

+307
-9
lines changed
 

‎graphql.schema.json

+92
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,39 @@
183183
},
184184
"isDeprecated": false,
185185
"deprecationReason": null
186+
},
187+
{
188+
"name": "checkForUpdates",
189+
"description": null,
190+
"args": [
191+
{
192+
"name": "currentVersion",
193+
"description": null,
194+
"type": {
195+
"kind": "NON_NULL",
196+
"name": null,
197+
"ofType": {
198+
"kind": "SCALAR",
199+
"name": "String",
200+
"ofType": null
201+
}
202+
},
203+
"defaultValue": null,
204+
"isDeprecated": false,
205+
"deprecationReason": null
206+
}
207+
],
208+
"type": {
209+
"kind": "NON_NULL",
210+
"name": null,
211+
"ofType": {
212+
"kind": "OBJECT",
213+
"name": "UpdatesAvailability",
214+
"ofType": null
215+
}
216+
},
217+
"isDeprecated": false,
218+
"deprecationReason": null
186219
}
187220
],
188221
"inputFields": null,
@@ -789,6 +822,65 @@
789822
],
790823
"possibleTypes": null
791824
},
825+
{
826+
"kind": "OBJECT",
827+
"name": "UpdatesAvailability",
828+
"description": null,
829+
"fields": [
830+
{
831+
"name": "updateAvailable",
832+
"description": null,
833+
"args": [],
834+
"type": {
835+
"kind": "NON_NULL",
836+
"name": null,
837+
"ofType": {
838+
"kind": "SCALAR",
839+
"name": "Boolean",
840+
"ofType": null
841+
}
842+
},
843+
"isDeprecated": false,
844+
"deprecationReason": null
845+
},
846+
{
847+
"name": "newestVersion",
848+
"description": null,
849+
"args": [],
850+
"type": {
851+
"kind": "NON_NULL",
852+
"name": null,
853+
"ofType": {
854+
"kind": "SCALAR",
855+
"name": "String",
856+
"ofType": null
857+
}
858+
},
859+
"isDeprecated": false,
860+
"deprecationReason": null
861+
},
862+
{
863+
"name": "releaseUrl",
864+
"description": null,
865+
"args": [],
866+
"type": {
867+
"kind": "NON_NULL",
868+
"name": null,
869+
"ofType": {
870+
"kind": "SCALAR",
871+
"name": "String",
872+
"ofType": null
873+
}
874+
},
875+
"isDeprecated": false,
876+
"deprecationReason": null
877+
}
878+
],
879+
"inputFields": null,
880+
"interfaces": [],
881+
"enumValues": null,
882+
"possibleTypes": null
883+
},
792884
{
793885
"kind": "OBJECT",
794886
"name": "Mutation",

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@
321321
"react-router-dom": "^5.2.0",
322322
"reflect-metadata": "^0.1.13",
323323
"regenerator-runtime": "^0.13.5",
324+
"semver": "^7.3.5",
324325
"simple-git": "^2.37.0",
325326
"source-map-support": "^0.5.19",
326327
"type-graphql": "^1.1.1",

‎src/api/index.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import './src/graphql/enum/DeviceTarget';
2020
// eslint-disable-next-line import/extensions
2121
import './src/graphql/enum/UserDefineKey';
2222
import UserDefinesBuilder from './src/services/UserDefinesBuilder';
23+
import UpdatesService from './src/services/Updates';
24+
import UpdatesResolver from './src/graphql/resolvers/Updates.resolver';
2325

2426
export default class ApiServer {
2527
app: Express | undefined;
@@ -58,6 +60,13 @@ export default class ApiServer {
5860
logger
5961
)
6062
);
63+
Container.set(
64+
UpdatesService,
65+
new UpdatesService(
66+
config.configuratorGit.owner,
67+
config.configuratorGit.repositoryName
68+
)
69+
);
6170

6271
const rawRepoUrl = `https://raw.githubusercontent.com/${config.git.owner}/${config.git.repositoryName}`;
6372
Container.set(
@@ -66,7 +75,7 @@ export default class ApiServer {
6675
);
6776

6877
const schema = await buildSchema({
69-
resolvers: [FirmwareResolver, SourcesResolver],
78+
resolvers: [FirmwareResolver, SourcesResolver, UpdatesResolver],
7079
container: Container,
7180
pubSub,
7281
});

‎src/api/src/config/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { GitRepo } from '../services/Firmware';
33

44
export interface IConfig {
55
git: GitRepo;
6+
configuratorGit: GitRepo;
67
firmwaresPath: string;
78
PATH: string;
89
env: NodeJS.ProcessEnv;

‎src/api/src/graphql/resolvers/Sources.resolver.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default class SourcesResolver {
2121

2222
@Query(() => [String])
2323
async gitTags() {
24-
return new OctopusGitHubClient().loadTags(
24+
return this.gitClient.loadTags(
2525
this.config.git.owner,
2626
this.config.git.repositoryName
2727
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Arg, Query, Resolver } from 'type-graphql';
2+
import { Service } from 'typedi';
3+
import UpdatesAvailability from '../../models/UpdatesAvailability';
4+
import UpdatesService from '../../services/Updates';
5+
6+
@Service()
7+
@Resolver()
8+
export default class UpdatesResolver {
9+
constructor(private updatesService: UpdatesService) {}
10+
11+
@Query(() => UpdatesAvailability)
12+
async checkForUpdates(@Arg('currentVersion') currentVersion: string) {
13+
return this.updatesService.checkForNewerReleases(currentVersion);
14+
}
15+
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Field, ObjectType } from 'type-graphql';
2+
3+
@ObjectType('UpdatesAvailability')
4+
export default class UpdatesAvailability {
5+
@Field()
6+
updateAvailable: boolean;
7+
8+
@Field()
9+
newestVersion: string;
10+
11+
@Field()
12+
releaseUrl: string;
13+
14+
constructor(updateAvailable: boolean, newestVersion = '', releaseUrl = '') {
15+
this.updateAvailable = updateAvailable;
16+
this.newestVersion = newestVersion;
17+
this.releaseUrl = releaseUrl;
18+
}
19+
}

‎src/api/src/services/Updates/index.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Octokit } from '@octokit/rest';
2+
import { Service } from 'typedi';
3+
import semver from 'semver/preload';
4+
import UpdatesAvailability from '../../models/UpdatesAvailability';
5+
6+
@Service()
7+
export default class UpdatesService {
8+
client: Octokit;
9+
10+
constructor(private repositoryOwner: string, private repositoryName: string) {
11+
this.client = new Octokit();
12+
}
13+
14+
async checkForNewerReleases(
15+
currentVersion: string
16+
): Promise<UpdatesAvailability> {
17+
const response = await this.client.repos.listReleases({
18+
owner: this.repositoryOwner,
19+
repo: this.repositoryName,
20+
per_page: 100,
21+
});
22+
23+
if (response.status !== 200 || response.data.length === 0) {
24+
return new UpdatesAvailability(false);
25+
}
26+
27+
const newestVersion = semver.coerce(response.data[0].tag_name);
28+
if (newestVersion === null) {
29+
return new UpdatesAvailability(false);
30+
}
31+
32+
if (semver.gt(newestVersion, currentVersion)) {
33+
return new UpdatesAvailability(
34+
true,
35+
newestVersion.format(),
36+
response.data[0].html_url
37+
);
38+
}
39+
return new UpdatesAvailability(false);
40+
}
41+
}

‎src/main.dev.ts

+6
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,12 @@ const createWindow = async () => {
237237
owner: 'ExpressLRS',
238238
repositoryName: 'ExpressLRS',
239239
},
240+
configuratorGit: {
241+
cloneUrl: 'https://github.com/ExpressLRS/ExpressLRS-Configurator',
242+
url: 'https://github.com/ExpressLRS/ExpressLRS-Configurator',
243+
owner: 'ExpressLRS',
244+
repositoryName: 'ExpressLRS-Configurator',
245+
},
240246
firmwaresPath,
241247
getPlatformioPath,
242248
platformioStateTempStoragePath,

‎src/ui/components/DeviceTargetForm/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ const deviceTargetToCategory = (deviceTarget: DeviceTarget): string => {
103103
'DIY 2.4 Ghz',
104104

105105
// DIY_2400_RX_STM32_CCG_Nano_v0_5
106-
[DeviceTarget.DIY_2400_RX_STM32_CCG_Nano_v0_5]: 'DIY 2.4 Ghz',
106+
[DeviceTarget.DIY_2400_RX_STM32_CCG_Nano_v0_5_via_STLINK]: 'DIY 2.4 Ghz',
107107
[DeviceTarget.DIY_2400_RX_STM32_CCG_Nano_v0_5_via_BetaflightPassthrough]:
108108
'DIY 2.4 Ghz',
109109
};

‎src/ui/components/Header/index.tsx

+22
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import FacebookIcon from '@material-ui/icons/Facebook';
1111
import { Config } from '../../config';
1212
import LogotypeIcon from '../../../../assets/logotype.svg';
1313
import DiscordIcon from '../../../../assets/DiscordIcon.svg';
14+
import { useCheckForUpdatesQuery } from '../../gql/generated/types';
1415

1516
const useStyles = makeStyles((theme) => ({
1617
title: {
@@ -27,6 +28,11 @@ const useStyles = makeStyles((theme) => ({
2728
version: {
2829
fontSize: '0.4em',
2930
},
31+
updateAvailable: {
32+
fontSize: '0.4em',
33+
marginLeft: '0.4em',
34+
color: 'rgb(52 216 52) !important',
35+
},
3036
toolbar: {
3137
display: 'flex',
3238
justifyContent: 'space-between',
@@ -54,6 +60,11 @@ interface HeaderProps {
5460

5561
const Header: FunctionComponent<HeaderProps> = memo(({ className }) => {
5662
const styles = useStyles();
63+
const { data: updateResponse } = useCheckForUpdatesQuery({
64+
variables: {
65+
currentVersion: process.env.EXPRESSLRS_CONFIGURATOR_VERSION || '0.0.1',
66+
},
67+
});
5768
return (
5869
<AppBar position="static" color="default" className={className}>
5970
<Toolbar className={styles.toolbar}>
@@ -68,6 +79,17 @@ const Header: FunctionComponent<HeaderProps> = memo(({ className }) => {
6879
<span className={styles.version}>
6980
v{process.env.EXPRESSLRS_CONFIGURATOR_VERSION}
7081
</span>
82+
{updateResponse?.checkForUpdates?.updateAvailable && (
83+
<a
84+
href={updateResponse?.checkForUpdates?.releaseUrl}
85+
target="_blank"
86+
title="Click to download a newest release"
87+
rel="noreferrer noreferrer"
88+
className={styles.updateAvailable}
89+
>
90+
Update is available!
91+
</a>
92+
)}
7193
</Typography>
7294
</div>
7395
<div className={styles.social}>

0 commit comments

Comments
 (0)
Please sign in to comment.