├── app ├── utils │ └── .gitkeep ├── app.icns ├── yarn.lock ├── store │ ├── configureStore.js │ ├── configureStore.prod.js │ └── configureStore.dev.js ├── containers │ ├── HomePage.js │ ├── App.js │ └── Root.js ├── reducers │ ├── index.js │ └── counter.js ├── components │ ├── Header │ │ ├── Header.scss │ │ └── Header.js │ └── home-1 │ │ ├── Home.js │ │ └── Home.scss ├── routes.js ├── package.json ├── index.js ├── app.global.scss ├── .eslintrc ├── actions │ └── counter.js ├── app.html ├── main.dev.js ├── menu.js └── db.json ├── internals ├── mocks │ └── fileMock.js ├── flow │ ├── WebpackAsset.js.flow │ └── CSSModule.js.flow ├── img │ ├── flow.png │ ├── jest.png │ ├── js.png │ ├── npm.png │ ├── yarn.png │ ├── eslint.png │ ├── react.png │ ├── redux.png │ ├── js-padded.png │ ├── webpack.png │ ├── flow-padded.png │ ├── jest-padded.png │ ├── yarn-padded.png │ ├── eslint-padded.png │ ├── flow-padded-90.png │ ├── jest-padded-90.png │ ├── react-padded.png │ ├── react-router.png │ ├── redux-padded.png │ ├── webpack-padded.png │ ├── yarn-padded-90.png │ ├── eslint-padded-90.png │ ├── react-padded-90.png │ ├── redux-padded-90.png │ ├── react-router-padded.png │ ├── webpack-padded-90.png │ └── react-router-padded-90.png └── scripts │ ├── RunTests.js │ ├── CheckNodeEnv.js │ ├── CheckPortInUse.js │ ├── ElectronRebuild.js │ ├── CheckBuiltsExist.js │ └── CheckNativeDep.js ├── .stylelintrc ├── flow-typed └── module_vx.x.x.js ├── resources ├── icon.icns ├── icon.ico ├── icon.png ├── icon-2.png ├── test │ ├── icon.png │ └── Git-Icon-Black.png ├── icons │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ ├── 96x96.png │ └── 1024x1024.png ├── screenShot-1.png ├── external-link.svg └── icon.svg ├── .gitattributes ├── webpack.config.eslint.js ├── test ├── example.js ├── .eslintrc ├── actions │ ├── __snapshots__ │ │ └── counter.spec.js.snap │ └── counter.spec.js ├── reducers │ ├── __snapshots__ │ │ └── counter.spec.js.snap │ └── counter.spec.js ├── components │ ├── __snapshots__ │ │ └── Counter.spec.js.snap │ └── Counter.spec.js ├── containers │ └── CounterPage.spec.js └── e2e │ └── e2e.spec.js ├── CHANGELOG.md ├── .editorconfig ├── CONTRIBUTING.md ├── .vscode └── settings.json ├── .babelrc ├── docs ├── style.css └── index.html ├── appveyor.yml ├── .travis.yml ├── .flowconfig ├── .gitignore ├── .dockerignore ├── .eslintignore ├── LICENSE ├── .eslintrc ├── webpack.config.main.prod.js ├── webpack.config.renderer.dev.dll.js ├── webpack.config.base.js ├── README.md ├── webpack.config.renderer.prod.js ├── webpack.config.renderer.dev.js └── package.json /app/utils/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internals/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard" 3 | } 4 | -------------------------------------------------------------------------------- /internals/flow/WebpackAsset.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare export default string 3 | -------------------------------------------------------------------------------- /app/app.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/app/app.icns -------------------------------------------------------------------------------- /flow-typed/module_vx.x.x.js: -------------------------------------------------------------------------------- 1 | declare module 'module' { 2 | declare module.exports: any; 3 | } 4 | -------------------------------------------------------------------------------- /internals/flow/CSSModule.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | declare export default { [key: string]: string } -------------------------------------------------------------------------------- /resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/icon.icns -------------------------------------------------------------------------------- /resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/icon.ico -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/icon.png -------------------------------------------------------------------------------- /internals/img/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/flow.png -------------------------------------------------------------------------------- /internals/img/jest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/jest.png -------------------------------------------------------------------------------- /internals/img/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/js.png -------------------------------------------------------------------------------- /internals/img/npm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/npm.png -------------------------------------------------------------------------------- /internals/img/yarn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/yarn.png -------------------------------------------------------------------------------- /resources/icon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/icon-2.png -------------------------------------------------------------------------------- /internals/img/eslint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/eslint.png -------------------------------------------------------------------------------- /internals/img/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/react.png -------------------------------------------------------------------------------- /internals/img/redux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/redux.png -------------------------------------------------------------------------------- /resources/test/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/test/icon.png -------------------------------------------------------------------------------- /app/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /internals/img/js-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/js-padded.png -------------------------------------------------------------------------------- /internals/img/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/webpack.png -------------------------------------------------------------------------------- /resources/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/icons/128x128.png -------------------------------------------------------------------------------- /resources/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/icons/16x16.png -------------------------------------------------------------------------------- /resources/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/icons/24x24.png -------------------------------------------------------------------------------- /resources/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/icons/256x256.png -------------------------------------------------------------------------------- /resources/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/icons/32x32.png -------------------------------------------------------------------------------- /resources/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/icons/48x48.png -------------------------------------------------------------------------------- /resources/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/icons/512x512.png -------------------------------------------------------------------------------- /resources/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/icons/64x64.png -------------------------------------------------------------------------------- /resources/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/icons/96x96.png -------------------------------------------------------------------------------- /resources/screenShot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/screenShot-1.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.png binary 3 | *.jpg binary 4 | *.jpeg binary 5 | *.ico binary 6 | *.icns binary 7 | -------------------------------------------------------------------------------- /internals/img/flow-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/flow-padded.png -------------------------------------------------------------------------------- /internals/img/jest-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/jest-padded.png -------------------------------------------------------------------------------- /internals/img/yarn-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/yarn-padded.png -------------------------------------------------------------------------------- /resources/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/icons/1024x1024.png -------------------------------------------------------------------------------- /webpack.config.eslint.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | 3 | module.exports = require('./webpack.config.renderer.dev'); 4 | -------------------------------------------------------------------------------- /internals/img/eslint-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/eslint-padded.png -------------------------------------------------------------------------------- /internals/img/flow-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/flow-padded-90.png -------------------------------------------------------------------------------- /internals/img/jest-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/jest-padded-90.png -------------------------------------------------------------------------------- /internals/img/react-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/react-padded.png -------------------------------------------------------------------------------- /internals/img/react-router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/react-router.png -------------------------------------------------------------------------------- /internals/img/redux-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/redux-padded.png -------------------------------------------------------------------------------- /internals/img/webpack-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/webpack-padded.png -------------------------------------------------------------------------------- /internals/img/yarn-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/yarn-padded-90.png -------------------------------------------------------------------------------- /internals/img/eslint-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/eslint-padded-90.png -------------------------------------------------------------------------------- /internals/img/react-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/react-padded-90.png -------------------------------------------------------------------------------- /internals/img/redux-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/redux-padded-90.png -------------------------------------------------------------------------------- /resources/test/Git-Icon-Black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/resources/test/Git-Icon-Black.png -------------------------------------------------------------------------------- /internals/img/react-router-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/react-router-padded.png -------------------------------------------------------------------------------- /internals/img/webpack-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/webpack-padded-90.png -------------------------------------------------------------------------------- /internals/img/react-router-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariorodeghiero/git-commands/HEAD/internals/img/react-router-padded-90.png -------------------------------------------------------------------------------- /test/example.js: -------------------------------------------------------------------------------- 1 | describe('description', () => { 2 | it('should have description', () => { 3 | expect(1 + 2).toBe(3); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## 1.0.0 6 | 7 | * Initial release 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest/globals": true 4 | }, 5 | "plugins": [ 6 | "jest" 7 | ], 8 | "rules": { 9 | "jest/no-disabled-tests": "warn", 10 | "jest/no-focused-tests": "error", 11 | "jest/no-identical-title": "error" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/store/configureStore.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | if (process.env.NODE_ENV === 'production') { 3 | module.exports = require('./configureStore.prod'); // eslint-disable-line global-require 4 | } else { 5 | module.exports = require('./configureStore.dev'); // eslint-disable-line global-require 6 | } 7 | -------------------------------------------------------------------------------- /app/containers/HomePage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Home from '../components/home-1/Home'; 3 | 4 | type Props = {}; 5 | 6 | export default class HomePage extends Component { 7 | props: Props; 8 | 9 | render() { 10 | return ; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/containers/App.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from 'react'; 3 | 4 | type Props = { 5 | children: React.Node 6 | }; 7 | 8 | export default class App extends React.Component { 9 | props: Props; 10 | 11 | render() { 12 | return
{this.props.children}
; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/reducers/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { combineReducers } from 'redux'; 3 | import { routerReducer as router } from 'react-router-redux'; 4 | import counter from './counter'; 5 | 6 | const rootReducer = combineReducers({ 7 | counter, 8 | router 9 | }); 10 | 11 | export default rootReducer; 12 | -------------------------------------------------------------------------------- /test/actions/__snapshots__/counter.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`actions should decrement should create decrement action 1`] = ` 4 | Object { 5 | "type": "DECREMENT_COUNTER", 6 | } 7 | `; 8 | 9 | exports[`actions should increment should create increment action 1`] = ` 10 | Object { 11 | "type": "INCREMENT_COUNTER", 12 | } 13 | `; 14 | -------------------------------------------------------------------------------- /test/reducers/__snapshots__/counter.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`reducers counter should handle DECREMENT_COUNTER 1`] = `0`; 4 | 5 | exports[`reducers counter should handle INCREMENT_COUNTER 1`] = `2`; 6 | 7 | exports[`reducers counter should handle initial state 1`] = `0`; 8 | 9 | exports[`reducers counter should handle unknown action type 1`] = `1`; 10 | -------------------------------------------------------------------------------- /internals/scripts/RunTests.js: -------------------------------------------------------------------------------- 1 | import spawn from 'cross-spawn'; 2 | import path from 'path'; 3 | 4 | const pattern = 5 | process.argv[2] === 'e2e' 6 | ? 'test/e2e/.+\\.spec\\.js' 7 | : 'test/(?!e2e/)[^/]+/.+\\.spec\\.js$'; 8 | 9 | const result = spawn.sync( 10 | path.normalize('./node_modules/.bin/jest'), 11 | [pattern, ...process.argv.slice(2)], 12 | { stdio: 'inherit' } 13 | ); 14 | 15 | process.exit(result.status); 16 | -------------------------------------------------------------------------------- /internals/scripts/CheckNodeEnv.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import chalk from 'chalk'; 3 | 4 | export default function CheckNodeEnv(expectedEnv: string) { 5 | if (!expectedEnv) { 6 | throw new Error('"expectedEnv" not set'); 7 | } 8 | 9 | if (process.env.NODE_ENV !== expectedEnv) { 10 | console.log( 11 | chalk.whiteBright.bgRed.bold( 12 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config` 13 | ) 14 | ); 15 | process.exit(2); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/components/Header/Header.scss: -------------------------------------------------------------------------------- 1 | .headerContainer { 2 | h2 { 3 | padding: 20px; 4 | font-size: 1.6rem; 5 | font-weight: 300; 6 | color: #d3d3d3; 7 | } 8 | p { 9 | margin: 20px; 10 | float: right; 11 | font-size: 0.8rem; 12 | } 13 | .logo { 14 | float: left; 15 | padding-top: 20px; 16 | padding-left: 20px; 17 | padding-right: 10px; 18 | width: 28px; 19 | height: 28px; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/routes.js: -------------------------------------------------------------------------------- 1 | /* eslint flowtype-errors/show-errors: 0 */ 2 | import React from 'react'; 3 | import { Switch, Route } from 'react-router'; 4 | import App from './containers/App'; 5 | import HomePage from './containers/HomePage'; 6 | // import CounterPage from './containers/CounterPage'; 7 | 8 | export default () => ( 9 | 10 | 11 | {/* */} 12 | 13 | 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 1. Fork it! 4 | 2. Create your feature branch: `git checkout -b my-new-feature` 5 | 3. Commit your changes: `git commit -m 'Add some feature'` 6 | 4. Push to the branch: `git push origin my-new-feature` 7 | 8 | \*\*To contribute with new commands, simply insert in the file "app/db.json" following the current pattern. 9 | 10 | \*\*After your pull request is merged\*\*, you can safely delete your branch. 11 | 12 | ### [<-- Back](https://github.com/mariorodeghiero/git-commands) 13 | -------------------------------------------------------------------------------- /app/reducers/counter.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../actions/counter'; 3 | 4 | export type counterStateType = { 5 | +counter: number 6 | }; 7 | 8 | type actionType = { 9 | +type: string 10 | }; 11 | 12 | export default function counter(state: number = 0, action: actionType) { 13 | switch (action.type) { 14 | case INCREMENT_COUNTER: 15 | return state + 1; 16 | case DECREMENT_COUNTER: 17 | return state - 1; 18 | default: 19 | return state; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript.validate.enable": false, 3 | "flow.useNPMPackagedFlow": true, 4 | "search.exclude": { 5 | ".git": true, 6 | ".eslintcache": true, 7 | "app/dist": true, 8 | "app/main.prod.js": true, 9 | "app/main.prod.js.map": true, 10 | "bower_components": true, 11 | "dll": true, 12 | "flow-typed": true, 13 | "release": true, 14 | "node_modules": true, 15 | "npm-debug.log.*": true, 16 | "test/**/__snapshots__": true, 17 | "yarn.lock": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/containers/Root.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component } from 'react'; 3 | import { Provider } from 'react-redux'; 4 | import { ConnectedRouter } from 'react-router-redux'; 5 | import Routes from '../routes'; 6 | 7 | type Props = { 8 | store: {}, 9 | history: {} 10 | }; 11 | 12 | export default class Root extends Component { 13 | render() { 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internals/scripts/CheckPortInUse.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import chalk from 'chalk'; 3 | import detectPort from 'detect-port'; 4 | 5 | (function CheckPortInUse() { 6 | const port: string = process.env.PORT || '1212'; 7 | 8 | detectPort(port, (err: ?Error, availablePort: number) => { 9 | if (port !== String(availablePort)) { 10 | throw new Error( 11 | chalk.whiteBright.bgRed.bold( 12 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm run dev` 13 | ) 14 | ); 15 | } else { 16 | process.exit(0); 17 | } 18 | }); 19 | })(); 20 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { "node": 7 }, 5 | "useBuiltIns": true 6 | }], 7 | "stage-0", 8 | "react" 9 | ], 10 | "plugins": ["add-module-exports"], 11 | "env": { 12 | "production": { 13 | "presets": ["react-optimize"], 14 | "plugins": ["dev-expression"] 15 | }, 16 | "development": { 17 | "plugins": [ 18 | "transform-class-properties", 19 | "transform-es2015-classes", 20 | ["flow-runtime", { 21 | "assert": true, 22 | "annotate": true 23 | }] 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import icon from "../../../resources/icon.svg" 3 | import styles from'./Header.scss' 4 | 5 | 6 | class Header extends Component { 7 | state = { } 8 | render() { 9 | return ( 10 |
11 |
12 |

⚖️ MIT License

13 | Git logo 14 |

Commands

15 |
16 |
17 | ); 18 | } 19 | } 20 | 21 | export default Header; 22 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-react-boilerplate", 3 | "productName": "Git Commands", 4 | "version": "1.0.0", 5 | "description": "App built with Electron and ReactJS", 6 | "main": "./main.prod.js", 7 | "author": { 8 | "name": "Mário Antônio A. Rodeghiero", 9 | "email": "mario.rodeghiero@gmail.com", 10 | "url": "https://github.com/mariorodeghiero" 11 | }, 12 | "scripts": { 13 | "electron-rebuild": 14 | "node -r babel-register ../internals/scripts/ElectronRebuild.js", 15 | "postinstall": "npm run electron-rebuild" 16 | }, 17 | "license": "MIT", 18 | "dependencies": {} 19 | } 20 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Roboto:400,300,100"); 2 | html, 3 | body { 4 | color: #aeb1b0; 5 | height: 100vh; 6 | background-color: #002b36; 7 | background-image: linear-gradient( 90deg, rgba(4, 131, 163, 0.5) 10%, rgba(0, 43, 54, 0.7)); 8 | } 9 | 10 | h1, 11 | h2, 12 | h3, 13 | h4, 14 | a, 15 | p, 16 | li, 17 | span, 18 | button { 19 | font-family: "Roboto"; 20 | font-weight: 100; 21 | line-height: 1.1; 22 | letter-spacing: 0.025em; 23 | } 24 | 25 | li { 26 | list-style: none; 27 | } 28 | 29 | .btn-dark { 30 | background-color: #000000; 31 | color: #ffffff; 32 | } 33 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | os: unstable 2 | 3 | environment: 4 | matrix: 5 | - nodejs_version: 10 6 | - nodejs_version: 9 7 | 8 | cache: 9 | - "%LOCALAPPDATA%/Yarn" 10 | - node_modules -> package.json 11 | - app/node_modules -> app/package.json 12 | 13 | matrix: 14 | fast_finish: true 15 | 16 | build: off 17 | 18 | version: '{build}' 19 | 20 | shallow_clone: true 21 | 22 | clone_depth: 1 23 | 24 | install: 25 | - ps: Install-Product node $env:nodejs_version 26 | - set CI=true 27 | - yarn 28 | - cd app && yarn 29 | 30 | test_script: 31 | - node --version 32 | - yarn lint 33 | - yarn package 34 | - yarn test 35 | - yarn test-e2e 36 | -------------------------------------------------------------------------------- /app/store/configureStore.prod.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { createStore, applyMiddleware } from 'redux'; 3 | import thunk from 'redux-thunk'; 4 | import { createHashHistory } from 'history'; 5 | import { routerMiddleware } from 'react-router-redux'; 6 | import rootReducer from '../reducers'; 7 | import type { counterStateType } from '../reducers/counter'; 8 | 9 | const history = createHashHistory(); 10 | const router = routerMiddleware(history); 11 | const enhancer = applyMiddleware(thunk, router); 12 | 13 | function configureStore(initialState?: counterStateType) { 14 | return createStore(rootReducer, initialState, enhancer); 15 | } 16 | 17 | export default { configureStore, history }; 18 | -------------------------------------------------------------------------------- /internals/scripts/ElectronRebuild.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import path from 'path'; 3 | import { execSync } from 'child_process'; 4 | import fs from 'fs'; 5 | import dependencies from '../../app/package.json'; 6 | 7 | const nodeModulesPath = path.join(__dirname, '..', '..', 'app', 'node_modules'); 8 | 9 | if ( 10 | Object.keys(dependencies || {}).length > 0 && 11 | fs.existsSync(nodeModulesPath) 12 | ) { 13 | const electronRebuildCmd = 14 | '../node_modules/.bin/electron-rebuild --parallel --force --types prod,dev,optional --module-dir .'; 15 | 16 | const cmd = 17 | process.platform === 'win32' 18 | ? electronRebuildCmd.replace(/\//g, '\\') 19 | : electronRebuildCmd; 20 | 21 | execSync(cmd, { 22 | cwd: path.join(__dirname, '..', '..', 'app') 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /test/reducers/counter.spec.js: -------------------------------------------------------------------------------- 1 | import counter from '../../app/reducers/counter'; 2 | import { 3 | INCREMENT_COUNTER, 4 | DECREMENT_COUNTER 5 | } from '../../app/actions/counter'; 6 | 7 | describe('reducers', () => { 8 | describe('counter', () => { 9 | it('should handle initial state', () => { 10 | expect(counter(undefined, {})).toMatchSnapshot(); 11 | }); 12 | 13 | it('should handle INCREMENT_COUNTER', () => { 14 | expect(counter(1, { type: INCREMENT_COUNTER })).toMatchSnapshot(); 15 | }); 16 | 17 | it('should handle DECREMENT_COUNTER', () => { 18 | expect(counter(1, { type: DECREMENT_COUNTER })).toMatchSnapshot(); 19 | }); 20 | 21 | it('should handle unknown action type', () => { 22 | expect(counter(1, { type: 'unknown' })).toMatchSnapshot(); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { AppContainer } from 'react-hot-loader'; 4 | import Root from './containers/Root'; 5 | import { configureStore, history } from './store/configureStore'; 6 | import './app.global.scss'; 7 | 8 | const store = configureStore(); 9 | 10 | render( 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ); 16 | 17 | if (module.hot) { 18 | module.hot.accept('./containers/Root', () => { 19 | const NextRoot = require('./containers/Root'); // eslint-disable-line global-require 20 | render( 21 | 22 | 23 | , 24 | document.getElementById('root') 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /app/app.global.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @NOTE: Prepend a `~` to css file paths that are in your node_modules 3 | * See https://github.com/webpack-contrib/sass-loader#imports 4 | */ 5 | 6 | @import "~font-awesome/css/font-awesome.css"; 7 | @import url("https://fonts.googleapis.com/css?family=Roboto:400,300,100"); 8 | * { 9 | margin: 0; 10 | padding: 0; 11 | } 12 | 13 | html, 14 | body { 15 | color: #aeb1b0; 16 | height: 100vh; 17 | background-color: #002b36; 18 | background-image: linear-gradient( 90deg, rgba(4, 131, 163, 0.5) 10%, rgba(0, 43, 54, 0.7)); 19 | } 20 | 21 | h1, 22 | h2, 23 | h3, 24 | h4, 25 | a, 26 | p, 27 | li, 28 | span, 29 | button { 30 | font-family: "Roboto"; 31 | font-weight: 100; 32 | line-height: 1.1; 33 | letter-spacing: 0.025em; 34 | } 35 | 36 | li { 37 | list-style: none; 38 | } 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: true 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - node 7 | - 9 8 | 9 | cache: 10 | yarn: true 11 | directories: 12 | - node_modules 13 | - app/node_modules 14 | 15 | addons: 16 | apt: 17 | sources: 18 | - ubuntu-toolchain-r-test 19 | packages: 20 | - g++-4.8 21 | - icnsutils 22 | - graphicsmagick 23 | - xz-utils 24 | - xorriso 25 | 26 | install: 27 | - export CXX="g++-4.8" 28 | - yarn 29 | - cd app && yarn && cd .. 30 | - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16" 31 | 32 | before_script: 33 | - export DISPLAY=:99.0 34 | - sh -e /etc/init.d/xvfb start & 35 | - sleep 3 36 | 37 | script: 38 | - node --version 39 | - yarn lint 40 | - yarn package 41 | - yarn test 42 | - yarn test-e2e 43 | -------------------------------------------------------------------------------- /app/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "flowtype/boolean-style": ["error", "boolean"], 4 | "flowtype/define-flow-type": "warn", 5 | "flowtype/delimiter-dangle": ["error", "never"], 6 | "flowtype/generic-spacing": ["error", "never"], 7 | "flowtype/no-primitive-constructor-types": "error", 8 | "flowtype/no-weak-types": "warn", 9 | "flowtype/object-type-delimiter": ["error", "comma"], 10 | "flowtype/require-parameter-type": "off", 11 | "flowtype/require-return-type": "off", 12 | "flowtype/require-valid-file-annotation": "off", 13 | "flowtype/semi": ["error", "always"], 14 | "flowtype/space-after-type-colon": ["error", "always"], 15 | "flowtype/space-before-generic-bracket": ["error", "never"], 16 | "flowtype/space-before-type-colon": ["error", "never"], 17 | "flowtype/union-intersection-spacing": ["error", "always"], 18 | "flowtype/use-flow-type": "error", 19 | "flowtype/valid-syntax": "error" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /internals/scripts/CheckBuiltsExist.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | // Check if the renderer and main bundles are built 3 | import path from 'path'; 4 | import chalk from 'chalk'; 5 | import fs from 'fs'; 6 | 7 | function CheckBuildsExist() { 8 | const mainPath = path.join(__dirname, '..', '..', 'app', 'main.prod.js'); 9 | const rendererPath = path.join( 10 | __dirname, 11 | '..', 12 | '..', 13 | 'app', 14 | 'dist', 15 | 'renderer.prod.js' 16 | ); 17 | 18 | if (!fs.existsSync(mainPath)) { 19 | throw new Error( 20 | chalk.whiteBright.bgRed.bold( 21 | 'The main process is not built yet. Build it by running "npm run build-main"' 22 | ) 23 | ); 24 | } 25 | 26 | if (!fs.existsSync(rendererPath)) { 27 | throw new Error( 28 | chalk.whiteBright.bgRed.bold( 29 | 'The renderer process is not built yet. Build it by running "npm run build-renderer"' 30 | ) 31 | ); 32 | } 33 | } 34 | 35 | CheckBuildsExist(); 36 | -------------------------------------------------------------------------------- /app/actions/counter.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { counterStateType } from '../reducers/counter'; 3 | 4 | type actionType = { 5 | +type: string 6 | }; 7 | 8 | export const INCREMENT_COUNTER = 'INCREMENT_COUNTER'; 9 | export const DECREMENT_COUNTER = 'DECREMENT_COUNTER'; 10 | 11 | export function increment() { 12 | return { 13 | type: INCREMENT_COUNTER 14 | }; 15 | } 16 | 17 | export function decrement() { 18 | return { 19 | type: DECREMENT_COUNTER 20 | }; 21 | } 22 | 23 | export function incrementIfOdd() { 24 | return ( 25 | dispatch: (action: actionType) => void, 26 | getState: () => counterStateType 27 | ) => { 28 | const { counter } = getState(); 29 | 30 | if (counter % 2 === 0) { 31 | return; 32 | } 33 | 34 | dispatch(increment()); 35 | }; 36 | } 37 | 38 | export function incrementAsync(delay: number = 1000) { 39 | return (dispatch: (action: actionType) => void) => { 40 | setTimeout(() => { 41 | dispatch(increment()); 42 | }, delay); 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /app/main.prod.js 3 | /app/main.prod.js.map 4 | /app/dist/.* 5 | /resources/.* 6 | /node_modules/webpack-cli 7 | /release/.* 8 | /dll/.* 9 | /release/.* 10 | /git/.* 11 | 12 | [include] 13 | 14 | [libs] 15 | 16 | [options] 17 | esproposal.class_static_fields=enable 18 | esproposal.class_instance_fields=enable 19 | esproposal.export_star_as=enable 20 | module.name_mapper.extension='css' -> '/internals/flow/CSSModule.js.flow' 21 | module.name_mapper.extension='styl' -> '/internals/flow/CSSModule.js.flow' 22 | module.name_mapper.extension='scss' -> '/internals/flow/CSSModule.js.flow' 23 | module.name_mapper.extension='png' -> '/internals/flow/WebpackAsset.js.flow' 24 | module.name_mapper.extension='jpg' -> '/internals/flow/WebpackAsset.js.flow' 25 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe 26 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | .eslintcache 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | app/node_modules 30 | 31 | # OSX 32 | .DS_Store 33 | 34 | # flow-typed 35 | flow-typed/npm/* 36 | !flow-typed/npm/module_vx.x.x.js 37 | 38 | # App packaged 39 | release 40 | app/main.prod.js 41 | app/main.prod.js.map 42 | app/renderer.prod.js 43 | app/renderer.prod.js.map 44 | app/style.css 45 | app/style.css.map 46 | dist 47 | dll 48 | main.js 49 | main.js.map 50 | 51 | .idea 52 | npm-debug.log.* 53 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | .eslintcache 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | app/node_modules 30 | 31 | # OSX 32 | .DS_Store 33 | 34 | # flow-typed 35 | flow-typed/npm/* 36 | !flow-typed/npm/module_vx.x.x.js 37 | 38 | # App packaged 39 | release 40 | app/main.prod.js 41 | app/main.prod.js.map 42 | app/renderer.prod.js 43 | app/renderer.prod.js.map 44 | app/style.css 45 | app/style.css.map 46 | dist 47 | dll 48 | main.js 49 | main.js.map 50 | 51 | .idea 52 | npm-debug.log.* 53 | .*.dockerfile -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | .eslintcache 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | app/node_modules 30 | 31 | # OSX 32 | .DS_Store 33 | 34 | # flow-typed 35 | flow-typed/npm/* 36 | !flow-typed/npm/module_vx.x.x.js 37 | 38 | # App packaged 39 | release 40 | app/main.prod.js 41 | app/main.prod.js.map 42 | app/renderer.prod.js 43 | app/renderer.prod.js.map 44 | app/style.css 45 | app/style.css.map 46 | dist 47 | dll 48 | main.js 49 | main.js.map 50 | 51 | .idea 52 | npm-debug.log.* 53 | __snapshots__ 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mário Antônio do Amaral Rodeghiero 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Git CheatSheet 7 | 18 | 19 | 20 | 21 |
22 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /resources/external-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/components/__snapshots__/Counter.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Counter component should match exact snapshot 1`] = ` 4 |
5 |
6 |
10 | 14 | 17 | 18 |
19 |
23 | 1 24 |
25 |
28 | 37 | 46 | 53 | 60 |
61 |
62 |
63 | `; 64 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "allowImportExportEverywhere": true 6 | }, 7 | "extends": [ 8 | "airbnb", 9 | "prettier" 10 | ], 11 | "env": { 12 | "browser": true, 13 | "node": true 14 | }, 15 | "rules": { 16 | "arrow-parens": ["off"], 17 | "compat/compat": "error", 18 | "consistent-return": "off", 19 | "comma-dangle": "off", 20 | "generator-star-spacing": "off", 21 | "import/no-unresolved": "error", 22 | "import/no-extraneous-dependencies": "off", 23 | "jsx-a11y/anchor-is-valid": "off", 24 | "no-console": "off", 25 | "no-use-before-define": "off", 26 | "no-multi-assign": "off", 27 | "promise/param-names": "error", 28 | "promise/always-return": "error", 29 | "promise/catch-or-return": "error", 30 | "promise/no-native": "off", 31 | "react/sort-comp": ["error", { 32 | "order": ["type-annotations", "static-methods", "lifecycle", "everything-else", "render"] 33 | }], 34 | "react/jsx-no-bind": "off", 35 | "react/jsx-filename-extension": ["error", { "extensions": [".js", ".jsx"] }], 36 | "react/prefer-stateless-function": "off" 37 | }, 38 | "plugins": [ 39 | "flowtype", 40 | "import", 41 | "promise", 42 | "compat", 43 | "react" 44 | ], 45 | "settings": { 46 | "import/resolver": { 47 | "webpack": { 48 | "config": "webpack.config.eslint.js" 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/actions/counter.spec.js: -------------------------------------------------------------------------------- 1 | import { spy } from 'sinon'; 2 | import * as actions from '../../app/actions/counter'; 3 | 4 | describe('actions', () => { 5 | it('should increment should create increment action', () => { 6 | expect(actions.increment()).toMatchSnapshot(); 7 | }); 8 | 9 | it('should decrement should create decrement action', () => { 10 | expect(actions.decrement()).toMatchSnapshot(); 11 | }); 12 | 13 | it('should incrementIfOdd should create increment action', () => { 14 | const fn = actions.incrementIfOdd(); 15 | expect(fn).toBeInstanceOf(Function); 16 | const dispatch = spy(); 17 | const getState = () => ({ counter: 1 }); 18 | fn(dispatch, getState); 19 | expect(dispatch.calledWith({ type: actions.INCREMENT_COUNTER })).toBe(true); 20 | }); 21 | 22 | it('should incrementIfOdd shouldnt create increment action if counter is even', () => { 23 | const fn = actions.incrementIfOdd(); 24 | const dispatch = spy(); 25 | const getState = () => ({ counter: 2 }); 26 | fn(dispatch, getState); 27 | expect(dispatch.called).toBe(false); 28 | }); 29 | 30 | // There's no nice way to test this at the moment... 31 | it('should incrementAsync', done => { 32 | const fn = actions.incrementAsync(1); 33 | expect(fn).toBeInstanceOf(Function); 34 | const dispatch = spy(); 35 | fn(dispatch); 36 | setTimeout(() => { 37 | expect(dispatch.calledWith({ type: actions.INCREMENT_COUNTER })).toBe( 38 | true 39 | ); 40 | done(); 41 | }, 5); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /webpack.config.main.prod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for production electron main process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import merge from 'webpack-merge'; 7 | import UglifyJSPlugin from 'uglifyjs-webpack-plugin'; 8 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 9 | import baseConfig from './webpack.config.base'; 10 | import CheckNodeEnv from './internals/scripts/CheckNodeEnv'; 11 | 12 | CheckNodeEnv('production'); 13 | 14 | export default merge.smart(baseConfig, { 15 | devtool: 'source-map', 16 | 17 | mode: 'production', 18 | 19 | target: 'electron-main', 20 | 21 | entry: './app/main.dev', 22 | 23 | output: { 24 | path: __dirname, 25 | filename: './app/main.prod.js' 26 | }, 27 | 28 | plugins: [ 29 | new UglifyJSPlugin({ 30 | parallel: true, 31 | sourceMap: true 32 | }), 33 | 34 | new BundleAnalyzerPlugin({ 35 | analyzerMode: 36 | process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled', 37 | openAnalyzer: process.env.OPEN_ANALYZER === 'true' 38 | }), 39 | 40 | /** 41 | * Create global constants which can be configured at compile time. 42 | * 43 | * Useful for allowing different behaviour between development builds and 44 | * release builds 45 | * 46 | * NODE_ENV should be production so that modules do not perform certain 47 | * development checks 48 | */ 49 | new webpack.EnvironmentPlugin({ 50 | NODE_ENV: 'production', 51 | DEBUG_PROD: 'false' 52 | }) 53 | ], 54 | 55 | /** 56 | * Disables webpack processing of __dirname and __filename. 57 | * If you run the bundle in node.js it falls back to these values of node.js. 58 | * https://github.com/webpack/webpack/issues/2010 59 | */ 60 | node: { 61 | __dirname: false, 62 | __filename: false 63 | } 64 | }); 65 | -------------------------------------------------------------------------------- /app/components/home-1/Home.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, {Component} from 'react'; 3 | import styles from './Home.scss'; 4 | import iconExternal from "../../../resources/external-link.svg" 5 | import Header from '../header/Header'; 6 | 7 | // type Props = {}; 8 | 9 | export default class Home extends Component < Props > { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | git: [] 14 | }; 15 | } 16 | componentDidMount() { 17 | const url = "https://raw.githubusercontent.com/mariorodeghiero/git-commands/master/app/db.j" + 18 | "son" 19 | fetch(url) 20 | .then(res => res.json()) 21 | .then(data => { 22 | const git = data 23 | .git 24 | .map((git, index) => ( 25 |
26 |

{git.title}

27 | 28 | Git icon 34 | 35 |
36 | 45 |
46 | )) 47 | return this.setState({git}) 48 | }) 49 | .catch(err => console.error(err)) 50 | } 51 | render() { 52 | return ( 53 |
54 |
55 |
56 |
57 | {this.state.git} 58 |
59 |
60 |
61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/containers/CounterPage.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Enzyme, { mount } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | import { Provider } from 'react-redux'; 5 | import { createBrowserHistory } from 'history'; 6 | import { ConnectedRouter } from 'react-router-redux'; 7 | import CounterPage from '../../app/containers/CounterPage'; 8 | import { configureStore } from '../../app/store/configureStore'; 9 | 10 | Enzyme.configure({ adapter: new Adapter() }); 11 | 12 | function setup(initialState) { 13 | const store = configureStore(initialState); 14 | const history = createBrowserHistory(); 15 | const provider = ( 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | const app = mount(provider); 23 | return { 24 | app, 25 | buttons: app.find('button'), 26 | p: app.find('.counter') 27 | }; 28 | } 29 | 30 | describe('containers', () => { 31 | describe('App', () => { 32 | it('should display initial count', () => { 33 | const { p } = setup(); 34 | expect(p.text()).toMatch(/^0$/); 35 | }); 36 | 37 | it('should display updated count after increment button click', () => { 38 | const { buttons, p } = setup(); 39 | buttons.at(0).simulate('click'); 40 | expect(p.text()).toMatch(/^1$/); 41 | }); 42 | 43 | it('should display updated count after decrement button click', () => { 44 | const { buttons, p } = setup(); 45 | buttons.at(1).simulate('click'); 46 | expect(p.text()).toMatch(/^-1$/); 47 | }); 48 | 49 | it('shouldnt change if even and if odd button clicked', () => { 50 | const { buttons, p } = setup(); 51 | buttons.at(2).simulate('click'); 52 | expect(p.text()).toMatch(/^0$/); 53 | }); 54 | 55 | it('should change if odd and if odd button clicked', () => { 56 | const { buttons, p } = setup({ counter: 1 }); 57 | buttons.at(2).simulate('click'); 58 | expect(p.text()).toMatch(/^2$/); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /webpack.config.renderer.dev.dll.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0, import/no-dynamic-require: 0 */ 2 | 3 | /** 4 | * Builds the DLL for development electron renderer process 5 | */ 6 | 7 | import webpack from 'webpack'; 8 | import path from 'path'; 9 | import merge from 'webpack-merge'; 10 | import baseConfig from './webpack.config.base'; 11 | import { dependencies } from './package.json'; 12 | import CheckNodeEnv from './internals/scripts/CheckNodeEnv'; 13 | 14 | CheckNodeEnv('development'); 15 | 16 | const dist = path.resolve(process.cwd(), 'dll'); 17 | 18 | export default merge.smart(baseConfig, { 19 | context: process.cwd(), 20 | 21 | devtool: 'eval', 22 | 23 | mode: 'development', 24 | 25 | target: 'electron-renderer', 26 | 27 | externals: ['fsevents', 'crypto-browserify'], 28 | 29 | /** 30 | * Use `module` from `webpack.config.renderer.dev.js` 31 | */ 32 | module: require('./webpack.config.renderer.dev').module, 33 | 34 | entry: { 35 | renderer: Object.keys(dependencies || {}).filter( 36 | dependency => dependency !== 'font-awesome' 37 | ) 38 | }, 39 | 40 | output: { 41 | library: 'renderer', 42 | path: dist, 43 | filename: '[name].dev.dll.js', 44 | libraryTarget: 'var' 45 | }, 46 | 47 | plugins: [ 48 | new webpack.DllPlugin({ 49 | path: path.join(dist, '[name].json'), 50 | name: '[name]' 51 | }), 52 | 53 | /** 54 | * Create global constants which can be configured at compile time. 55 | * 56 | * Useful for allowing different behaviour between development builds and 57 | * release builds 58 | * 59 | * NODE_ENV should be production so that modules do not perform certain 60 | * development checks 61 | */ 62 | new webpack.EnvironmentPlugin({ 63 | NODE_ENV: 'development' 64 | }), 65 | 66 | new webpack.LoaderOptionsPlugin({ 67 | debug: true, 68 | options: { 69 | context: path.resolve(process.cwd(), 'app'), 70 | output: { 71 | path: path.resolve(process.cwd(), 'dll') 72 | } 73 | } 74 | }) 75 | ] 76 | }); 77 | -------------------------------------------------------------------------------- /webpack.config.base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Base webpack config used across other specific configs 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import fs from 'fs'; 8 | import { dependencies as externals } from './app/package.json'; 9 | import { dependencies as possibleExternals } from './package.json'; 10 | 11 | // Find all the dependencies without a `main` property and add them as webpack externals 12 | function filterDepWithoutEntryPoints(dep: string): boolean { 13 | // Return true if we want to add a dependency to externals 14 | try { 15 | // If the root of the dependency has an index.js, return true 16 | if (fs.existsSync(path.join(__dirname, `node_modules/${dep}/index.js`))) { 17 | return false; 18 | } 19 | const pgkString = fs 20 | .readFileSync(path.join(__dirname, `node_modules/${dep}/package.json`)) 21 | .toString(); 22 | const pkg = JSON.parse(pgkString); 23 | const fields = ['main', 'module', 'jsnext:main', 'browser']; 24 | return !fields.some(field => field in pkg); 25 | } catch (e) { 26 | console.log(e); 27 | return true; 28 | } 29 | } 30 | 31 | export default { 32 | externals: [ 33 | ...Object.keys(externals || {}), 34 | ...Object.keys(possibleExternals || {}).filter(filterDepWithoutEntryPoints) 35 | ], 36 | 37 | module: { 38 | rules: [ 39 | { 40 | test: /\.jsx?$/, 41 | exclude: /node_modules/, 42 | use: { 43 | loader: 'babel-loader', 44 | options: { 45 | cacheDirectory: true 46 | } 47 | } 48 | } 49 | ] 50 | }, 51 | 52 | output: { 53 | path: path.join(__dirname, 'app'), 54 | // https://github.com/webpack/webpack/issues/1114 55 | libraryTarget: 'commonjs2' 56 | }, 57 | 58 | /** 59 | * Determine the array of extensions that should be used to resolve modules. 60 | */ 61 | resolve: { 62 | extensions: ['.js', '.jsx', '.json'], 63 | modules: [path.join(__dirname, 'app'), 'node_modules'] 64 | }, 65 | 66 | plugins: [ 67 | new webpack.EnvironmentPlugin({ 68 | NODE_ENV: 'production' 69 | }), 70 | 71 | new webpack.NamedModulesPlugin() 72 | ] 73 | }; 74 | -------------------------------------------------------------------------------- /app/components/home-1/Home.scss: -------------------------------------------------------------------------------- 1 | // custom scroll-bar 2 | @mixin custom-scroll-bar() { 3 | &::-webkit-scrollbar { 4 | border-radius: 10px; 5 | width: 3px; 6 | } 7 | &::-webkit-scrollbar-thumb { 8 | border-radius: 10px; 9 | background: #084658; 10 | } 11 | &::-webkit-scrollbar-track { 12 | border-radius: 10px; 13 | background-color: #002b36; 14 | } 15 | } 16 | 17 | .container { 18 | min-height: 660px; 19 | width: 100%; 20 | .containerBox { 21 | align-content: space-between; 22 | justify-content: space-around; 23 | padding: 10px; 24 | display: grid; 25 | grid-template-columns: repeat(auto-fill, 350px); 26 | grid-template-rows: auto; 27 | grid-gap: 10px; 28 | .box { 29 | width: 100%; 30 | padding: 10px; 31 | a { 32 | text-decoration: none; 33 | color: inherit; 34 | .externalLink { 35 | float: right; 36 | padding-right: 25px; 37 | margin-top: -20px; 38 | width: 14px; 39 | height: 14px; 40 | } 41 | } 42 | h3 { 43 | text-align: center; 44 | padding-bottom: 8px; 45 | font-weight: 200; 46 | font-size: 1.2rem; 47 | color: #d3d3d3; 48 | } 49 | hr { 50 | margin: 0 auto; 51 | opacity: 0.1; 52 | width: 70%; 53 | } 54 | nav { 55 | height: 250px; 56 | overflow: auto; 57 | @include custom-scroll-bar(); 58 | } 59 | 60 | ul { 61 | margin-left: 30px; 62 | padding-top: 20px; 63 | li { 64 | padding: 5px; 65 | font-size: 0.9rem; 66 | &:hover { 67 | font-weight: 200; 68 | color: #dfdbdb; 69 | } 70 | } 71 | } 72 | 73 | } 74 | 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /test/components/Counter.spec.js: -------------------------------------------------------------------------------- 1 | import { spy } from 'sinon'; 2 | import React from 'react'; 3 | import Enzyme, { shallow } from 'enzyme'; 4 | import Adapter from 'enzyme-adapter-react-16'; 5 | import { BrowserRouter as Router } from 'react-router-dom'; 6 | import renderer from 'react-test-renderer'; 7 | import Counter from '../../app/components/Counter'; 8 | 9 | Enzyme.configure({ adapter: new Adapter() }); 10 | 11 | function setup() { 12 | const actions = { 13 | increment: spy(), 14 | incrementIfOdd: spy(), 15 | incrementAsync: spy(), 16 | decrement: spy() 17 | }; 18 | const component = shallow(); 19 | return { 20 | component, 21 | actions, 22 | buttons: component.find('button'), 23 | p: component.find('.counter') 24 | }; 25 | } 26 | 27 | describe('Counter component', () => { 28 | it('should should display count', () => { 29 | const { p } = setup(); 30 | expect(p.text()).toMatch(/^1$/); 31 | }); 32 | 33 | it('should first button should call increment', () => { 34 | const { buttons, actions } = setup(); 35 | buttons.at(0).simulate('click'); 36 | expect(actions.increment.called).toBe(true); 37 | }); 38 | 39 | it('should match exact snapshot', () => { 40 | const { actions } = setup(); 41 | const counter = ( 42 |
43 | 44 | 45 | 46 |
47 | ); 48 | const tree = renderer.create(counter).toJSON(); 49 | 50 | expect(tree).toMatchSnapshot(); 51 | }); 52 | 53 | it('should second button should call decrement', () => { 54 | const { buttons, actions } = setup(); 55 | buttons.at(1).simulate('click'); 56 | expect(actions.decrement.called).toBe(true); 57 | }); 58 | 59 | it('should third button should call incrementIfOdd', () => { 60 | const { buttons, actions } = setup(); 61 | buttons.at(2).simulate('click'); 62 | expect(actions.incrementIfOdd.called).toBe(true); 63 | }); 64 | 65 | it('should fourth button should call incrementAsync', () => { 66 | const { buttons, actions } = setup(); 67 | buttons.at(3).simulate('click'); 68 | expect(actions.incrementAsync.called).toBe(true); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /app/store/configureStore.dev.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { createHashHistory } from 'history'; 4 | import { routerMiddleware, routerActions } from 'react-router-redux'; 5 | import { createLogger } from 'redux-logger'; 6 | import rootReducer from '../reducers'; 7 | import * as counterActions from '../actions/counter'; 8 | import type { counterStateType } from '../reducers/counter'; 9 | 10 | const history = createHashHistory(); 11 | 12 | const configureStore = (initialState?: counterStateType) => { 13 | // Redux Configuration 14 | const middleware = []; 15 | const enhancers = []; 16 | 17 | // Thunk Middleware 18 | middleware.push(thunk); 19 | 20 | // Logging Middleware 21 | const logger = createLogger({ 22 | level: 'info', 23 | collapsed: true 24 | }); 25 | 26 | // Skip redux logs in console during the tests 27 | if (process.env.NODE_ENV !== 'test') { 28 | middleware.push(logger); 29 | } 30 | 31 | // Router Middleware 32 | const router = routerMiddleware(history); 33 | middleware.push(router); 34 | 35 | // Redux DevTools Configuration 36 | const actionCreators = { 37 | ...counterActions, 38 | ...routerActions 39 | }; 40 | // If Redux DevTools Extension is installed use it, otherwise use Redux compose 41 | /* eslint-disable no-underscore-dangle */ 42 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 43 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ 44 | // Options: http://zalmoxisus.github.io/redux-devtools-extension/API/Arguments.html 45 | actionCreators 46 | }) 47 | : compose; 48 | /* eslint-enable no-underscore-dangle */ 49 | 50 | // Apply Middleware & Compose Enhancers 51 | enhancers.push(applyMiddleware(...middleware)); 52 | const enhancer = composeEnhancers(...enhancers); 53 | 54 | // Create Store 55 | const store = createStore(rootReducer, initialState, enhancer); 56 | 57 | if (module.hot) { 58 | module.hot.accept( 59 | '../reducers', 60 | () => store.replaceReducer(require('../reducers')) // eslint-disable-line global-require 61 | ); 62 | } 63 | 64 | return store; 65 | }; 66 | 67 | export default { configureStore, history }; 68 | -------------------------------------------------------------------------------- /internals/scripts/CheckNativeDep.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import fs from 'fs'; 3 | import chalk from 'chalk'; 4 | import { execSync } from 'child_process'; 5 | import { dependencies } from '../../package.json'; 6 | 7 | (() => { 8 | if (!dependencies) return; 9 | 10 | const dependenciesKeys = Object.keys(dependencies); 11 | const nativeDeps = fs 12 | .readdirSync('node_modules') 13 | .filter(folder => fs.existsSync(`node_modules/${folder}/binding.gyp`)); 14 | 15 | try { 16 | // Find the reason for why the dependency is installed. If it is installed 17 | // because of a devDependency then that is okay. Warn when it is installed 18 | // because of a dependency 19 | const dependenciesObject = JSON.parse( 20 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString() 21 | ); 22 | const rootDependencies = Object.keys(dependenciesObject.dependencies); 23 | const filteredRootDependencies = rootDependencies.filter(rootDependency => 24 | dependenciesKeys.includes(rootDependency) 25 | ); 26 | 27 | if (filteredRootDependencies.length > 0) { 28 | const plural = filteredRootDependencies.length > 1; 29 | console.log(` 30 | 31 | ${chalk.whiteBright.bgYellow.bold( 32 | 'Webpack does not work with native dependencies.' 33 | )} 34 | ${chalk.bold(filteredRootDependencies.join(', '))} ${ 35 | plural ? 'are native dependencies' : 'is a native dependency' 36 | } and should be installed inside of the "./app" folder. 37 | 38 | 39 | First uninstall the packages from "./package.json": 40 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')} 41 | 42 | ${chalk.bold( 43 | 'Then, instead of installing the package to the root "./package.json":' 44 | )} 45 | ${chalk.whiteBright.bgRed.bold('npm install your-package --save')} 46 | 47 | ${chalk.bold('Install the package to "./app/package.json"')} 48 | ${chalk.whiteBright.bgGreen.bold('cd ./app && npm install your-package --save')} 49 | 50 | 51 | Read more about native dependencies at: 52 | ${chalk.bold( 53 | 'https://github.com/chentsulin/electron-react-boilerplate/wiki/Module-Structure----Two-package.json-Structure' 54 | )} 55 | 56 | 57 | `); 58 | 59 | process.exit(1); 60 | } 61 | } catch (e) { 62 | console.log('Native dependencies could not be checked'); 63 | } 64 | })(); 65 | -------------------------------------------------------------------------------- /app/main.dev.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0, flowtype-errors/show-errors: 0 */ 2 | 3 | /** 4 | * This module executes inside of electron's main process. You can start 5 | * electron renderer process from here and communicate with the other processes 6 | * through IPC. 7 | * 8 | * When running `npm run build` or `npm run build-main`, this file is compiled to 9 | * `./app/main.prod.js` using webpack. This gives us some performance wins. 10 | * 11 | * @flow 12 | */ 13 | import { app, BrowserWindow } from 'electron'; 14 | import MenuBuilder from './menu'; 15 | 16 | let mainWindow = null; 17 | 18 | if (process.env.NODE_ENV === 'production') { 19 | const sourceMapSupport = require('source-map-support'); 20 | sourceMapSupport.install(); 21 | } 22 | 23 | if ( 24 | process.env.NODE_ENV === 'development' || 25 | process.env.DEBUG_PROD === 'true' 26 | ) { 27 | require('electron-debug')(); 28 | const path = require('path'); 29 | const p = path.join(__dirname, '..', 'app', 'node_modules'); 30 | require('module').globalPaths.push(p); 31 | } 32 | 33 | const installExtensions = async () => { 34 | const installer = require('electron-devtools-installer'); 35 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS; 36 | const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS']; 37 | 38 | return Promise.all( 39 | extensions.map(name => installer.default(installer[name], forceDownload)) 40 | ).catch(console.log); 41 | }; 42 | 43 | /** 44 | * Add event listeners... 45 | */ 46 | 47 | app.on('window-all-closed', () => { 48 | // Respect the OSX convention of having the application in memory even 49 | // after all windows have been closed 50 | if (process.platform !== 'darwin') { 51 | app.quit(); 52 | } 53 | }); 54 | 55 | app.on('ready', async () => { 56 | if ( 57 | process.env.NODE_ENV === 'development' || 58 | process.env.DEBUG_PROD === 'true' 59 | ) { 60 | await installExtensions(); 61 | } 62 | 63 | mainWindow = new BrowserWindow({ 64 | show: false, 65 | width: 1130, 66 | height: 730 67 | }); 68 | 69 | mainWindow.loadURL(`file://${__dirname}/app.html`); 70 | 71 | // @TODO: Use 'ready-to-show' event 72 | // https://github.com/electron/electron/blob/master/docs/api/browser-window.md#using-ready-to-show-event 73 | mainWindow.webContents.on('did-finish-load', () => { 74 | if (!mainWindow) { 75 | throw new Error('"mainWindow" is not defined'); 76 | } 77 | mainWindow.show(); 78 | mainWindow.focus(); 79 | }); 80 | 81 | mainWindow.on('closed', () => { 82 | mainWindow = null; 83 | }); 84 | 85 | const menuBuilder = new MenuBuilder(mainWindow); 86 | menuBuilder.buildMenu(); 87 | }); 88 | -------------------------------------------------------------------------------- /resources/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub](https://img.shields.io/github/tag/mariorodeghiero/git-commands.svg?style=flat-square)](https://github.com/mariorodeghiero/git-commands/tags) 2 | [![The MIT License](https://img.shields.io/badge/license-MIT-orange.svg?style=flat-square)](http://opensource.org/licenses/MIT) 3 | 4 | # Git Commands 5 | 6 | Git Commands is a desktop app built with ElectronJS and ReactJS to be cross-platform, providing quick access to the main Git commands and with direct links to the documentation. 7 | 8 | ## Screenshot 9 | 10 | ![Git Commands](./resources/screenShot-1.png) 11 | 12 | **Note!!** You can download the App for **Mac** and **Windows** in [Git Commands](https://mariorodeghiero.com/git-commands/). 13 | 14 | ## Built With 15 | 16 | * [ReactJS](https://reactjs.org) - ReactJS 17 | * [ElectronJS](https://electronjs.org) - ElectronJS 18 | * [electron-react-boilerplate](https://github.com/chentsulin/electron-react-boilerplate) - electron-react-boilerplate 19 | 20 | ## Prerequisites 21 | 22 | * **Requires a node version >= 7 and an npm version >= 4.** 23 | * **If you have installation or compilation issues with this project, please see [our debugging guide](https://github.com/mariorodeghiero/git-commands/issues/2)** 24 | 25 | ## Install 26 | 27 | First, clone the repo via git: 28 | 29 | ```bash 30 | git clone --depth=1 https://github.com/mariorodeghiero/git-commands.git your-project-name 31 | ``` 32 | 33 | And then install dependencies with yarn. 34 | 35 | ```bash 36 | $ cd your-project-name 37 | $ yarn 38 | ``` 39 | 40 | **Note**: If you can't use [yarn](https://github.com/yarnpkg/yarn), run `npm install`. 41 | 42 | ## Run 43 | 44 | Start the app in the `dev` environment: 45 | 46 | ```bash 47 | $ npm run dev 48 | ``` 49 | 50 | ## Packaging 51 | 52 | To package apps for the local platform: 53 | 54 | ```bash 55 | $ npm run package 56 | ``` 57 | 58 | To package apps for all platforms: 59 | 60 | First, refer to [Multi Platform Build](https://www.electron.build/multi-platform-build) for dependencies. 61 | 62 | Then, 63 | 64 | ```bash 65 | $ npm run package-all 66 | ``` 67 | 68 | To package apps with options: 69 | 70 | ```bash 71 | $ npm run package -- --[option] 72 | ``` 73 | 74 | To improve the usability of this App, you can create keyboard shortcuts to start quickly. 75 | 76 | * For Mac_OS use "Automator". 77 | * For Windows access "Properties" of the App. 78 | 79 | ## Contributing 80 | 81 | Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. 82 | 83 | ## Author 84 | 85 | * [Mário Antônio do Amaral Rodeghiero](https://github.com/mariorodeghiero) 86 | 87 | ## Contributors 88 | 89 | | [![Mário Rodeghiero](https://avatars1.githubusercontent.com/u/24671133?s=88&v=4)](https://github.com/mariorodeghiero) | 90 | | --------------------------------------------------------------------------------------------------------------------- | 91 | | [Mário Rodeghiero](https://github.com/mariorodeghiero) 92 | 93 | ## License 94 | 95 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 96 | -------------------------------------------------------------------------------- /test/e2e/e2e.spec.js: -------------------------------------------------------------------------------- 1 | import { Application } from 'spectron'; 2 | import electronPath from 'electron'; 3 | import path from 'path'; 4 | 5 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; 6 | 7 | const delay = time => new Promise(resolve => setTimeout(resolve, time)); 8 | 9 | describe('main window', function spec() { 10 | beforeAll(async () => { 11 | this.app = new Application({ 12 | path: electronPath, 13 | args: [path.join(__dirname, '..', '..', 'app')] 14 | }); 15 | 16 | return this.app.start(); 17 | }); 18 | 19 | afterAll(() => { 20 | if (this.app && this.app.isRunning()) { 21 | return this.app.stop(); 22 | } 23 | }); 24 | 25 | const findCounter = () => this.app.client.element('[data-tid="counter"]'); 26 | 27 | const findButtons = async () => { 28 | const { value } = await this.app.client.elements('[data-tclass="btn"]'); 29 | return value.map(btn => btn.ELEMENT); 30 | }; 31 | 32 | it('should open window', async () => { 33 | const { client, browserWindow } = this.app; 34 | 35 | await client.waitUntilWindowLoaded(); 36 | await delay(500); 37 | const title = await browserWindow.getTitle(); 38 | expect(title).toBe('Hello Electron React!'); 39 | }); 40 | 41 | it("should haven't any logs in console of main window", async () => { 42 | const { client } = this.app; 43 | const logs = await client.getRenderProcessLogs(); 44 | // Print renderer process logs 45 | logs.forEach(log => { 46 | console.log(log.message); 47 | console.log(log.source); 48 | console.log(log.level); 49 | expect(log.level).not.toEqual('SEVERE'); 50 | }); 51 | // @NOTE: Temporarily have to disable this assertion because there are some warnings in 52 | // electron@2. Loading files from localhost in development uses http and this causes 53 | // electron to throw warnings 54 | // expect(logs).toHaveLength(0); 55 | }); 56 | 57 | it('should to Counter with click "to Counter" link', async () => { 58 | const { client } = this.app; 59 | 60 | await client.click('[data-tid=container] > a'); 61 | expect(await findCounter().getText()).toBe('0'); 62 | }); 63 | 64 | it('should display updated count after increment button click', async () => { 65 | const { client } = this.app; 66 | 67 | const buttons = await findButtons(); 68 | await client.elementIdClick(buttons[0]); // + 69 | expect(await findCounter().getText()).toBe('1'); 70 | }); 71 | 72 | it('should display updated count after descrement button click', async () => { 73 | const { client } = this.app; 74 | 75 | const buttons = await findButtons(); 76 | await client.elementIdClick(buttons[1]); // - 77 | expect(await findCounter().getText()).toBe('0'); 78 | }); 79 | 80 | it('shouldnt change if even and if odd button clicked', async () => { 81 | const { client } = this.app; 82 | 83 | const buttons = await findButtons(); 84 | await client.elementIdClick(buttons[2]); // odd 85 | expect(await findCounter().getText()).toBe('0'); 86 | }); 87 | 88 | it('should change if odd and if odd button clicked', async () => { 89 | const { client } = this.app; 90 | 91 | const buttons = await findButtons(); 92 | await client.elementIdClick(buttons[0]); // + 93 | await client.elementIdClick(buttons[2]); // odd 94 | expect(await findCounter().getText()).toBe('2'); 95 | }); 96 | 97 | it('should change if async button clicked and a second later', async () => { 98 | const { client } = this.app; 99 | 100 | const buttons = await findButtons(); 101 | await client.elementIdClick(buttons[3]); // async 102 | expect(await findCounter().getText()).toBe('2'); 103 | await delay(3000); 104 | expect(await findCounter().getText()).toBe('3'); 105 | }); 106 | 107 | it('should back to home if back button clicked', async () => { 108 | const { client } = this.app; 109 | await client.element('[data-tid="backButton"] > a').click(); 110 | 111 | expect(await client.isExisting('[data-tid="container"]')).toBe(true); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Git Commands 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 41 |
42 | 43 |

Welcome to Git Commands

44 | Gif example 46 |
47 | Download for Mac OS 49 | Download for Windows 51 | 52 |
53 | 54 | 55 | 63 | 64 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /webpack.config.renderer.prod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Build config for electron renderer process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 8 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 9 | import merge from 'webpack-merge'; 10 | import UglifyJSPlugin from 'uglifyjs-webpack-plugin'; 11 | import baseConfig from './webpack.config.base'; 12 | import CheckNodeEnv from './internals/scripts/CheckNodeEnv'; 13 | 14 | CheckNodeEnv('production'); 15 | 16 | export default merge.smart(baseConfig, { 17 | devtool: 'source-map', 18 | 19 | mode: 'production', 20 | 21 | target: 'electron-renderer', 22 | 23 | entry: './app/index', 24 | 25 | output: { 26 | path: path.join(__dirname, 'app/dist'), 27 | publicPath: './dist/', 28 | filename: 'renderer.prod.js' 29 | }, 30 | 31 | module: { 32 | rules: [ 33 | // Extract all .global.css to style.css as is 34 | { 35 | test: /\.global\.css$/, 36 | use: ExtractTextPlugin.extract({ 37 | publicPath: './', 38 | use: { 39 | loader: 'css-loader', 40 | options: { 41 | minimize: true 42 | } 43 | }, 44 | fallback: 'style-loader' 45 | }) 46 | }, 47 | // Pipe other styles through css modules and append to style.css 48 | { 49 | test: /^((?!\.global).)*\.css$/, 50 | use: ExtractTextPlugin.extract({ 51 | use: { 52 | loader: 'css-loader', 53 | options: { 54 | modules: true, 55 | minimize: true, 56 | importLoaders: 1, 57 | localIdentName: '[name]__[local]__[hash:base64:5]' 58 | } 59 | } 60 | }) 61 | }, 62 | // Add SASS support - compile all .global.scss files and pipe it to style.css 63 | { 64 | test: /\.global\.(scss|sass)$/, 65 | use: ExtractTextPlugin.extract({ 66 | use: [ 67 | { 68 | loader: 'css-loader', 69 | options: { 70 | minimize: true 71 | } 72 | }, 73 | { 74 | loader: 'sass-loader' 75 | } 76 | ], 77 | fallback: 'style-loader' 78 | }) 79 | }, 80 | // Add SASS support - compile all other .scss files and pipe it to style.css 81 | { 82 | test: /^((?!\.global).)*\.(scss|sass)$/, 83 | use: ExtractTextPlugin.extract({ 84 | use: [ 85 | { 86 | loader: 'css-loader', 87 | options: { 88 | modules: true, 89 | minimize: true, 90 | importLoaders: 1, 91 | localIdentName: '[name]__[local]__[hash:base64:5]' 92 | } 93 | }, 94 | { 95 | loader: 'sass-loader' 96 | } 97 | ] 98 | }) 99 | }, 100 | // WOFF Font 101 | { 102 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 103 | use: { 104 | loader: 'url-loader', 105 | options: { 106 | limit: 10000, 107 | mimetype: 'application/font-woff' 108 | } 109 | } 110 | }, 111 | // WOFF2 Font 112 | { 113 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 114 | use: { 115 | loader: 'url-loader', 116 | options: { 117 | limit: 10000, 118 | mimetype: 'application/font-woff' 119 | } 120 | } 121 | }, 122 | // TTF Font 123 | { 124 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 125 | use: { 126 | loader: 'url-loader', 127 | options: { 128 | limit: 10000, 129 | mimetype: 'application/octet-stream' 130 | } 131 | } 132 | }, 133 | // EOT Font 134 | { 135 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 136 | use: 'file-loader' 137 | }, 138 | // SVG Font 139 | { 140 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 141 | use: { 142 | loader: 'url-loader', 143 | options: { 144 | limit: 10000, 145 | mimetype: 'image/svg+xml' 146 | } 147 | } 148 | }, 149 | // Common Image Formats 150 | { 151 | test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/, 152 | use: 'url-loader' 153 | } 154 | ] 155 | }, 156 | 157 | plugins: [ 158 | /** 159 | * Create global constants which can be configured at compile time. 160 | * 161 | * Useful for allowing different behaviour between development builds and 162 | * release builds 163 | * 164 | * NODE_ENV should be production so that modules do not perform certain 165 | * development checks 166 | */ 167 | new webpack.EnvironmentPlugin({ 168 | NODE_ENV: 'production' 169 | }), 170 | 171 | new UglifyJSPlugin({ 172 | parallel: true, 173 | sourceMap: true 174 | }), 175 | 176 | new ExtractTextPlugin('style.css'), 177 | 178 | new BundleAnalyzerPlugin({ 179 | analyzerMode: 180 | process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled', 181 | openAnalyzer: process.env.OPEN_ANALYZER === 'true' 182 | }) 183 | ] 184 | }); 185 | -------------------------------------------------------------------------------- /app/menu.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { app, Menu, shell, BrowserWindow } from 'electron'; 3 | 4 | export default class MenuBuilder { 5 | mainWindow: BrowserWindow; 6 | 7 | constructor(mainWindow: BrowserWindow) { 8 | this.mainWindow = mainWindow; 9 | } 10 | 11 | buildMenu() { 12 | if ( 13 | process.env.NODE_ENV === 'development' || 14 | process.env.DEBUG_PROD === 'true' 15 | ) { 16 | this.setupDevelopmentEnvironment(); 17 | } 18 | 19 | const template = process.platform === 'darwin' 20 | ? this.buildDarwinTemplate() 21 | : this.buildDefaultTemplate(); 22 | 23 | const menu = Menu.buildFromTemplate(template); 24 | Menu.setApplicationMenu(menu); 25 | 26 | return menu; 27 | } 28 | 29 | setupDevelopmentEnvironment() { 30 | this.mainWindow.openDevTools(); 31 | this.mainWindow.webContents.on('context-menu', (e, props) => { 32 | const { x, y } = props; 33 | 34 | Menu.buildFromTemplate([ 35 | { 36 | label: 'Inspect element', 37 | click: () => { 38 | this.mainWindow.inspectElement(x, y); 39 | } 40 | } 41 | ]).popup(this.mainWindow); 42 | }); 43 | } 44 | 45 | buildDarwinTemplate() { 46 | const subMenuAbout = { 47 | label: 'Git Commands', 48 | submenu: [ 49 | { 50 | label: 'About Git Commands', 51 | selector: 'orderFrontStandardAboutPanel:' 52 | }, 53 | { 54 | label: 'Hide CheatSheet', 55 | accelerator: 'Command+H', 56 | selector: 'hide:' 57 | }, 58 | { 59 | label: 'Hide Others', 60 | accelerator: 'Command+Shift+H', 61 | selector: 'hideOtherApplications:' 62 | }, 63 | { label: 'Show All', selector: 'unhideAllApplications:' }, 64 | { type: 'separator' }, 65 | { 66 | label: 'Quit', 67 | accelerator: 'Command+Q', 68 | click: () => { 69 | app.quit(); 70 | } 71 | } 72 | ] 73 | }; 74 | const subMenuEdit = { 75 | label: 'Edit', 76 | submenu: [ 77 | { label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' }, 78 | { label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' }, 79 | { type: 'separator' }, 80 | { label: 'Cut', accelerator: 'Command+X', selector: 'cut:' }, 81 | { label: 'Copy', accelerator: 'Command+C', selector: 'copy:' }, 82 | { label: 'Paste', accelerator: 'Command+V', selector: 'paste:' }, 83 | { 84 | label: 'Select All', 85 | accelerator: 'Command+A', 86 | selector: 'selectAll:' 87 | } 88 | ] 89 | }; 90 | const subMenuViewDev = { 91 | label: 'View', 92 | submenu: [ 93 | { 94 | label: 'Reload', 95 | accelerator: 'Command+R', 96 | click: () => { 97 | this.mainWindow.webContents.reload(); 98 | } 99 | }, 100 | { 101 | label: 'Toggle Full Screen', 102 | accelerator: 'Ctrl+Command+F', 103 | click: () => { 104 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 105 | } 106 | }, 107 | { 108 | label: 'Toggle Developer Tools', 109 | accelerator: 'Alt+Command+I', 110 | click: () => { 111 | this.mainWindow.toggleDevTools(); 112 | } 113 | } 114 | ] 115 | }; 116 | const subMenuViewProd = { 117 | label: 'View', 118 | submenu: [ 119 | { 120 | label: 'Toggle Full Screen', 121 | accelerator: 'Ctrl+Command+F', 122 | click: () => { 123 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 124 | } 125 | } 126 | ] 127 | }; 128 | const subMenuWindow = { 129 | label: 'Window', 130 | submenu: [ 131 | { 132 | label: 'Minimize', 133 | accelerator: 'Command+M', 134 | selector: 'performMiniaturize:' 135 | }, 136 | { label: 'Close', accelerator: 'Command+W', selector: 'performClose:' }, 137 | { type: 'separator' }, 138 | { label: 'Bring All to Front', selector: 'arrangeInFront:' } 139 | ] 140 | }; 141 | const subMenuHelp = { 142 | label: 'Help', 143 | submenu: [ 144 | { 145 | label: 'Learn More', 146 | click() { 147 | shell.openExternal('https://github.com/mariorodeghiero/git-commands'); 148 | } 149 | }, 150 | { 151 | label: 'Documentation', 152 | click() { 153 | shell.openExternal( 154 | 'https://github.com/mariorodeghiero/git-commands/blob/master/README.md' 155 | ); 156 | } 157 | }, 158 | { 159 | label: 'Search Issues', 160 | click() { 161 | shell.openExternal('https://github.com/mariorodeghiero/git-commands/issues'); 162 | } 163 | } 164 | ] 165 | }; 166 | 167 | const subMenuView = 168 | process.env.NODE_ENV === 'development' ? subMenuViewDev : subMenuViewProd; 169 | 170 | return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp]; 171 | } 172 | 173 | buildDefaultTemplate() { 174 | const templateDefault = [ 175 | { 176 | label: '&File', 177 | submenu: [ 178 | { 179 | label: '&Open', 180 | accelerator: 'Ctrl+O' 181 | }, 182 | { 183 | label: '&Close', 184 | accelerator: 'Ctrl+W', 185 | click: () => { 186 | this.mainWindow.close(); 187 | } 188 | } 189 | ] 190 | }, 191 | { 192 | label: '&View', 193 | submenu: 194 | process.env.NODE_ENV === 'development' 195 | ? [ 196 | { 197 | label: '&Reload', 198 | accelerator: 'Ctrl+R', 199 | click: () => { 200 | this.mainWindow.webContents.reload(); 201 | } 202 | }, 203 | { 204 | label: 'Toggle &Full Screen', 205 | accelerator: 'F11', 206 | click: () => { 207 | this.mainWindow.setFullScreen( 208 | !this.mainWindow.isFullScreen() 209 | ); 210 | } 211 | }, 212 | { 213 | label: 'Toggle &Developer Tools', 214 | accelerator: 'Alt+Ctrl+I', 215 | click: () => { 216 | this.mainWindow.toggleDevTools(); 217 | } 218 | } 219 | ] 220 | : [ 221 | { 222 | label: 'Toggle &Full Screen', 223 | accelerator: 'F11', 224 | click: () => { 225 | this.mainWindow.setFullScreen( 226 | !this.mainWindow.isFullScreen() 227 | ); 228 | } 229 | } 230 | ] 231 | }, 232 | { 233 | label: 'Help', 234 | submenu: [ 235 | { 236 | label: 'Learn More', 237 | click() { 238 | shell.openExternal('https://github.com/mariorodeghiero/git-commands'); 239 | } 240 | }, 241 | { 242 | label: 'Documentation', 243 | click() { 244 | shell.openExternal( 245 | 'https://github.com/mariorodeghiero/git-commands/blob/master/README.md' 246 | ); 247 | } 248 | }, 249 | { 250 | label: 'Search Issues', 251 | click() { 252 | shell.openExternal('https://github.com/mariorodeghiero/git-commands/issues'); 253 | } 254 | } 255 | ] 256 | } 257 | ]; 258 | 259 | return templateDefault; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /webpack.config.renderer.dev.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0, import/no-dynamic-require: 0 */ 2 | 3 | /** 4 | * Build config for development electron renderer process that uses 5 | * Hot-Module-Replacement 6 | * 7 | * https://webpack.js.org/concepts/hot-module-replacement/ 8 | */ 9 | 10 | import path from 'path'; 11 | import fs from 'fs'; 12 | import webpack from 'webpack'; 13 | import chalk from 'chalk'; 14 | import merge from 'webpack-merge'; 15 | import { spawn, execSync } from 'child_process'; 16 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 17 | import baseConfig from './webpack.config.base'; 18 | import CheckNodeEnv from './internals/scripts/CheckNodeEnv'; 19 | 20 | CheckNodeEnv('development'); 21 | 22 | const port = process.env.PORT || 1212; 23 | const publicPath = `http://localhost:${port}/dist`; 24 | const dll = path.resolve(process.cwd(), 'dll'); 25 | const manifest = path.resolve(dll, 'renderer.json'); 26 | const requiredByDLLConfig = module.parent.filename.includes( 27 | 'webpack.config.renderer.dev.dll' 28 | ); 29 | 30 | /** 31 | * Warn if the DLL is not built 32 | */ 33 | if (!requiredByDLLConfig && !(fs.existsSync(dll) && fs.existsSync(manifest))) { 34 | console.log( 35 | chalk.black.bgYellow.bold( 36 | 'The DLL files are missing. Sit back while we build them for you with "npm run build-dll"' 37 | ) 38 | ); 39 | execSync('npm run build-dll'); 40 | } 41 | 42 | export default merge.smart(baseConfig, { 43 | devtool: 'inline-source-map', 44 | 45 | mode: 'development', 46 | 47 | target: 'electron-renderer', 48 | 49 | entry: [ 50 | 'react-hot-loader/patch', 51 | `webpack-dev-server/client?http://localhost:${port}/`, 52 | 'webpack/hot/only-dev-server', 53 | path.join(__dirname, 'app/index.js') 54 | ], 55 | 56 | output: { 57 | publicPath: `http://localhost:${port}/dist/`, 58 | filename: 'renderer.dev.js' 59 | }, 60 | 61 | module: { 62 | rules: [ 63 | { 64 | test: /\.jsx?$/, 65 | exclude: /node_modules/, 66 | use: { 67 | loader: 'babel-loader', 68 | options: { 69 | cacheDirectory: true, 70 | plugins: [ 71 | // Here, we include babel plugins that are only required for the 72 | // renderer process. The 'transform-*' plugins must be included 73 | // before react-hot-loader/babel 74 | 'transform-class-properties', 75 | 'transform-es2015-classes', 76 | 'react-hot-loader/babel' 77 | ] 78 | } 79 | } 80 | }, 81 | { 82 | test: /\.global\.css$/, 83 | use: [ 84 | { 85 | loader: 'style-loader' 86 | }, 87 | { 88 | loader: 'css-loader', 89 | options: { 90 | sourceMap: true 91 | } 92 | } 93 | ] 94 | }, 95 | { 96 | test: /^((?!\.global).)*\.css$/, 97 | use: [ 98 | { 99 | loader: 'style-loader' 100 | }, 101 | { 102 | loader: 'css-loader', 103 | options: { 104 | modules: true, 105 | sourceMap: true, 106 | importLoaders: 1, 107 | localIdentName: '[name]__[local]__[hash:base64:5]' 108 | } 109 | } 110 | ] 111 | }, 112 | // SASS support - compile all .global.scss files and pipe it to style.css 113 | { 114 | test: /\.global\.(scss|sass)$/, 115 | use: [ 116 | { 117 | loader: 'style-loader' 118 | }, 119 | { 120 | loader: 'css-loader', 121 | options: { 122 | sourceMap: true 123 | } 124 | }, 125 | { 126 | loader: 'sass-loader' 127 | } 128 | ] 129 | }, 130 | // SASS support - compile all other .scss files and pipe it to style.css 131 | { 132 | test: /^((?!\.global).)*\.(scss|sass)$/, 133 | use: [ 134 | { 135 | loader: 'style-loader' 136 | }, 137 | { 138 | loader: 'css-loader', 139 | options: { 140 | modules: true, 141 | sourceMap: true, 142 | importLoaders: 1, 143 | localIdentName: '[name]__[local]__[hash:base64:5]' 144 | } 145 | }, 146 | { 147 | loader: 'sass-loader' 148 | } 149 | ] 150 | }, 151 | // WOFF Font 152 | { 153 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 154 | use: { 155 | loader: 'url-loader', 156 | options: { 157 | limit: 10000, 158 | mimetype: 'application/font-woff' 159 | } 160 | } 161 | }, 162 | // WOFF2 Font 163 | { 164 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 165 | use: { 166 | loader: 'url-loader', 167 | options: { 168 | limit: 10000, 169 | mimetype: 'application/font-woff' 170 | } 171 | } 172 | }, 173 | // TTF Font 174 | { 175 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 176 | use: { 177 | loader: 'url-loader', 178 | options: { 179 | limit: 10000, 180 | mimetype: 'application/octet-stream' 181 | } 182 | } 183 | }, 184 | // EOT Font 185 | { 186 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 187 | use: 'file-loader' 188 | }, 189 | // SVG Font 190 | { 191 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 192 | use: { 193 | loader: 'url-loader', 194 | options: { 195 | limit: 10000, 196 | mimetype: 'image/svg+xml' 197 | } 198 | } 199 | }, 200 | // Common Image Formats 201 | { 202 | test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/, 203 | use: 'url-loader' 204 | } 205 | ] 206 | }, 207 | 208 | plugins: [ 209 | requiredByDLLConfig 210 | ? null 211 | : new webpack.DllReferencePlugin({ 212 | context: process.cwd(), 213 | manifest: require(manifest), 214 | sourceType: 'var' 215 | }), 216 | 217 | new webpack.HotModuleReplacementPlugin({ 218 | multiStep: true 219 | }), 220 | 221 | new webpack.NoEmitOnErrorsPlugin(), 222 | 223 | /** 224 | * Create global constants which can be configured at compile time. 225 | * 226 | * Useful for allowing different behaviour between development builds and 227 | * release builds 228 | * 229 | * NODE_ENV should be production so that modules do not perform certain 230 | * development checks 231 | * 232 | * By default, use 'development' as NODE_ENV. This can be overriden with 233 | * 'staging', for example, by changing the ENV variables in the npm scripts 234 | */ 235 | new webpack.EnvironmentPlugin({ 236 | NODE_ENV: 'development' 237 | }), 238 | 239 | new webpack.LoaderOptionsPlugin({ 240 | debug: true 241 | }), 242 | 243 | new ExtractTextPlugin({ 244 | filename: '[name].css' 245 | }) 246 | ], 247 | 248 | node: { 249 | __dirname: false, 250 | __filename: false 251 | }, 252 | 253 | devServer: { 254 | port, 255 | publicPath, 256 | compress: true, 257 | noInfo: true, 258 | stats: 'errors-only', 259 | inline: true, 260 | lazy: false, 261 | hot: true, 262 | headers: { 'Access-Control-Allow-Origin': '*' }, 263 | contentBase: path.join(__dirname, 'dist'), 264 | watchOptions: { 265 | aggregateTimeout: 300, 266 | ignored: /node_modules/, 267 | poll: 100 268 | }, 269 | historyApiFallback: { 270 | verbose: true, 271 | disableDotRule: false 272 | }, 273 | before() { 274 | if (process.env.START_HOT) { 275 | console.log('Starting Main Process...'); 276 | spawn('npm', ['run', 'start-main-dev'], { 277 | shell: true, 278 | env: process.env, 279 | stdio: 'inherit' 280 | }) 281 | .on('close', code => process.exit(code)) 282 | .on('error', spawnError => console.error(spawnError)); 283 | } 284 | } 285 | } 286 | }); 287 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-react-boilerplate", 3 | "productName": "Git Commands", 4 | "version": "0.13.3", 5 | "description": 6 | "Electron application boilerplate based on React, React Router, Webpack, React Hot Loader for rapid application development", 7 | "scripts": { 8 | "build": "concurrently \"npm run build-main\" \"npm run build-renderer\"", 9 | "build-dll": 10 | "cross-env NODE_ENV=development node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.renderer.dev.dll.js --colors", 11 | "build-main": 12 | "cross-env NODE_ENV=production node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.main.prod.js --colors", 13 | "build-renderer": 14 | "cross-env NODE_ENV=production node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.renderer.prod.js --colors", 15 | "dev": 16 | "cross-env START_HOT=1 node -r babel-register ./internals/scripts/CheckPortInUse.js && cross-env START_HOT=1 npm run start-renderer-dev", 17 | "electron-rebuild": 18 | "electron-rebuild --parallel --force --types prod,dev,optional --module-dir app", 19 | "flow": "flow", 20 | "flow-typed": 21 | "rimraf flow-typed/npm && flow-typed install --overwrite || true", 22 | "lint": 23 | "cross-env NODE_ENV=development eslint --cache --format=node_modules/eslint-formatter-pretty .", 24 | "lint-fix": "npm run --silent lint -- --fix; exit 0", 25 | "lint-styles": "stylelint app/*.css app/components/*.css --syntax scss", 26 | "lint-styles-fix": "stylefmt -r app/*.css app/components/*.css", 27 | "package": "npm run build && build --publish never", 28 | "package-all": "npm run build && build -mwl", 29 | "package-linux": "npm run build && build --linux", 30 | "package-win": "npm run build && build --win --x64", 31 | "postinstall": 32 | "node -r babel-register internals/scripts/CheckNativeDep.js && npm run flow-typed && npm run build-dll && electron-builder install-app-deps && node node_modules/fbjs-scripts/node/check-dev-engines.js package.json", 33 | "postlint-fix": 34 | "prettier --ignore-path .eslintignore --single-quote --write '**/*.js'", 35 | "precommit": "lint-staged", 36 | "prestart": "npm run build", 37 | "start": "cross-env NODE_ENV=production electron ./app/", 38 | "start-main-dev": 39 | "cross-env HOT=1 NODE_ENV=development electron -r babel-register ./app/main.dev.js", 40 | "start-renderer-dev": 41 | "cross-env NODE_ENV=development node --trace-warnings -r babel-register ./node_modules/webpack-dev-server/bin/webpack-dev-server --config webpack.config.renderer.dev.js", 42 | "test": 43 | "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 node --trace-warnings -r babel-register ./internals/scripts/RunTests.js", 44 | "test-all": 45 | "npm run lint && npm run flow && npm run build && npm run test && npm run test-e2e", 46 | "test-e2e": 47 | "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 node --trace-warnings -r babel-register ./internals/scripts/RunTests.js e2e", 48 | "test-watch": "npm test -- --watch" 49 | }, 50 | "browserslist": "electron 1.6", 51 | "lint-staged": { 52 | "*.js": [ 53 | "cross-env NODE_ENV=development eslint --cache --format=node_modules/eslint-formatter-pretty", 54 | "prettier --ignore-path .eslintignore --single-quote --write", 55 | "git add" 56 | ] 57 | }, 58 | "build": { 59 | "productName": "Git Commands", 60 | "appId": "org.develar.ElectronReact", 61 | "files": [ 62 | "dist/", 63 | "node_modules/", 64 | "app.html", 65 | "main.prod.js", 66 | "main.prod.js.map", 67 | "package.json" 68 | ], 69 | "dmg": { 70 | "contents": [ 71 | { 72 | "x": 130, 73 | "y": 220 74 | }, 75 | { 76 | "x": 410, 77 | "y": 220, 78 | "type": "link", 79 | "path": "/Applications" 80 | } 81 | ] 82 | }, 83 | "win": { 84 | "target": ["nsis"] 85 | }, 86 | "linux": { 87 | "target": ["deb", "AppImage"], 88 | "category": "Development" 89 | }, 90 | "directories": { 91 | "buildResources": "resources", 92 | "output": "release" 93 | } 94 | }, 95 | "repository": { 96 | "type": "git", 97 | "url": "git+https://github.com/chentsulin/electron-react-boilerplate.git" 98 | }, 99 | "author": { 100 | "name": "Mário Antônio A. Rodeghiero", 101 | "email": "mario.rodeghiero@gmail.com", 102 | "url": "https://github.com/mariorodeghiero" 103 | }, 104 | "license": "MIT", 105 | "bugs": { 106 | "url": "https://github.com/chentsulin/electron-react-boilerplate/issues" 107 | }, 108 | "keywords": [ 109 | "electron", 110 | "boilerplate", 111 | "react", 112 | "redux", 113 | "flow", 114 | "sass", 115 | "webpack", 116 | "hot", 117 | "reload" 118 | ], 119 | "homepage": "https://github.com/chentsulin/electron-react-boilerplate#readme", 120 | "jest": { 121 | "moduleNameMapper": { 122 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": 123 | "/internals/mocks/fileMock.js", 124 | "\\.(css|less|sass|scss)$": "identity-obj-proxy" 125 | }, 126 | "moduleFileExtensions": ["js"], 127 | "moduleDirectories": ["node_modules", "app/node_modules"], 128 | "transform": { 129 | "^.+\\.js$": "babel-jest" 130 | }, 131 | "setupFiles": ["./internals/scripts/CheckBuiltsExist.js"] 132 | }, 133 | "devDependencies": { 134 | "babel-core": "^6.26.3", 135 | "babel-eslint": "^8.2.3", 136 | "babel-jest": "^23.0.0", 137 | "babel-loader": "^7.1.4", 138 | "babel-plugin-add-module-exports": "^0.2.1", 139 | "babel-plugin-dev-expression": "^0.2.1", 140 | "babel-plugin-flow-runtime": "^0.17.0", 141 | "babel-plugin-transform-class-properties": "^6.24.1", 142 | "babel-plugin-transform-es2015-classes": "^6.24.1", 143 | "babel-preset-env": "^1.7.0", 144 | "babel-preset-react": "^6.24.1", 145 | "babel-preset-react-optimize": "^1.0.1", 146 | "babel-preset-stage-0": "^6.24.1", 147 | "babel-register": "^6.26.0", 148 | "chalk": "^2.4.1", 149 | "concurrently": "^3.5.1", 150 | "cross-env": "^5.1.6", 151 | "cross-spawn": "^6.0.5", 152 | "css-loader": "^0.28.11", 153 | "detect-port": "^1.2.3", 154 | "electron": "^2.0.2", 155 | "electron-builder": "^20.14.7", 156 | "electron-devtools-installer": "^2.2.4", 157 | "electron-rebuild": "^1.7.3", 158 | "enzyme": "^3.3.0", 159 | "enzyme-adapter-react-16": "^1.1.1", 160 | "enzyme-to-json": "^3.3.4", 161 | "eslint": "^4.19.1", 162 | "eslint-config-airbnb": "^16.1.0", 163 | "eslint-config-prettier": "^2.9.0", 164 | "eslint-formatter-pretty": "^1.3.0", 165 | "eslint-import-resolver-webpack": "^0.10.0", 166 | "eslint-plugin-compat": "^2.2.0", 167 | "eslint-plugin-flowtype": "^2.47.1", 168 | "eslint-plugin-import": "^2.12.0", 169 | "eslint-plugin-jest": "^21.15.2", 170 | "eslint-plugin-jsx-a11y": "6.0.3", 171 | "eslint-plugin-promise": "^3.7.0", 172 | "eslint-plugin-react": "^7.8.2", 173 | "express": "^4.16.3", 174 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 175 | "fbjs-scripts": "^0.8.3", 176 | "file-loader": "^1.1.11", 177 | "flow-bin": "^0.72.0", 178 | "flow-runtime": "^0.17.0", 179 | "flow-typed": "^2.4.0", 180 | "husky": "^0.14.3", 181 | "identity-obj-proxy": "^3.0.0", 182 | "jest": "^23.0.0", 183 | "lint-staged": "^7.1.2", 184 | "minimist": "^1.2.0", 185 | "node-sass": "^4.9.0", 186 | "npm-logical-tree": "^1.2.1", 187 | "prettier": "^1.12.1", 188 | "react-test-renderer": "^16.4.0", 189 | "redux-logger": "^3.0.6", 190 | "rimraf": "^2.6.2", 191 | "sass-loader": "^7.0.1", 192 | "sinon": "^5.0.10", 193 | "spectron": "^3.8.0", 194 | "style-loader": "^0.21.0", 195 | "stylefmt": "^6.0.0", 196 | "stylelint": "^9.2.1", 197 | "stylelint-config-standard": "^18.2.0", 198 | "uglifyjs-webpack-plugin": "1.2.5", 199 | "url-loader": "^1.0.1", 200 | "webpack": "^4.8.3", 201 | "webpack-bundle-analyzer": "^2.13.1", 202 | "webpack-cli": "^2.1.4", 203 | "webpack-dev-server": "^3.1.4", 204 | "webpack-merge": "^4.1.2" 205 | }, 206 | "dependencies": { 207 | "devtron": "^1.4.0", 208 | "electron-debug": "^1.5.0", 209 | "font-awesome": "^4.7.0", 210 | "history": "^4.7.2", 211 | "react": "^16.4.0", 212 | "react-dom": "^16.4.0", 213 | "react-hot-loader": "^4.2.0", 214 | "react-redux": "^5.0.7", 215 | "react-router": "^4.2.0", 216 | "react-router-dom": "^4.2.2", 217 | "react-router-redux": "^5.0.0-alpha.6", 218 | "redux": "^4.0.0", 219 | "redux-thunk": "^2.2.0", 220 | "source-map-support": "^0.5.6" 221 | }, 222 | "devEngines": { 223 | "node": ">=7.x", 224 | "npm": ">=4.x", 225 | "yarn": ">=0.21.3" 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /app/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": [ 3 | { 4 | "title": "Configurations", 5 | "url": 6 | "https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup", 7 | "command": [ 8 | { 9 | "id": 1, 10 | "command": "git config --global user.name \"your name\"", 11 | "note": "Configure the author name." 12 | }, 13 | { 14 | "id": 2, 15 | "command": "git config --global user.email \"your@email.com\"", 16 | "note": "Configure the author email." 17 | }, 18 | { 19 | "id": 3, 20 | "command": "git config --global color.ui true", 21 | "note": "Git automatically colors most of its output." 22 | }, 23 | { 24 | "id": 4, 25 | "command": "git config --global format.pretty oneline", 26 | "note": "Configure default format" 27 | }, 28 | { 29 | "id": 5, 30 | "command": "git config --list", 31 | "note": "View your Git configurations." 32 | }, 33 | { 34 | "id": 6, 35 | "command": "git help config", 36 | "note": "Getting help in configuring." 37 | }, 38 | { 39 | "id": 7, 40 | "command": "git help", 41 | "note": "Getting Help." 42 | }, 43 | { 44 | "id": 8, 45 | "command": "git init", 46 | "note": "Initializing Git repository." 47 | }, 48 | { 49 | "id": 9, 50 | "command": "git status", 51 | "note": 52 | "List the files you've changed and those you still need to add or commit." 53 | } 54 | ] 55 | }, 56 | { 57 | "title": "Commit History", 58 | "url": 59 | "https://git-scm.com/book/en/v2/Git-Basics-Viewing-the-Commit-History", 60 | "command": [ 61 | { 62 | "id": 1, 63 | "command": "git diff --stat", 64 | "note": 65 | "Show changes between commits, commit and working tree, etc..." 66 | }, 67 | { 68 | "id": 2, 69 | "command": "git log", 70 | "note": 71 | "Lists the commits made in the repository in chronological order." 72 | }, 73 | { 74 | "id": 3, 75 | "command": "git log --author=name_author", 76 | "note": "List author" 77 | }, 78 | { 79 | "id": 4, 80 | "command": "git log --pretty=oneline", 81 | "note": "Shows each commit on a single line" 82 | }, 83 | { 84 | "id": 5, 85 | "command": "git log --graph --oneline --decorate --all", 86 | "note": "A simple one line to the point answer" 87 | }, 88 | { 89 | "id": 6, 90 | "command": "git log --stat", 91 | "note": "See some abbreviated statistics for each commit." 92 | }, 93 | { 94 | "id": 7, 95 | "command": "git log --help", 96 | "note": "List help options" 97 | }, 98 | { 99 | "id": 8, 100 | "command": "git log -m --name-status", 101 | "note": 102 | "Lists the name and status of each file included in only merge commits." 103 | } 104 | ] 105 | }, 106 | { 107 | "title": "Starting files", 108 | "url": 109 | "https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup", 110 | "command": [ 111 | { 112 | "id": 1, 113 | "command": "git add file-name.txt", 114 | "note": "Add a specific file." 115 | }, 116 | { 117 | "id": 2, 118 | "command": "git add file-name.ts another-file.js", 119 | "note": "Add two different files." 120 | }, 121 | { 122 | "id": 3, 123 | "command": "git add .", 124 | "note": "Add files located in the root directory." 125 | }, 126 | { 127 | "id": 4, 128 | "command": "git add --all", 129 | "note": 130 | "Add files from those other directories plus the files in the root directory to the staging area." 131 | }, 132 | { 133 | "id": 5, 134 | "command": "git rm --cached file-name.txt", 135 | "note": "Removing files from the staging area." 136 | }, 137 | { 138 | "id": 6, 139 | "command": "git reset file-name.txt", 140 | "note": "Untrack files." 141 | }, 142 | { 143 | "id": 7, 144 | "command": "git add -i", 145 | "note": 146 | "Add/remove/patch/diff files interactively to and from the staging area." 147 | } 148 | ] 149 | }, 150 | { 151 | "title": "Commiting", 152 | "url": 153 | "https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository#_committing_changes", 154 | "command": [ 155 | { 156 | "id": 1, 157 | "command": "git commit -m \"Add three files\"", 158 | "note": 159 | "Commit changes to head (but not yet to the remote repository)" 160 | }, 161 | { 162 | "id": 2, 163 | "command": "git commit -a -m \"Do something once more\"", 164 | "note": 165 | "Commit any files you've added with git add, and also commit any files you've changed since then." 166 | }, 167 | { 168 | "id": 3, 169 | "command": "git commit --amend -m \"enter your message\"", 170 | "note": "Make the current changes part of the previous commit." 171 | }, 172 | { 173 | "id": 4, 174 | "command": "git reset --soft HEAD^", 175 | "note": 176 | "To restore everything back to the way it was prior to the last commit and if you want to keep your changes." 177 | }, 178 | { 179 | "id": 5, 180 | "command": "git reset --hard origin/master", 181 | "note": 182 | "To restore everything back to the way it was prior to the last commit and if you DON'T care about keeping the changes." 183 | } 184 | ] 185 | }, 186 | { 187 | "title": "Remote repositories", 188 | "url": "https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes", 189 | "command": [ 190 | { 191 | "id": 1, 192 | "command": "git remote add origin https://url", 193 | "note": "Add a remote origin." 194 | }, 195 | { 196 | "id": 2, 197 | "command": "git remote set-url origin https://url", 198 | "note": "Set a remote origin" 199 | }, 200 | { 201 | "id": 3, 202 | "command": "git push -u origin master", 203 | "note": "Copying your code to a remote repository" 204 | }, 205 | { 206 | "id": 4, 207 | "command": "git push -f origin master", 208 | "note": "Forced update." 209 | }, 210 | { 211 | "id": 5, 212 | "command": "git remote -v", 213 | "note": "List all remote repositories." 214 | }, 215 | { 216 | "id": 6, 217 | "command": "git clone https://url", 218 | "note": "Clone an entire project from a remote repository." 219 | }, 220 | { 221 | "id": 7, 222 | "command": "git pull", 223 | "note": "Update your local repository." 224 | }, 225 | { 226 | "id": 8, 227 | "command": "git tag 1.0.0 commit_id", 228 | "note": "" 229 | }, 230 | { 231 | "id": 9, 232 | "command": "git tag -a v1.0.0 -m \"my version 1.0.0\"", 233 | "note": "" 234 | }, 235 | { 236 | "id": 10, 237 | "command": "git show v1.0", 238 | "note": "" 239 | } 240 | ] 241 | }, 242 | { 243 | "title": "Branching", 244 | "url": 245 | "https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging", 246 | "command": [ 247 | { 248 | "id": 1, 249 | "command": "git branch", 250 | "note": "See current branche." 251 | }, 252 | { 253 | "id": 2, 254 | "command": "git branch branch-name", 255 | "note": "Create a new branch." 256 | }, 257 | { 258 | "id": 3, 259 | "command": "git checkout branch-name", 260 | "note": "Switch branches." 261 | }, 262 | { 263 | "id": 4, 264 | "command": "git checkout -b branch-name", 265 | "note": "Create a new branch and switch to that branch right away." 266 | }, 267 | { 268 | "id": 5, 269 | "command": "git checkout branch-name fileName.txt", 270 | "note": "Recovers the file to the last Commit" 271 | }, 272 | { 273 | "id": 6, 274 | "command": "git merge branch-name", 275 | "note": "Merge Branch." 276 | } 277 | ] 278 | } 279 | ] 280 | } 281 | --------------------------------------------------------------------------------