├── .gitignore ├── .travis.yml ├── README.md ├── backend ├── db.js ├── get-chromium-builds │ ├── index.js │ └── run.js ├── index.js ├── manifest.yml ├── package.json └── scraper.js ├── frontend ├── package.json ├── public │ ├── _redirects │ └── index.html └── src │ ├── App.js │ ├── Footer.js │ ├── Header.js │ ├── NotFound.js │ ├── ReleaseDownloads.js │ ├── ReleaseFilter.js │ ├── ReleaseTable.js │ ├── index.css │ ├── index.js │ └── util.js ├── package.json ├── renovate.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 10 5 | 6 | script: 7 | - yarn build 8 | 9 | sudo: false 10 | 11 | cache: 12 | npm: true 13 | directories: 14 | # cypress 15 | - ~/.cache 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | chromium-downloads 2 | ========== 3 | 4 | > [!Caution] 5 | > This service is no longer maintained. We recommend using an alternative for downloading Chromium like: 6 | 7 | --- 8 | 9 | Live at: 10 | 11 | ![image](https://user-images.githubusercontent.com/1151760/52878049-c29d0000-3129-11e9-8c71-e9497fc7a253.png) 12 | 13 | ### Installing dependencies 14 | 15 | ``` 16 | yarn 17 | ``` 18 | 19 | ### Starting the dev server 20 | 21 | ``` 22 | yarn start 23 | ``` 24 | -------------------------------------------------------------------------------- /backend/db.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require("sequelize"); 2 | 3 | let DATABASE_URL = process.env.DATABASE_URL; 4 | 5 | if (process.env.NODE_ENV === "production") { 6 | DATABASE_URL += "?ssl=true"; 7 | } 8 | 9 | const sequelize = new Sequelize(DATABASE_URL); 10 | 11 | class Build extends Sequelize.Model {} 12 | Build.init( 13 | { 14 | version: Sequelize.STRING, 15 | os: Sequelize.STRING, 16 | channel: Sequelize.STRING, 17 | timestamp: Sequelize.DATE, 18 | baseRevision: Sequelize.STRING, 19 | artifactsRevision: Sequelize.STRING, 20 | downloads: Sequelize.JSONB, 21 | }, 22 | { 23 | sequelize, 24 | modelName: "builds", 25 | timestamps: false, 26 | indexes: [ 27 | { 28 | unique: true, 29 | fields: ["version", "os", "channel", "timestamp"], 30 | }, 31 | ], 32 | pool: { 33 | max: 4, 34 | min: 1, 35 | }, 36 | } 37 | ); 38 | 39 | async function initialize() { 40 | console.log(sequelize); 41 | sequelize.sync(); 42 | } 43 | 44 | module.exports = { 45 | initialize, 46 | Build, 47 | }; 48 | -------------------------------------------------------------------------------- /backend/get-chromium-builds/index.js: -------------------------------------------------------------------------------- 1 | const got = require('got') 2 | 3 | // Cypress's minimum browser version is 64 4 | const BASES_TO_CHECK = 120 5 | 6 | var osInfo = { 7 | 'win64': { 8 | name: 'Windows (x64)', 9 | baseDir: 'Win_x64', 10 | files: [ 11 | { 12 | type: 'installer', 13 | filename: 'mini_installer.exe' 14 | }, 15 | { 16 | type: 'archive', 17 | filename: 'chrome-win.zip' 18 | }, 19 | { 20 | type: 'archive', 21 | filename: 'chrome-win32.zip' 22 | } 23 | ] 24 | }, 25 | 'win': { 26 | name: 'Windows (x86)', 27 | baseDir: 'Win' 28 | }, 29 | 'mac': { 30 | name: 'Mac OS', 31 | baseDir: 'Mac', 32 | files: [ 33 | { 34 | type: 'archive', 35 | filename: 'chrome-mac.zip' 36 | } 37 | ] 38 | }, 39 | 'linux': { 40 | name: 'Linux', 41 | baseDir: 'Linux_x64', 42 | files: [ 43 | { 44 | type: 'archive', 45 | filename: 'chrome-linux.zip' 46 | } 47 | ] 48 | } 49 | } 50 | 51 | osInfo.win.files = osInfo.win64.files 52 | 53 | function getStorageApiUrl(os, base) { 54 | return `https://www.googleapis.com/storage/v1/b/chromium-browser-snapshots/o?delimiter=/&prefix=${osInfo[os].baseDir}/${base}/&fields=items(kind,mediaLink,metadata,name,size,updated),kind,prefixes,nextPageToken` 55 | } 56 | 57 | function findInitialBase(version) { 58 | return got(`https://chromiumdash.appspot.com/fetch_version?version=${version}`, { json: true }) 59 | .then(({ body }) => { 60 | if (!body['chromium_main_branch_position']) { 61 | throw new Error(`Initial base position not found for Chromium ${version}`) 62 | } 63 | return Number(body['chromium_main_branch_position']) 64 | }) 65 | } 66 | 67 | function findDownloadsAtBase(os, base) { 68 | return got(getStorageApiUrl(os, base), { json: true }) 69 | } 70 | 71 | function getDownloads(os, version) { 72 | return findInitialBase(version) 73 | .then(base => { 74 | let foundBase = base 75 | 76 | const tryBase = () => 77 | findDownloadsAtBase(os, foundBase) 78 | .then(({ body }) => { 79 | if (body.items && body.items.length > 0) { 80 | return body.items 81 | } 82 | 83 | if (foundBase - base > BASES_TO_CHECK) { 84 | throw new Error("Reached maximum base check limit before finding artifact.") 85 | } 86 | 87 | foundBase += 1 88 | return tryBase() 89 | }) 90 | 91 | return tryBase() 92 | .then(items => { 93 | return items.map(item => { 94 | const basename = item.name.slice(item.name.lastIndexOf('/') + 1) 95 | const knownFile = osInfo[os].files.find(file => file.filename === basename) 96 | 97 | return { 98 | name: item.name, 99 | basename, 100 | url: item.mediaLink, 101 | size: item.size, 102 | info: knownFile 103 | } 104 | }) 105 | }) 106 | .then(downloads => { 107 | return { 108 | downloads, 109 | base, 110 | foundBase 111 | } 112 | }) 113 | }) 114 | } 115 | 116 | function getBuilds() { 117 | return got('https://versionhistory.googleapis.com/v1/chrome/platforms/all/channels/all/versions/all/releases?filter=starttime%3E2021-01-01T00:00:00Z', { json: true }) 118 | .then(({ body: releaseHistory}) => { 119 | return releaseHistory.releases.map(release => { 120 | release.timestamp = release.serving.startTime 121 | release.version = release.name.toString().split("/")[6] 122 | release.os = release.name.toString().split("/")[2] 123 | release.channel = release.name.toString().split("/")[4] 124 | 125 | if (!osInfo[release.os]) { 126 | return false 127 | } 128 | 129 | release.getDownloads = () => { 130 | return getDownloads(release.os, release.version) 131 | } 132 | 133 | return release 134 | }) 135 | }) 136 | } 137 | 138 | module.exports = { 139 | getBuilds, 140 | getDownloads, 141 | osInfo 142 | } 143 | -------------------------------------------------------------------------------- /backend/get-chromium-builds/run.js: -------------------------------------------------------------------------------- 1 | const { getBuilds } = require('.') 2 | 3 | getBuilds() 4 | .then(builds => { 5 | console.log(builds[1]) 6 | return builds[1].getDownloads() 7 | }) 8 | .then(console.log) 9 | -------------------------------------------------------------------------------- /backend/index.js: -------------------------------------------------------------------------------- 1 | const db = require("./db"); 2 | const express = require("express"); 3 | const scraper = require("./scraper"); 4 | 5 | const PORT = Number(process.env.PORT) || 3001; 6 | 7 | const app = express(); 8 | 9 | app.use((req, res, next) => { 10 | res.setHeader("access-control-allow-origin", "*"); 11 | 12 | next(); 13 | }); 14 | 15 | app.get("/builds", (req, res) => { 16 | db.Build.findAll({ 17 | attributes: ["version", "os", "channel", "timestamp"], 18 | order: [["timestamp", "DESC"]], 19 | }).then((builds) => { 20 | res.json(builds); 21 | }); 22 | }); 23 | 24 | app.get("/builds/:version/:channel/:os", (req, res) => { 25 | db.Build.findAll({ 26 | where: { 27 | channel: req.params.channel, 28 | os: req.params.os, 29 | version: req.params.version, 30 | }, 31 | }).then((builds) => { 32 | if (!builds.length) { 33 | return res.sendStatus(404); 34 | } 35 | 36 | res.json(builds[0]); 37 | }); 38 | }); 39 | 40 | console.log("Initializing"); 41 | 42 | console.log(db.initialize); 43 | 44 | db.initialize() 45 | .then(() => { 46 | console.log("Starting scraping"); 47 | scraper.start(); 48 | 49 | app.listen(PORT, () => { 50 | console.log(`Backend listening on ${PORT}.`); 51 | }); 52 | }) 53 | .catch((e) => { 54 | console.error(e); 55 | }); 56 | -------------------------------------------------------------------------------- /backend/manifest.yml: -------------------------------------------------------------------------------- 1 | applications: 2 | - name: chromium-downloads-backend 3 | memory: 256M 4 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "bluebird": "^3.5.5", 7 | "express": "^4.17.1", 8 | "got": "^9.6.0", 9 | "nodemon": "^1.19.1", 10 | "pg": "^8.8.0", 11 | "sequelize": "^6.28.0" 12 | }, 13 | "scripts": { 14 | "start": "node index.js", 15 | "start:watch": "nodemon ./index.js", 16 | "deploy": "bluemix cf push" 17 | }, 18 | "main": "index" 19 | } 20 | -------------------------------------------------------------------------------- /backend/scraper.js: -------------------------------------------------------------------------------- 1 | const { Build } = require('./db') 2 | const { getBuilds } = require('./get-chromium-builds') 3 | const Promise = require('bluebird') 4 | 5 | function saveBuild(build) { 6 | return Build.create(build) 7 | } 8 | 9 | function scrape() { 10 | console.log('Getting builds') 11 | return getBuilds() 12 | .then(builds => { 13 | return Promise.map(builds, (build) => { 14 | if (build.os && build.version && build.channel) { 15 | console.log(`Getting downloads for Chromium ${build.version} ${build.channel} on ${build.os}`) 16 | return build.getDownloads() 17 | .then(({ downloads, base, foundBase }) => { 18 | console.log(`Received downloads for Chromium ${build.version} ${build.channel} on ${build.os}`) 19 | build.downloads = downloads 20 | build.baseRevision = base 21 | build.artifactsRevision = foundBase 22 | return build 23 | }) 24 | .then(saveBuild) 25 | .catch(() => { 26 | console.error(`Had an error storing downloads for Chromium ${build.version} ${build.channel} on ${build.os}`) 27 | return 28 | }) 29 | } 30 | }) 31 | }) 32 | } 33 | 34 | function start() { 35 | return scrape() 36 | .then(() => { 37 | return Promise.delay(5 * 60 * 1000) 38 | }) 39 | .then(start) 40 | } 41 | 42 | module.exports = { 43 | start 44 | } 45 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@blueprintjs/core": "3.15.1", 7 | "file-size": "1.0.0", 8 | "react": "16.8.6", 9 | "react-dom": "16.8.6", 10 | "react-router-dom": "4.4.0-beta.8", 11 | "react-scripts": "2.1.8", 12 | "concurrently": "^4.1.1", 13 | "watch": "^1.0.2" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject" 20 | }, 21 | "eslintConfig": { 22 | "extends": "react-app" 23 | }, 24 | "browserslist": [ 25 | ">0.2%", 26 | "not dead", 27 | "not ie <= 11", 28 | "not op_mini all" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /frontend/public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | Chromium Downloads Tool 11 | 12 | 13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { BrowserRouter, Route, Switch } from 'react-router-dom'; 3 | import { osKeys, channelKeys } from './util' 4 | import Header from './Header'; 5 | import Footer from './Footer'; 6 | import NotFound from './NotFound'; 7 | import ReleaseTable from './ReleaseTable'; 8 | import ReleaseDownloads from './ReleaseDownloads'; 9 | 10 | const channelRegex = channelKeys.join('|') 11 | const osRegex = osKeys.join('|') 12 | const versionRegex = "\\d+\\.\\d+\\.\\d+\\.\\d+" 13 | 14 | class App extends Component { 15 | render() { 16 | return ( 17 | 18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
30 | ); 31 | } 32 | } 33 | 34 | export default App; 35 | -------------------------------------------------------------------------------- /frontend/src/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card } from '@blueprintjs/core'; 3 | 4 | export default class Footer extends React.PureComponent { 5 | render() { 6 | return ( 7 | 8 | Got a feature request or an issue? Post it on the GitHub repo!
9 | The Chromium™ open source project is a registered trademark of Google LLC. All rights reserved. 10 |
11 | ) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { Card, H1 } from '@blueprintjs/core'; 4 | 5 | export default class Header extends React.PureComponent { 6 | render() { 7 | return ( 8 | 9 | 10 |

Chromium Downloads Tool

11 | 12 |
13 | ) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { NonIdealState, Card } from '@blueprintjs/core' 4 | 5 | export default class NotFound extends React.PureComponent { 6 | render() { 7 | return ( 8 | 9 | Go home}/> 13 | 14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/ReleaseDownloads.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Button, NonIdealState, Spinner, Card, H2, H4, HTMLTable, Icon, Breadcrumbs, Tag } from '@blueprintjs/core' 3 | import { Link } from 'react-router-dom' 4 | import { osInfo, getHumanReadableSize, channelInfo } from './util' 5 | 6 | export default class ReleaseDownloads extends React.Component { 7 | constructor(props) { 8 | super(props) 9 | this.state = { 10 | logs: [], 11 | chromiumBaseCur: undefined, 12 | downloads: [], 13 | loaded: false, 14 | error: false 15 | } 16 | this.basesChecked = 0 17 | } 18 | 19 | componentDidMount() { 20 | this.releaseOs = this.props.match.params.releaseOs 21 | this.releaseVersion = this.props.match.params.releaseVersion 22 | this.releaseChannel = this.props.match.params.releaseChannel 23 | this._findDownloads() 24 | .then(({ downloads, baseRevision, artifactsRevision }) => { 25 | this.setState({ loaded: true, downloads, baseRevision, artifactsRevision }) 26 | }) 27 | .catch(error => { 28 | this.setState({ error }) 29 | }) 30 | } 31 | 32 | render() { 33 | if (this.state.error) { 34 | return this._renderError() 35 | } 36 | if (this.state.loaded) { 37 | return this._renderDownloads() 38 | } 39 | return this._renderLoading() 40 | } 41 | 42 | _renderDownloads() { 43 | let unknownDownloads = [] 44 | const title = `Chromium ${this.releaseVersion} for ${osInfo[this.releaseOs].name}` 45 | document.title = title 46 | return ( 47 | <> 48 | 49 | All Releases }, 52 | { text: {osInfo[this.releaseOs].name} Releases }, 53 | {} 54 | ] 55 | }/> 56 |

{title}

57 |
58 | Release channel: {this.releaseChannel}{' '} 59 | Base revision: {this.state.baseRevision}.{' '} 60 | Found build artifacts at {this.state.artifactsRevision} [browse files] 61 |
62 |
63 | 64 |

Archives and installers

65 | 66 | 67 | 68 | 69 | Filename 70 | File Size 71 | 72 | 73 | 74 | {this.state.downloads.map(download => { 75 | const knownFile = osInfo[this.releaseOs].files.find(file => file.filename === download.basename) 76 | if (!knownFile) { 77 | unknownDownloads.push(download) 78 | return false 79 | } 80 | return ( 81 | 82 | 83 | 84 | {download.basename} ({knownFile.name}) 85 | 86 | 87 | {getHumanReadableSize(download.size)} 88 | 89 | 90 | ) 91 | }).filter(a=>a)} 92 | 93 | 94 |
95 | 96 |

Other files

97 | 98 | 99 | 100 | 101 | Filename 102 | File Size 103 | 104 | 105 | 106 | {unknownDownloads.map(download => { 107 | return ( 108 | 109 | 110 | 111 | {download.basename} 112 | 113 | 114 | {getHumanReadableSize(download.size)} 115 | 116 | 117 | ) 118 | }).filter(a=>a)} 119 | 120 | 121 |
122 | 123 | ) 124 | } 125 | 126 | _renderLoading() { 127 | return ( 128 | 129 | } 130 | description={this.state.logs.map((s, i) => {s}
)} 131 | title="Loading" 132 | /> 133 |
134 | ) 135 | } 136 | 137 | _renderError() { 138 | return ( 139 | 140 | 142 |
143 | An error occurred while trying to find artifacts:
144 | "{this.state.error.message}"

145 | Please try a different release or come back later.
146 |
147 |
148 |
149 | Logs:
150 | {this.state.logs.map((s, i) => {s}
)} 151 |
152 | 153 | } 154 | action={} 155 | title="Error Finding Artifacts" 156 | /> 157 |
158 | ) 159 | } 160 | 161 | _findDownloads() { 162 | this.setState({ 163 | logs: this._logWith(`Loading downloads...`) 164 | }) 165 | return fetch(`${window.API_URL}/builds/${this.releaseVersion}/${this.releaseChannel}/${this.releaseOs}`) 166 | .then(response => response.json()) 167 | } 168 | 169 | _logWith(msg) { 170 | return [msg, ...this.state.logs] 171 | } 172 | 173 | _getCrRevUrl(base) { 174 | return `https://crrev.com/${base}` 175 | } 176 | 177 | _getStorageBrowserUrl(base) { 178 | return `https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=${osInfo[this.releaseOs].baseDir}/${base}/` 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /frontend/src/ReleaseFilter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Button, ButtonGroup, Popover, Checkbox, Tag, Position } from '@blueprintjs/core' 3 | import { osInfo, osKeys, channelInfo, channelKeys } from './util' 4 | 5 | export default class ReleaseFilter extends React.PureComponent { 6 | render() { 7 | return ( 8 | 9 | {!this.props.hideOs && this._renderFilters('Operating System', 'os', osKeys, osKeys.map(osKey => osInfo[osKey].name))} 10 | {this._renderFilters('Release Channel', 'channel', channelKeys, channelKeys.map(channelKey => { 11 | return {channelKey} 12 | }))} 13 | {this._renderFilters('Major Version', 'majorVersion', this.props.majorVersions, this.props.majorVersions)} 14 | } 55 | /> 56 | ) 57 | } 58 | 59 | _renderLoading() { 60 | return ( 61 | } 62 | description="Loading release history..." 63 | title="Loading" 64 | /> 65 | ) 66 | } 67 | 68 | _renderReleases() { 69 | const title = document.title = `Latest Releases${this._getOs() ? ` for ${osInfo[this._getOs()].name}` : ''}` 70 | const filteredReleases = this._getFilteredReleases() 71 | return ( 72 | <> 73 | {this._getOs() && All Releases }, 76 | {} 77 | ] 78 | }/>} 79 | 80 | { 84 | this.setState({ 85 | filters: Object.assign({}, this.state.filters, { 86 | [type]: this.state.filters[type].filter(key2 => value || key2 !== key).concat(value ? [key] : []) 87 | }) 88 | }) 89 | }} 90 | onClearFilters={() => { 91 | this.setState({ 92 | filters: { 93 | os: [], channel: [], majorVersion: [] 94 | } 95 | }) 96 | }} 97 | /> 98 | 99 |

{title}

100 | 101 | 102 | 103 | 104 | Version 105 | Channel 106 | Platform 107 | Release Date 108 | 109 | 110 | 111 | 112 | {filteredReleases.length !== 0 ? filteredReleases.map(release => { 113 | return ( 114 | 115 | {release.version} 116 | {release.channel} 117 | 118 | {osInfo[release.os].name} 119 | 120 | {release.timestamp} 121 | 122 | Get downloads 123 | 124 | 125 | ) 126 | }) 127 | : 128 | 129 | No releases were found matching your filters. 130 | } 131 | 132 | 133 | 134 | ) 135 | } 136 | 137 | _getFilteredReleases() { 138 | return this.state.releases 139 | .filter(release => { 140 | return (this._getOs() ? release.os === this._getOs() : (this.state.filters.os.length === 0 || this.state.filters.os.includes(release.os))) 141 | && (this.state.filters.channel.length === 0 || this.state.filters.channel.includes(release.channel)) 142 | && (this.state.filters.majorVersion.length === 0 || this.state.filters.majorVersion.includes(release.version.split('.', 2)[0])) 143 | }) 144 | } 145 | 146 | _loadReleases() { 147 | fetch(`${window.API_URL}/builds`) 148 | .then(response => { 149 | return response.json() 150 | }) 151 | .then(releases => { 152 | // filter to OS's we recognize 153 | releases = releases.filter(release => osInfo[release.os]) 154 | releases = releases.sort((a, b) => { 155 | var items1 = a.version.split('.'); 156 | var items2 = b.version.split('.'); 157 | var k = 0; 158 | for (let i in items1) { 159 | let a1 = items1[i]; 160 | let b1 = items2[i]; 161 | if (typeof b1 === undefined) { 162 | k = -1; 163 | break; 164 | } else { 165 | if (a1 === b1) { 166 | continue; 167 | } 168 | k = Number(b1) - Number(a1); 169 | break; 170 | } 171 | } 172 | return k; 173 | }); 174 | this.setState({ 175 | releasesLoaded: true, 176 | releases 177 | }) 178 | }) 179 | .catch(err => { 180 | this.setState({ 181 | errorLoading: err 182 | }) 183 | }) 184 | } 185 | 186 | _getMajorVersions() { 187 | if (!this._majorVersions) { 188 | var majorVersions = [] 189 | this.state.releases.forEach(release => { 190 | const majorVersion = release.version.split('.', 2)[0] 191 | if (!majorVersions.includes(majorVersion)) { 192 | majorVersions.push(majorVersion) 193 | } 194 | }) 195 | this._majorVersions = majorVersions 196 | } 197 | return this._majorVersions 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @import "~@blueprintjs/icons/lib/css/blueprint-icons.css"; 2 | @import "~@blueprintjs/core/lib/css/blueprint.css"; 3 | 4 | .App { 5 | margin-left: auto; 6 | margin-right: auto; 7 | max-width: 800px; 8 | } 9 | 10 | .bp3-card { 11 | margin-bottom: 1em; 12 | } 13 | 14 | .bp3-non-ideal-state > div { 15 | max-width: none; 16 | } 17 | 18 | .card-header h1 { 19 | margin: 0; 20 | } 21 | 22 | .card-header a { 23 | text-decoration: none; 24 | } 25 | 26 | .card-header, .card-footer { 27 | text-align: center; 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | window.API_URL = process.env.REACT_APP_API_URL || 'http://localhost:3001' 7 | 8 | ReactDOM.render(, document.getElementById('root')); 9 | -------------------------------------------------------------------------------- /frontend/src/util.js: -------------------------------------------------------------------------------- 1 | const filesize = require('file-size') 2 | 3 | export const getHumanReadableSize = (size) => { 4 | return filesize(parseInt(size)).human('si') 5 | } 6 | 7 | export const channelInfo = { 8 | 'canary': { 9 | color: 'warning' 10 | }, 11 | 'canary_asan': { 12 | color: 'warning' 13 | }, 14 | 'stable': { 15 | color: 'success' 16 | }, 17 | 'dev': { 18 | color: 'primary' 19 | }, 20 | 'beta': { 21 | color: 'danger' 22 | }, 23 | 'extended': { 24 | color: 'danger' 25 | } 26 | } 27 | 28 | export const channelKeys = Object.keys(channelInfo) 29 | 30 | export var osInfo = { 31 | 'win64': { 32 | name: 'Windows (x64)', 33 | baseDir: 'Win_x64', 34 | files: [ 35 | { 36 | name: 'Installer', 37 | filename: 'mini_installer.exe' 38 | }, 39 | { 40 | name: 'Archive', 41 | filename: 'chrome-win.zip' 42 | }, 43 | { 44 | name: 'Archive', 45 | filename: 'chrome-win32.zip' 46 | } 47 | ] 48 | }, 49 | 'win': { 50 | name: 'Windows (x86)', 51 | baseDir: 'Win' 52 | }, 53 | 'mac': { 54 | name: 'Mac OS', 55 | baseDir: 'Mac', 56 | files: [ 57 | { 58 | name: 'Archive', 59 | filename: 'chrome-mac.zip' 60 | } 61 | ] 62 | }, 63 | 'linux': { 64 | name: 'Linux', 65 | baseDir: 'Linux_x64', 66 | files: [ 67 | { 68 | name: 'Archive', 69 | filename: 'chrome-linux.zip' 70 | } 71 | ] 72 | } 73 | } 74 | 75 | osInfo.win.files = osInfo.win64.files 76 | 77 | export const osKeys = Object.keys(osInfo) 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chromium-downloads", 3 | "version": "0.1.0", 4 | "private": true, 5 | "workspaces": [ 6 | "frontend", 7 | "backend" 8 | ], 9 | "dependencies": { 10 | "concurrently": "^4.1.1" 11 | }, 12 | "scripts": { 13 | "start:watch": "concurrently --kill-others \"yarn workspace frontend start\" \"yarn workspace backend start:watch\"", 14 | "start": "cd backend && yarn start" 15 | }, 16 | "eslintConfig": { 17 | "extends": "react-app" 18 | }, 19 | "browserslist": [ 20 | ">0.2%", 21 | "not dead", 22 | "not ie <= 11", 23 | "not op_mini all" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "automerge": true, 6 | "major": { 7 | "automerge": false 8 | }, 9 | "statusCheckVerify": true 10 | } 11 | --------------------------------------------------------------------------------