├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── THANKS.md
├── app
├── dist
│ └── .gitkeep
├── electron.js
├── main.ejs
├── main
│ ├── menu.js
│ ├── repoUtils.js
│ ├── rpc.js
│ └── session.js
├── package.json
└── src
│ ├── App.vue
│ ├── components
│ ├── AboutView.vue
│ ├── AppView
│ │ ├── CustomCommandsInput.vue
│ │ ├── CustomCommandsList.vue
│ │ ├── HeaderBar.vue
│ │ └── Settings.vue
│ ├── HelpView.vue
│ ├── RepoListView.vue
│ ├── RepoListView
│ │ ├── KnownRepos.vue
│ │ └── OpenRepoButton.vue
│ ├── RepoView.vue
│ ├── RepoView
│ │ ├── Command.vue
│ │ ├── CommandOutput.vue
│ │ └── Repo.vue
│ └── UpdateAvailableView.vue
│ ├── defaults.js
│ ├── directives
│ ├── KeyTracker.vue
│ ├── OpenExternal.vue
│ └── StayDown.vue
│ ├── fonts
│ └── lato
│ │ ├── FONTLOG.txt
│ │ ├── Lato-Black.woff2
│ │ ├── Lato-BlackItalic.woff2
│ │ ├── Lato-Bold.woff2
│ │ ├── Lato-BoldItalic.woff2
│ │ ├── Lato-Hairline.woff2
│ │ ├── Lato-HairlineItalic.woff2
│ │ ├── Lato-Italic.woff2
│ │ ├── Lato-Light.woff2
│ │ ├── Lato-LightItalic.woff2
│ │ ├── Lato-Regular.woff2
│ │ └── OFL.txt
│ ├── main.js
│ ├── modules
│ ├── DomUtils.js
│ ├── Hterm.js
│ ├── Rpc.js
│ └── WindowKeyManager.js
│ ├── routes.js
│ ├── styles
│ ├── _utils.scss
│ ├── animations
│ │ └── _move-down.scss
│ ├── components
│ │ ├── _drag-handle.scss
│ │ └── _logo.scss
│ ├── fonts
│ │ └── _lato.scss
│ ├── objects
│ │ ├── _buttons.scss
│ │ ├── _checkbox.scss
│ │ ├── _code.scss
│ │ ├── _headlines.scss
│ │ ├── _icon.scss
│ │ ├── _input.scss
│ │ ├── _lists.scss
│ │ ├── _paragraphs.scss
│ │ └── _small.scss
│ └── transitions
│ │ ├── _slide-down--slide-up.scss
│ │ ├── _slide-left--slide-right.scss
│ │ ├── _slide-right--slide-left.scss
│ │ └── _slide-up--slide-down.scss
│ └── vuex
│ ├── actions.js
│ ├── getters.js
│ ├── modules
│ ├── app.js
│ ├── defaults.js
│ ├── index.js
│ ├── repos.js
│ └── session.js
│ └── store.js
├── build
├── background.png
├── background@2x.png
└── icon.icns
├── config.js
├── devtools
├── backend.js
├── devtools.html
├── devtools.js
└── hook.js
├── dist
└── .gitkeep
├── media
├── logo.jpg
└── preview.jpg
├── package.json
├── tasks
├── release.js
├── runner.js
├── vue
│ ├── route.js
│ ├── route.template.txt
│ └── routes.template.txt
└── vuex
│ ├── module.js
│ └── module.template.txt
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0"],
3 | "plugins": ["transform-runtime"]
4 | }
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # For all files in this project:
2 | # - 2-space soft tabs
3 | # - All files end with a line-break
4 | # - Trim trailing whitespace
5 | [*]
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | app/node_modules/**
2 | app/dist/**
3 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'env': {
3 | 'browser': true,
4 | 'commonjs': true,
5 | 'es6': true,
6 | 'node': true
7 | },
8 | plugins: [
9 | 'html'
10 | ],
11 | 'extends': 'eslint:recommended',
12 | 'parserOptions': {
13 | 'sourceType': 'module'
14 | },
15 | 'rules': {
16 | 'array-bracket-spacing' : [
17 | 'error',
18 | 'always'
19 | ],
20 | 'key-spacing' : [
21 | 2,
22 | {
23 | 'singleLine' : {
24 | 'beforeColon' : true,
25 | 'afterColon' : true
26 | },
27 | 'multiLine': {
28 | 'beforeColon' : true,
29 | 'afterColon' : true,
30 | 'align' : 'colon'
31 | }
32 | }
33 | ],
34 | 'space-in-parens': [
35 | 'error',
36 | 'always'
37 | ],
38 | 'indent': [
39 | 'error',
40 | 2
41 | ],
42 | 'linebreak-style': [
43 | 'error',
44 | 'unix'
45 | ],
46 | 'quotes': [
47 | 'error',
48 | 'single'
49 | ],
50 | 'semi': [
51 | 'error',
52 | 'always'
53 | ],
54 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
55 | 'no-console' : process.env.NODE_ENV === 'production' ? 2 : 0
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | app/dist/*
3 | dist/*
4 | node_modules
5 | npm-debug.log
6 | npm-debug.log.*
7 | thumbs.db
8 | !.gitkeep
9 | drafts.sketch
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "6"
4 | script: npm test
5 | sudo: required
6 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Forrest
2 |
3 | Forrest is a community-driven project. As such, we welcome and encourage all sorts of
4 | contributions. They include, but are not limited to:
5 |
6 | - Constructive feedback
7 | - Questions about usage
8 | - Documentation changes
9 | - Feature requests
10 | - [Pull requests](#filing-pull-requests)
11 |
12 | We strongly suggest that before filing an issue, you search through the existing issues to see
13 | if it has already been filed by someone else.
14 |
15 | ## Contribution suggestions
16 |
17 | We use the label [`help wanted`](https://github.com/stefanjudis/forrest/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) in the issue tracker to denote fairly-well-scoped-out bugs or feature requests that the community can pick up and work on. If any of those labeled issues do not have enough information, please feel free to ask constructive questions. (This applies to any open issue.)
18 |
19 | ## Filing Pull Requests
20 |
21 | Here are some things to keep in mind as you file pull requests to fix bugs, add new features, etc.:
22 |
23 | * Travis CI is used to make sure that the project builds packages as expected on the supported
24 | platforms, using supported Node.js versions.
25 | * Please make sure your commits are rebased onto the latest commit in the master branch, and that
26 | you limit/squash the number of commits created to a "feature"-level. For instance:
27 |
28 | bad:
29 |
30 | ```
31 | commit 1: add foo option
32 | commit 2: standardize code
33 | commit 3: add test
34 | commit 4: add docs
35 | commit 5: add bar option
36 | commit 6: add test + docs
37 | ```
38 |
39 | good:
40 |
41 | ```
42 | commit 1: add foo option
43 | commit 2: add bar option
44 | ```
45 |
46 | ## For Collaborators
47 |
48 | Make sure to get a `:thumbsup:`, `+1` or `LGTM` from another collaborator before merging a PR.
49 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Stefan Judis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | 
4 |
5 | [](http://forthebadge.com) [](http://forthebadge.com) [](http://forthebadge.com) [](http://forthebadge.com)
6 |
7 | # Forrest
8 |
9 | > "Run Forrest, run!!!"
10 |
11 | 
12 |
13 | ## About
14 |
15 | Forrest is an npm desktop client to deal with daily work flows. It lets you control common npm commands and all custom scripts defined in the `package.json`.
16 |
17 | It's still in early stages, but you can download the latest version at the [releases section](https://github.com/stefanjudis/forrest/releases). Feedback is more than welcome and contributions would be even better. :)
18 |
19 | ### Forrest is an [OPEN Open Source Project](http://openopensource.org/)
20 |
21 | Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project.
22 |
23 | See [CONTRIBUTING.md](./CONTRIBUTING.md) and [openopensource.org](http://openopensource.org/) for more details.
24 |
25 | ## Thanks
26 |
27 | - [Evan You](https://github.com/yyx990803) | *for creation and maintenance of vue.js*
28 | - [Greg Holguin](https://github.com/SimulatedGREG) | *for creation of the electron vue boilerplate*
29 | - [Sindre Sorhus](https://github.com/sindresorhus) | *for all this great work and especially for solving the issues around environments and the holy PATH*
30 | - [Michael Kühnel](https://github.com/mischah) | *for giving Forrest its name*
31 | - Calvin Goodman | *for creation of the "Home" icon from the Noun Project*
32 | - Michal Beno | *for creation of the "Settings" icon from the Noun Project*
33 |
34 | Additionally Forrest relies on the work of a lot of open source maintainers. So I want to thank all these [people](./THANKS.md) for their great work, too.
35 |
--------------------------------------------------------------------------------
/THANKS.md:
--------------------------------------------------------------------------------
1 | # Credits for forrest
2 | ## forrest relies on the work of 462 people:
3 |
4 | - **Sindre Sorhus** *sindresorhus@gmail.com* (119 packages)
5 | - **amasad** *amjad.masad@gmail.com* (72 packages)
6 | - **Sebastian McKenzie** *sebmck@gmail.com* (72 packages)
7 | - **Jesse McCarthy** *npm-public@jessemccarthy.net* (71 packages)
8 | - **hzoo** *hi@henryzoo.com* (71 packages)
9 | - **loganfsmyth** *loganfsmyth@gmail.com* (71 packages)
10 | - **thejameskyle** *me@thejameskyle.com* (69 packages)
11 | - **Isaac Z. Schlueter** *isaacs@npmjs.com* (57 packages)
12 | - **John-David Dalton** *john.david.dalton@gmail.com* (44 packages)
13 | - **Mathias Bynens** *mathias@qiwi.be* (43 packages)
14 | - **Jon Schlinkert** *github@sellside.com* (38 packages)
15 | - **Blaine Bublitz** *blaine@iceddev.com* (38 packages)
16 | - **Douglas Wilson** *doug@somethingdoug.com* (37 packages)
17 | - **Ben Briggs** *beneb.info@gmail.com* (29 packages)
18 | - **Rebecca Turner** *me@re-becca.org* (29 packages)
19 | - **Forrest L Norvell** *forrest@npmjs.com* (28 packages)
20 | - **James Halliday** *mail@substack.net* (27 packages)
21 | - **Tobias Koppers** *tobias.koppers@googlemail.com* (23 packages)
22 | - **Kat Marchaán** *kzm@sykosomatic.org* (23 packages)
23 | - **Blake Embrey** *hello@blakeembrey.com* (22 packages)
24 | - **TJ Holowaychuk** *tj@vision-media.ca* (21 packages)
25 | - **doowb** *brian.woodward@gmail.com* (21 packages)
26 | - **Tobias Koppers @sokra** (21 packages)
27 | - **Bogdan Chadkin** *trysound@yandex.ru* (20 packages)
28 | - **Jonathan Ong** *jonathanrichardong@gmail.com* (18 packages)
29 | - **Kit Cambridge** *github@kitcambridge.be* (17 packages)
30 | - **Benjamin Tan** *demoneaux@gmail.com* (16 packages)
31 | - **Dominic Tarr** *dominic.tarr@gmail.com* (14 packages)
32 | - **Elan Shanker** *elan.shanker+npm@gmail.com* (13 packages)
33 | - **Benjamin Coe** *ben@npmjs.com* (11 packages)
34 | - **Roman Shtylman** *shtylman@gmail.com* (11 packages)
35 | - **Nathan Rajlich** *nathan@tootallnate.net* (10 packages)
36 | - **Felix Böhm** *me@feedic.com* (10 packages)
37 | - **Mathias Buus** *mathiasbuus@gmail.com* (9 packages)
38 | - **Rod Vagg** *r@va.gg* (9 packages)
39 | - **Mariusz Nowak** *medyk@medikoo.com* (8 packages)
40 | - **Mikeal Rogers** *mikeal.rogers@gmail.com* (8 packages)
41 | - **Shinnosuke Watanabe** *snnskwtnb@gmail.com* (8 packages)
42 | - **Charlie Robbins** *charlie.robbins@gmail.com* (8 packages)
43 | - **moox** *m@moox.io* (8 packages)
44 | - **Vsevolod Strukchinsky** *floatdrop@gmail.com* (8 packages)
45 | - **Max Ogden** *max@maxogden.com* (7 packages)
46 | - **Forbes Lindesay** *forbes@lindesay.co.uk* (7 packages)
47 | - **Linus Unnebäck** *linus@folkdatorn.se* (7 packages)
48 | - **Joshua Appelman** *jappelman@xebia.com* (7 packages)
49 | - **nzakas** *nicholas@nczconsulting.com* (7 packages)
50 | - **Julian Gruber** *julian@juliangruber.com* (7 packages)
51 | - **zcbenz** *zcbenz@gmail.com* (7 packages)
52 | - **geelen** *hi@glenmaddern.com* (6 packages)
53 | - **Jeremiah Senkpiel** *fishrock123@rocketmail.com* (6 packages)
54 | - **qix** *i.am.qix@gmail.com* (6 packages)
55 | - **kevinsawicki** *kevinsawicki@gmail.com* (6 packages)
56 | - **James Nylen** *jnylen@gmail.com* (6 packages)
57 | - **Andrew Goode** *andrewbgoode@gmail.com* (6 packages)
58 | - **Zeke Sikelianos** *zeke@sikelianos.com* (6 packages)
59 | - **Arnout Kazemier** *opensource@3rd-eden.com* (6 packages)
60 | - **develar** *develar@gmail.com* (6 packages)
61 | - **yyx990803** *yyx990803@gmail.com* (6 packages)
62 | - **Eran Hammer** *eran@hammer.io* (6 packages)
63 | - **Paul Miller** *paul+gh@paulmillr.com* (5 packages)
64 | - **Yusuke SUZUKI** *utatane.tea@gmail.com* (5 packages)
65 | - **Felix Geisendörfer** *felix@debuggable.com* (5 packages)
66 | - **Kevin Mårtensson** *kevinmartensson@gmail.com* (5 packages)
67 | - **Simon Boudrias** *admin@simonboudrias.com* (5 packages)
68 | - **Jake Verbaten** *raynos2@gmail.com* (5 packages)
69 | - **Thorsten Lorenz** *thlorenz@gmx.de* (5 packages)
70 | - **Ben Newman** *bn@cs.stanford.edu* (5 packages)
71 | - **Michael Ficarra** *github.public.email@michael.ficarra.me* (5 packages)
72 | - **markdalgleish** *mark.john.dalgleish@gmail.com* (5 packages)
73 | - **Calvin Metcalf** *calvin.metcalf@gmail.com* (5 packages)
74 | - **Simeon Velichkov** *simeonvelichkov@gmail.com* (5 packages)
75 | - **Jarrett Cruger** *jcrugzz@gmail.com* (5 packages)
76 | - **Chris Talkington** (5 packages)
77 | - **AJ ONeal** *awesome@coolaj86.com* (4 packages)
78 | - **Tim Oxley** *secoif@gmail.com* (4 packages)
79 | - **arekinath** *alex@cooperi.net* (4 packages)
80 | - **jlord** *to.jlord@gmail.com* (4 packages)
81 | - **George Zahariev** *z@georgezahariev.com* (4 packages)
82 | - **Glen Maddern** (4 packages)
83 | - **Kyle E. Mitchell** *kyle@kemitchell.com* (4 packages)
84 | - **JP Richardson** *jprichardson@gmail.com* (4 packages)
85 | - **Domenic Denicola** *domenic@domenicdenicola.com* (4 packages)
86 | - **ctalkington** *chris@christalkington.com* (4 packages)
87 | - **Hugh Kennedy** *hughskennedy@gmail.com* (4 packages)
88 | - **Evan You** (4 packages)
89 | - **Maxime Thirouin** (4 packages)
90 | - **Andrew Kelley** *superjoe30@gmail.com* (4 packages)
91 | - **Feross Aboukhadijeh** *feross@feross.org* (4 packages)
92 | - **Aria Minaei** (4 packages)
93 | - **ariaminaei** *aria.minaei@gmail.com* (4 packages)
94 | - **Robert Kieffer** *robert@broofa.com* (4 packages)
95 | - **Vitaly Puzrin** *vitaly@rcdesign.ru* (3 packages)
96 | - **Andres Suarez** *zertosh@gmail.com* (3 packages)
97 | - **Roy Riojas** (3 packages)
98 | - **royriojas** *royriojas@gmail.com* (3 packages)
99 | - **Irakli Gozalishvili** *rfobic@gmail.com* (3 packages)
100 | - **Jordan Harband** *ljharb@gmail.com* (3 packages)
101 | - **dap** *dap@cs.brown.edu* (3 packages)
102 | - **James Coglan** *jcoglan@gmail.com* (3 packages)
103 | - **dominicbarnes** *dominic@dbarnes.info* (3 packages)
104 | - **pfmooney** *patrick.f.mooney@gmail.com* (3 packages)
105 | - **** (3 packages)
106 | - **James Talmage** *james@talmage.io* (3 packages)
107 | - **Matthew Mueller** *matt@lapwinglabs.com* (3 packages)
108 | - **André Cruz** *amdfcruz@gmail.com* (3 packages)
109 | - **freeall** *freeall@gmail.com* (3 packages)
110 | - **Brian White** *mscdex@mscdex.net* (3 packages)
111 | - **dfcreative** *df.creative@gmail.com* (3 packages)
112 | - **Heather Arthur** *fayearthur@gmail.com* (3 packages)
113 | - **Mark Cavage** *mcavage@gmail.com* (3 packages)
114 | - **Robert Kowalski** *rok@kowalski.gd* (3 packages)
115 | - **Arthur Verschaeve** *arthur.versch@gmail.com* (3 packages)
116 | - **Andrey Sitnik** *andrey@sitnik.ru* (3 packages)
117 | - **unshift** *npm@unshift.io* (3 packages)
118 | - **Nodejitsu Inc.** *info@nodejitsu.com* (3 packages)
119 | - **Wyatt Preul** *wpreul@gmail.com* (2 packages)
120 | - **gabelevi** *gabelevi@gmail.com* (2 packages)
121 | - **Ariya Hidayat** *ariya.hidayat@gmail.com* (2 packages)
122 | - **Kris Kowal** *kris@cixar.com* (2 packages)
123 | - **Jason Smith** *jhs@iriscouch.com* (2 packages)
124 | - **Nathan LaFreniere** *quitlahok@gmail.com* (2 packages)
125 | - **Michael Hart** *michael.hart.au@gmail.com* (2 packages)
126 | - **alexindigo** *iam@alexindigo.com* (2 packages)
127 | - **dscape** *nunojobpinto@gmail.com* (2 packages)
128 | - **thejoshwolfe** *thejoshwolfe@gmail.com* (2 packages)
129 | - **"Cowboy" Ben Alman** (2 packages)
130 | - **Fred K. Schott** *fkschott@gmail.com* (2 packages)
131 | - **Federico Romero** *hi@federomero.uy* (2 packages)
132 | - **celer** *dtyree77@gmail.com* (2 packages)
133 | - **maxbrunsfeld** *maxbrunsfeld@gmail.com* (2 packages)
134 | - **bnoordhuis** *info@bnoordhuis.nl* (2 packages)
135 | - **apechimp** *apeherder@gmail.com* (2 packages)
136 | - **Addy Osmani** *addyosmani@gmail.com* (2 packages)
137 | - **David Chambers** *dc@davidchambers.me* (2 packages)
138 | - **Pascal Hartig** *passy@twitter.com* (2 packages)
139 | - **Joyent, Inc** (2 packages)
140 | - **Kevin Sawicki** (2 packages)
141 | - **Mikola Lysenko** (2 packages)
142 | - **mikolalysenko** *mikolalysenko@gmail.com* (2 packages)
143 | - **Ben Alman** *cowboy@rj3.net* (2 packages)
144 | - **Guillermo Rauch** *rauchg@gmail.com* (2 packages)
145 | - **Kyle Robinson Young** *kyle@dontkry.com* (2 packages)
146 | - **Eddie Monge** *eddie+npm@eddiemonge.com* (2 packages)
147 | - **Sam Mikes** *smikes@cubane.com* (2 packages)
148 | - **blakeembrey** *me@blakeembrey.com* (2 packages)
149 | - **Julian Fleischer** (2 packages)
150 | - **scravy** *julian@scravy.de* (2 packages)
151 | - **anthonyshort** *antshort@gmail.com* (2 packages)
152 | - **clintwood** *clint@anotherway.co.za* (2 packages)
153 | - **ianstormtaylor** *ian@ianstormtaylor.com* (2 packages)
154 | - **queckezz** *fabian.eichenberger@gmail.com* (2 packages)
155 | - **stephenmathieson** *me@stephenmathieson.com* (2 packages)
156 | - **thehydroimpulse** *dnfagnan@gmail.com* (2 packages)
157 | - **timaschew** *timaschew@gmail.com* (2 packages)
158 | - **Roman Dvornov** *rdvornov@gmail.com* (2 packages)
159 | - **trevorgerhardt** *trevorgerhardt@gmail.com* (2 packages)
160 | - **Amir Abu Shareb** *yields@icloud.com* (2 packages)
161 | - **michael mifsud** *xzyfer@gmail.com* (2 packages)
162 | - **IndigoUnited** *hello@indigounited.com* (2 packages)
163 | - **Zhiye Li** *github@zhiye.li* (2 packages)
164 | - **rreverser** *me@rreverser.com* (2 packages)
165 | - **benogle** *ogle.ben@gmail.com* (2 packages)
166 | - **Nicholas C. Zakas** *nicholas+npm@nczconsulting.com* (2 packages)
167 | - **atom** *nathan@github.com* (2 packages)
168 | - **Fedor Indutny** *fedor@indutny.com* (2 packages)
169 | - **Parsha Pourkhomami** (2 packages)
170 | - **parshap** *supster+npm@gmail.com* (2 packages)
171 | - **peerigon** *developers@peerigon.com* (2 packages)
172 | - **DC** *threedeecee@gmail.com* (2 packages)
173 | - **brycekahle** *bkahle@gmail.com* (2 packages)
174 | - **Tim** *tim@fostle.com* (2 packages)
175 | - **Carl Xiong** *xiongc05@gmail.com* (2 packages)
176 | - **parshap** *parshap+npm@gmail.com* (2 packages)
177 | - **jhnns** *mail@johannesewald.de* (2 packages)
178 | - **evanw** *evan.exe@gmail.com* (1 package)
179 | - **julien-f** *julien.fontanet@isonoe.net* (1 package)
180 | - **fritx** *uxfritz@163.com* (1 package)
181 | - **Alexander Early** *alexander.early@gmail.com* (1 package)
182 | - **sethlu** (1 package)
183 | - **Matt DesLauriers** *dave.des@gmail.com* (1 package)
184 | - **Paul Betts** *paul@paulbetts.org* (1 package)
185 | - **Beau Gunderson** *beau@beaugunderson.com* (1 package)
186 | - **Caolan McMahon** *caolan.mcmahon@gmail.com* (1 package)
187 | - **cstruct** *albin@mattsson.io* (1 package)
188 | - **James Burke** *jrburke@gmail.com* (1 package)
189 | - **Alex Ford** *alex.ford@codetunnel.com* (1 package)
190 | - **Kiko Beats** *josefrancisco.verdu@gmail.com* (1 package)
191 | - **Alexis Deveria** *adeveria@gmail.com* (1 package)
192 | - **Square, Inc.** (1 package)
193 | - **eventualbuddha** *me@brian-donovan.com* (1 package)
194 | - **Petka Antonov** *petka.antonov@gmail.com* (1 package)
195 | - **ivolodin** *ivolodin@gmail.com* (1 package)
196 | - **byk** *ben@byk.im* (1 package)
197 | - **lo1tuma** *schreck.mathias@gmail.com* (1 package)
198 | - **Luis Couto** *hello@luiscouto.pt* (1 package)
199 | - **couto** *couto@15minuteslate.net* (1 package)
200 | - **Michael Mclaughlin** *M8ch88l@gmail.com* (1 package)
201 | - **benoitz** *bzugmeyer@gmail.com* (1 package)
202 | - **jden** *jason@denizac.org* (1 package)
203 | - **xjamundx** *jamund@gmail.com* (1 package)
204 | - **Adam Bretz** *arbretz@gmail.com* (1 package)
205 | - **marijn** *marijnh@gmail.com* (1 package)
206 | - **lpinca** *luigipinca@gmail.com* (1 package)
207 | - **Jakub Pawlowicz** *contact@jakubpawlowicz.com* (1 package)
208 | - **Aslak Hellesøy** *aslak.hellesoy@gmail.com* (1 package)
209 | - **hacksparrow** *captain@hacksparrow.com* (1 package)
210 | - **jasnell** *jasnell@gmail.com* (1 package)
211 | - **Ben Ripkens** *bripkens.dev@gmail.com* (1 package)
212 | - **null** *jakub@goalsmashers.com* (1 package)
213 | - **Stefan Thomas** *justmoon@members.fsf.org* (1 package)
214 | - **Ruben Bridgewater** *ruben@bridgewater.de* (1 package)
215 | - **Alexis Sellier** *github@cloudhead.io* (1 package)
216 | - **Ramesh Nair** *ram@hiddentao.com* (1 package)
217 | - **Randall Koutnik** *@rkoutnik* (1 package)
218 | - **natevw** *natevw@yahoo.com* (1 package)
219 | - **zloirock** *zloirock@zloirock.ru* (1 package)
220 | - **Joshua Holbrook** *josh.holbrook@gmail.com* (1 package)
221 | - **jhs** *jhs@couchone.com* (1 package)
222 | - **Kent C. Dodds** *kent@doddsfamily.us* (1 package)
223 | - **https://github.com/nearinfinity/node-bplist-parser.git** (1 package)
224 | - **thethomaseffect** *thethomaseffect@gmail.com* (1 package)
225 | - **idralyuk** *igor@buran.us* (1 package)
226 | - **ceejbot** *ceejceej@gmail.com* (1 package)
227 | - **Dave Eddy** *dave@daveeddy.com* (1 package)
228 | - **Philipp Dunkel** *pip@pipobscure.com* (1 package)
229 | - **bajtos** *miro.bajtos@gmail.com* (1 package)
230 | - **Nick Fitzgerald** *nfitzgerald@mozilla.com* (1 package)
231 | - **Strongloop** *callback@strongloop.com* (1 package)
232 | - **mozilla-devtools** *mozilla-developer-tools@googlegroups.com* (1 package)
233 | - **dylanpiercey** *pierceydylan@gmail.com* (1 package)
234 | - **mozilla** *dherman@mozilla.com* (1 package)
235 | - **ctalkington** *chris@talkingtontech.com* (1 package)
236 | - **Alex Wilson** *alex.wilson@joyent.com* (1 package)
237 | - **nickfitzgerald** *fitzgen@gmail.com* (1 package)
238 | - **Ahmad Nassri** *ahmad@ahmadnassri.com* (1 package)
239 | - **Sergey Kryzhanovsky** *skryzhanovsky@ya.ru* (1 package)
240 | - **afelix** *skryzhanovsky@gmail.com* (1 package)
241 | - **watson** *w@tson.dk* (1 package)
242 | - **yoshuawuyts** *i@yoshuawuyts.com* (1 package)
243 | - **tadatuta** *i@tadatuta.com* (1 package)
244 | - **Robert Mustacchi** *rm@fingolfin.org* (1 package)
245 | - **Pierre Curto** (1 package)
246 | - **Michele Bini, Ron Garret, Guy K. Kloss** (1 package)
247 | - **Tom Wu** (1 package)
248 | - **andyperlitch** *andyperlitch@gmail.com* (1 package)
249 | - **Kris Zyp** (1 package)
250 | - **kriszyp** *kriszyp@gmail.com* (1 package)
251 | - **moll** *andri@dot.ee* (1 package)
252 | - **Jan Lehnardt** *jan@apache.org* (1 package)
253 | - **marcbachmann** *marc.brookman@gmail.com* (1 package)
254 | - **pierrec** *pierre.curto@gmail.com* (1 package)
255 | - **Dane Springmeyer** *dane@mapbox.com* (1 package)
256 | - **springmeyer** *dane@dbsgeo.com* (1 package)
257 | - **bergwerkgis** *wb@bergwerk-gis.at* (1 package)
258 | - **mikemorris** *michael.patrick.morris@gmail.com* (1 package)
259 | - **kkaefer** *kkaefer@gmail.com* (1 package)
260 | - **yhahn** *young@developmentseed.org* (1 package)
261 | - **Ben Alpert** *ben@benalpert.com* (1 package)
262 | - **Ryan Day** *soldair@gmail.com* (1 package)
263 | - **Jeremy Stashewsky** *jstashewsky@salesforce.com* (1 package)
264 | - **kossnocorp** *kossnocorp@gmail.com* (1 package)
265 | - **goinstant** *services@goinstant.com* (1 package)
266 | - **TweetNaCl-js contributors** (1 package)
267 | - **dchest** *dmitry@codingrobots.com* (1 package)
268 | - **Ilya Radchenko** *ilya@burstcreations.com* (1 package)
269 | - **joshperry** *josh@6bit.com* (1 package)
270 | - **Stefan Penner** *stefan.penner@gmail.com* (1 package)
271 | - **schnittstabil** *michael@schnittstabil.de* (1 package)
272 | - **ult_combo** *ultcombo@gmail.com* (1 package)
273 | - **jridgewell** *justin+npm@ridgewell.name* (1 package)
274 | - **Aaron Heckmann** *aaron.heckmann+github@gmail.com* (1 package)
275 | - **bevacqua** *nicolasbevacqua@gmail.com* (1 package)
276 | - **Ivan Sagalaev** *maniac@softwaremaniacs.org* (1 package)
277 | - **Greg Allen** *hi@firstandthird.com* (1 package)
278 | - **Lloyd Brookes** *75pound@gmail.com* (1 package)
279 | - **Steve Mao** *maochenyan@gmail.com* (1 package)
280 | - **stevemao** *steve.mao@healthinteract.com.au* (1 package)
281 | - **Juriy "kangax" Zaytsev** (1 package)
282 | - **alexlamsl** *alex+npm@starthq.com* (1 package)
283 | - **kangax** *kangax@gmail.com* (1 package)
284 | - **Charles Blaxland** *charles.blaxland@gmail.com* (1 package)
285 | - **jantimon** *j.nicklas@me.com* (1 package)
286 | - **egeste** *npm@egeste.net* (1 package)
287 | - **Daniel Aristizabal** *aristizabal.daniel@gmail.com* (1 package)
288 | - **ヨーント** *me@yawnt.com* (1 package)
289 | - **HubSpotDev** *devteam@hubspot.com* (1 package)
290 | - **hijonathan** *me@jonathan-kim.com* (1 package)
291 | - **bash** *ashbryanct@gmail.com* (1 package)
292 | - **Pavan Kumar Sunkara** *pavan.sss1991@gmail.com* (1 package)
293 | - **joeferner** *joe@fernsroth.com* (1 package)
294 | - **kael** (1 package)
295 | - **netroy** *aditya@netroy.in* (1 package)
296 | - **Jens Taylor** *jensyt@gmail.com* (1 package)
297 | - **Tyler Kellen** *tyler@sleekcode.net* (1 package)
298 | - **cpojer** *christoph.pojer@gmail.com* (1 package)
299 | - **whitequark** *whitequark@whitequark.org* (1 package)
300 | - **Qix** (1 package)
301 | - **wayfind** (1 package)
302 | - **gjtorikian** *gjtorikian@gmail.com* (1 package)
303 | - **Alex Kocharin** *alex@kocharin.ru* (1 package)
304 | - **Dan Kogai** (1 package)
305 | - **dankogai** *dankogai+github@gmail.com* (1 package)
306 | - **Simon Lydell** (1 package)
307 | - **lydell** *simon.lydell@gmail.com* (1 package)
308 | - **Vladimir Zapparov** *dervus.grim@gmail.com* (1 package)
309 | - **Paul Vorbach** *paul@vorba.ch* (1 package)
310 | - **Aseem Kishore** *aseem.kishore@gmail.com* (1 package)
311 | - **Douglas Crockford** (1 package)
312 | - **Tim Caswell** *tim@creationix.com* (1 package)
313 | - **Jonas Pommerening** *jonas.pommerening@gmail.com* (1 package)
314 | - **Sergey Berezhnoy** *veged@ya.ru* (1 package)
315 | - **jordanbtucker** *jordanbtucker@gmail.com* (1 package)
316 | - **Trent Mick** *trentm@gmail.com* (1 package)
317 | - **veged** *veged@mail.ru* (1 package)
318 | - **arikon** *peimei@ya.ru* (1 package)
319 | - **Steven Levithan** *steves_list@hotmail.com* (1 package)
320 | - **Bower** (1 package)
321 | - **Mat Scales** *mat@wibbly.org.uk* (1 package)
322 | - **Paul Irish** *paul.irish@gmail.com* (1 package)
323 | - **Adam Stankiewicz** *sheerun@sher.pl* (1 package)
324 | - **David DeSandro** *desandrocodes@gmail.com* (1 package)
325 | - **Viacheslav Lotsmanov** *lotsmanov89@gmail.com* (1 package)
326 | - **T. Jameson Little** *t.jameson.little@gmail.com* (1 package)
327 | - **Funerr** *thefunerr@gmail.com* (1 package)
328 | - **Jonathan Rajavuori** *jrajav@gmail.com* (1 package)
329 | - **Benjamin Byholm** *bbyholm@abo.fi* (1 package)
330 | - **Apache CouchDB** *dev@couchdb.apache.org* (1 package)
331 | - **jhs** *jason.h.smith@gmail.com* (1 package)
332 | - **jo** *schmidt@netzmerk.com* (1 package)
333 | - **pgte** *pedro.teixeira@gmail.com* (1 package)
334 | - **Charlie McConnell** *charlie@charlieistheman.com* (1 package)
335 | - **Maciej Małecki** *me@mmalecki.com* (1 package)
336 | - **Matt Lavin** *matt.lavin@gmail.com* (1 package)
337 | - **Andrew Nesbitt** *andrewnez@gmail.com* (1 package)
338 | - **Adeel** *adeelbm@outlook.com* (1 package)
339 | - **Dean Mao** *deanmao@gmail.com* (1 package)
340 | - **Keith Cirkel** *npm@keithcirkel.co.uk* (1 package)
341 | - **Laurent Goderre** *laurent.goderre@gmail.com* (1 package)
342 | - **Marcin Cieślak** *npm@saper.info* (1 package)
343 | - **DY** *dfcreative@gmail.com* (1 package)
344 | - **Meryn Stol** *merynstol@gmail.com* (1 package)
345 | - **Elijah Insua** *tmpvar@gmail.com* (1 package)
346 | - **Sam Roberts** *sam@strongloop.com* (1 package)
347 | - **Nadav Ivgi** (1 package)
348 | - **nadav** *npm@shesek.info* (1 package)
349 | - **trevorburnham** *trevorburnham@gmail.com* (1 package)
350 | - **kat** *kat@lua.cz* (1 package)
351 | - **Devon Govett** *devongovett@gmail.com* (1 package)
352 | - **Tim Koschützki** *tim@debuggable.com* (1 package)
353 | - **Aria Stewart** *aredridel@dinhe.net* (1 package)
354 | - **Graeme Yeates** *yeatesgraeme@gmail.com* (1 package)
355 | - **kenan** *kenan@kenany.me* (1 package)
356 | - **thefourtheye** *thechargingvolcano@gmail.com* (1 package)
357 | - **Marak Squires** (1 package)
358 | - **The Linux Foundation** (1 package)
359 | - **segment** *tj@segment.io* (1 package)
360 | - **dsc** *dsc@less.ly* (1 package)
361 | - **probablycorey** *probablycorey@gmail.com* (1 package)
362 | - **null** *support@marak.com* (1 package)
363 | - **grncdr** *glurgle@gmail.com* (1 package)
364 | - **yisi** *yiorsi@gmail.com* (1 package)
365 | - **Brian J. Brennan** *brianloveswords@gmail.com* (1 package)
366 | - **Drew Young** (1 package)
367 | - **drewyoung1** *drewyoung1@gmail.com* (1 package)
368 | - **Ivan Nikulin** *ifaaan@gmail.com* (1 package)
369 | - **retrofox** *rdsuarez@gmail.com* (1 package)
370 | - **coreh** *thecoreh@gmail.com* (1 package)
371 | - **kelonye** *kelonyemitchel@gmail.com* (1 package)
372 | - **cristiandouce** *cristian@gravityonmars.com* (1 package)
373 | - **swatinem** *arpad.borsos@googlemail.com* (1 package)
374 | - **stagas** *gstagas@gmail.com* (1 package)
375 | - **calvinfo** *calvin@calv.info* (1 package)
376 | - **nami-doc** *vendethiel@hotmail.fr* (1 package)
377 | - **Daniel Cousens** (1 package)
378 | - **dcousens** *email@dcousens.com* (1 package)
379 | - **mreinstein** *reinstein.mike@gmail.com* (1 package)
380 | - **Justineo** *justice360@gmail.com* (1 package)
381 | - **Mark Dalgleish** (1 package)
382 | - **alexgorbatchev** *alex.gorbatchev@gmail.com* (1 package)
383 | - **Christoffer Hallas** *christoffer.hallas@gmail.com* (1 package)
384 | - **Jordan Scales** *scalesjordan@gmail.com* (1 package)
385 | - **Nathan Zadoks** *nathan@nathan7.eu* (1 package)
386 | - **2013+ Bevry Pty Ltd** *us@bevry.me* (1 package)
387 | - **balupton** *b@lupton.cc* (1 package)
388 | - **reconbot** *wizard@roborooter.com* (1 package)
389 | - **spaintrain** *mc.s.pain.how.er+npm@gmail.com* (1 package)
390 | - **Jeff Morrison** *jeff@anafx.com* (1 package)
391 | - **Paul O’Shannessy** *paul@oshannessy.com* (1 package)
392 | - **MoOx** (1 package)
393 | - **'Julian Viereck'** *julian.viereck@gmail.com* (1 package)
394 | - **Steven Vachon** *contact@svachon.com* (1 package)
395 | - **thegoleffect** *thegoleffect@gmail.com* (1 package)
396 | - **Troy Goode** *troygoode@gmail.com* (1 package)
397 | - **julianduque** *julianduquej@gmail.com* (1 package)
398 | - **vbuterin** *vbuterin@gmail.com* (1 package)
399 | - **midnightlightning** *boydb@midnightdesign.ws* (1 package)
400 | - **Cloud Programmability Team** (1 package)
401 | - **mattpodwysocki** *matthew.podwysocki@gmail.com* (1 package)
402 | - **evansolomon** *evan@evanalyze.com* (1 package)
403 | - **Conrad Pankoff** *deoxxa@fknsrs.biz* (1 package)
404 | - **lox** *lachlan@ljd.cc* (1 package)
405 | - **J. Tangelder** (1 package)
406 | - **jtangelder** *j.tangelder@gmail.com* (1 package)
407 | - **akiran** *kiran.coder0@gmail.com* (1 package)
408 | - **celer** *celer@scrypt.net* (1 package)
409 | - **Wes Todd** (1 package)
410 | - **wesleytodd** *wes@wesleytodd.com* (1 package)
411 | - **Artur Adib** *arturadib@gmail.com* (1 package)
412 | - **ariporad** *ari@ariporad.com* (1 package)
413 | - **nfischer** *ntfschr@gmail.com* (1 package)
414 | - **Jeremie Miller** *jeremie@jabber.org* (1 package)
415 | - **Marek Majkowski** (1 package)
416 | - **majek** *majek04@gmail.com* (1 package)
417 | - **msackman** *matthew@rabbitmq.com* (1 package)
418 | - **squaremo** *mikeb@squaremobius.net* (1 package)
419 | - **glasser** *glasser@meteor.com* (1 package)
420 | - **rynomad** *nomad.ry@gmail.com* (1 package)
421 | - **Bryce Kahle** (1 package)
422 | - **Alexandru Marasteanu** *hello@alexei.ro* (1 package)
423 | - **Stefan Judis** (1 package)
424 | - **sebastianhoitz** *hoitz@komola.de* (1 package)
425 | - **Sam Day** *me@samcday.com.au* (1 package)
426 | - **samcday** *sam.c.day@gmail.com* (1 package)
427 | - **Kir Belevich** *kir@soulshine.in* (1 package)
428 | - **greli** *grelimail@gmail.com* (1 package)
429 | - **Gajus Kuizinas** *gajus@gajus.com* (1 package)
430 | - **gajus** *gk@anuary.com* (1 package)
431 | - **Bryce Baril** *bryce@ravenwall.com* (1 package)
432 | - **J. Ryan Stinnett** *jryans@gmail.com* (1 package)
433 | - **KARASZI István** *github@spam.raszi.hu* (1 package)
434 | - **raszi** *npm@spam.raszi.hu* (1 package)
435 | - **Marcel Klehr** *mklehr@gmx.net* (1 package)
436 | - **stefanjudis** *stefanjudis@gmail.com* (1 package)
437 | - **brianloveswords** *brian@nyhacker.org* (1 package)
438 | - **Henrik Joreteg** *henrik@andyet.net* (1 package)
439 | - **Geraint Luff** (1 package)
440 | - **geraintluff** *luffgd@gmail.com* (1 package)
441 | - **bartvds** *bartvanderschoor@gmail.com* (1 package)
442 | - **Microsoft Corp.** (1 package)
443 | - **typescript** *typescript@microsoft.com* (1 package)
444 | - **Mihai Bazon** *mihai.bazon@gmail.com* (1 package)
445 | - **rvanvelzen1** *rvanvelzen1@gmail.com* (1 package)
446 | - **Jeremy Ashkenas** *jashkenas@gmail.com* (1 package)
447 | - **Halász Ádám** *mail@adamhalasz.com* (1 package)
448 | - **Felix Gnass** *fgnass@gmail.com* (1 package)
449 | - **Bjarke Walling** *bwp@bwp.dk* (1 package)
450 | - **Joyent** (1 package)
451 | - **Jared Hanson** *jaredhanson@gmail.com* (1 package)
452 | - **Vincent Voyer** *vincent.voyer@gmail.com* (1 package)
453 | - **Titus Wormer** *tituswormer@gmail.com* (1 package)
454 | - **Sjoerd Visscher** (1 package)
455 | - **Benjamin Thomas** *ben@bentomas.com* (1 package)
456 | - **Dmitrii Karpich** *meettya@gmail.com* (1 package)
457 | - **Christopher Jeffrey (JJ)** *chjjeffrey@gmail.com* (1 package)
458 | - **Alberto Pose** *albertopose@gmail.com* (1 package)
459 | - **Ozgur Ozcitak** *oozcitak@gmail.com* (1 package)
460 | - **jindw** *jindw@xidea.org* (1 package)
461 | - **yaron** *yaronn01@gmail.com* (1 package)
462 | - **bigeasy** *alan@prettyrobots.com* (1 package)
463 | - **kethinov** *kethinov@gmail.com* (1 package)
464 | - **jinjinyun** *jinyun.jin@gmail.com* (1 package)
465 | - **jstash** *jeremy@goinstant.com* (1 package)
466 |
467 |
--------------------------------------------------------------------------------
/app/dist/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/app/dist/.gitkeep
--------------------------------------------------------------------------------
/app/electron.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { app, ipcMain, BrowserWindow } = require( 'electron' );
4 | const path = require( 'path' );
5 | const ElectronSettings = require( 'electron-settings' );
6 | const windowStateKeeper = require( 'electron-window-state' );
7 | const uuid = require( 'uuid' );
8 | const GitHubApi = require( 'github' );
9 | const menu = require( './main/menu' );
10 | const Session = require( './main/session' );
11 | const repoUtils = require( './main/repoUtils' );
12 | const createRPC = require( './main/rpc' );
13 | const github = new GitHubApi( {
14 | protocol : 'https',
15 | headers : {
16 | 'user-agent' : 'Forrest - npm desktop client'
17 | },
18 | timeout : 5000
19 | } );
20 |
21 |
22 | // configuration for the available static windows
23 | const initialBgColor = '#f1f1f1';
24 | const staticWindows = {
25 | about : {
26 | config : {
27 | height : 700,
28 | width : 475,
29 | backgroundColor : initialBgColor,
30 | titleBarStyle : 'hidden',
31 | resizable : false
32 | },
33 | hash : '#!/about',
34 | initializedWindow : null
35 | },
36 | help : {
37 | config : {
38 | height : 400,
39 | width : 800,
40 | backgroundColor : initialBgColor,
41 | titleBarStyle : 'hidden',
42 | resizable : false
43 | },
44 | hash : '#!/help',
45 | initializedWindow : null
46 | },
47 | updateAvailable : {
48 | config : {
49 | height : 300,
50 | width : 300,
51 | backgroundColor : initialBgColor,
52 | titleBarStyle : 'hidden',
53 | resizable : false
54 | },
55 | hash : '#!/update-available',
56 | initializedWindow : null
57 | }
58 | };
59 |
60 | const windowSet = new Set( [] );
61 | const rpcSet = new Set( [] );
62 |
63 | let config = {};
64 | let baseUrl = {
65 | development : '',
66 | production : `file://${ __dirname }/dist/index.html`
67 | };
68 |
69 | // enable experimental feature to use context menus
70 | app.commandLine.appendSwitch( '--enable-experimental-web-platform-features' );
71 |
72 | if ( process.env.NODE_ENV === 'development' ) {
73 | config = require( '../config' ).config;
74 | baseUrl.development = `http://localhost:${ config.port }`;
75 |
76 | config.url = `${ baseUrl.development }`;
77 | } else {
78 | config.devtron = false;
79 | config.url = `${ baseUrl.production }`;
80 | }
81 |
82 | ipcMain.on( 'openNewWindow', createWindow );
83 |
84 | ipcMain.on( 'shellWrite', () => console.log( arguments ) );
85 |
86 | let settings = new ElectronSettings( {
87 | configFileName : 'npm-app'
88 | } );
89 |
90 |
91 | /**
92 | * Open a selected static window
93 | *
94 | * @param {String} type - name of the selected window type
95 | */
96 | function openStaticWindow( type ) {
97 | if ( ! staticWindows[ type ].initializedWindow ) {
98 | staticWindows[ type ].initializedWindow = new BrowserWindow(
99 | staticWindows[ type ].config
100 | );
101 |
102 | staticWindows[ type ].initializedWindow.loadURL(
103 | `${ config.url }` + staticWindows[ type ].hash
104 | );
105 |
106 | staticWindows[ type ].initializedWindow.on(
107 | 'closed', () => staticWindows[ type ].initializedWindow = null
108 | );
109 | } else {
110 | staticWindows[ type ].initializedWindow.focus();
111 | }
112 | }
113 |
114 | /**
115 | *
116 | */
117 | function createWindow( event, hash ) {
118 | let mainWindowState = windowStateKeeper( {
119 | defaultWidth : 300,
120 | defaultHeight : 500
121 | } );
122 |
123 | /**
124 | * Initial window options
125 | */
126 | let newWindow = new BrowserWindow( {
127 | height : mainWindowState.height,
128 | width : mainWindowState.width,
129 | x : mainWindowState.x,
130 | y : mainWindowState.y,
131 | backgroundColor : initialBgColor,
132 | alwaysOnTop : settings.get( 'app.alwaysOnTop' ),
133 | minWidth : 250,
134 | titleBarStyle : 'hidden',
135 | 'web-preferences' : {
136 | plugins : true
137 | }
138 | } );
139 |
140 | mainWindowState.manage( newWindow );
141 |
142 | let url = hash ? `${ config.url }${ hash }` : config.url;
143 |
144 | newWindow.loadURL( url );
145 |
146 | windowSet.add( newWindow );
147 |
148 | if ( process.env.NODE_ENV === 'development' ) {
149 | newWindow.webContents.openDevTools( { mode : 'undocked' } );
150 | }
151 |
152 | if ( config.devtron ) {
153 | BrowserWindow.addDevToolsExtension( path.join( __dirname, '../node_modules/devtron' ) );
154 | } else {
155 | BrowserWindow.removeDevToolsExtension( 'devtron' );
156 | }
157 |
158 | const rpc = createRPC( newWindow );
159 | const sessions = new Map();
160 |
161 | rpcSet.add( rpc );
162 |
163 | rpc.on( 'create session', () => {
164 | initSession( { /* rows, cols, cwd, shell */ }, ( uid, session ) => {
165 | sessions.set( uid, session );
166 |
167 | console.log( 'sesstion created', uid );
168 | rpc.emit( 'session set', {
169 | uid,
170 | shell : session.shell,
171 | pid : session.pty.pid
172 | } );
173 |
174 | rpc.emit( 'settings loaded', settings.get( 'app' ) || {} );
175 | rpc.emit( 'repos loaded', settings.get( 'repos' ) || [] );
176 |
177 | session.on( 'data', ( data ) => {
178 | rpc.emit( 'session data', { uid, data } );
179 | } );
180 |
181 | session.on( 'exit', () => {
182 | rpc.emit( 'session exit', { uid } );
183 | sessions.delete( uid );
184 | } );
185 | } );
186 |
187 | rpc.on( 'data', ( { uid, data } ) => {
188 | sessions.get( uid ).write( data );
189 | } );
190 |
191 | rpc.on( 'update app settings', ( { name, setting } ) => {
192 | settings.set( `app.${ name }`, setting );
193 |
194 | // TODO save on loop here
195 | windowSet.forEach( ( window ) => {
196 | if ( name === 'alwaysOnTop' ) {
197 | window.setAlwaysOnTop( setting );
198 | }
199 | } );
200 |
201 | rpcSet.forEach( rpc => rpc.emit( 'setting set', { name, setting } ) );
202 | } );
203 |
204 | rpc.on( 'add repo', ( repoPath ) => {
205 | repoUtils.readRepoData( repoPath )
206 | .then( repo => {
207 | const savedRepos = settings.get( 'repos' ) || [];
208 | const repos = [ ...savedRepos, repo ];
209 |
210 | settings.set( 'repos', repos );
211 |
212 | emitAll( 'repos updated', repos );
213 | } )
214 | .catch( ( error ) => {
215 | /* eslint-disable no-console */
216 | console.log( error );
217 | } );
218 | } );
219 |
220 | rpc.on( 'update repo', ( repoPath ) => {
221 | Promise.all( settings.get( 'repos' ).map( ( repo ) => {
222 | if ( repo.path === repoPath ) {
223 | return repoUtils.readRepoData( repoPath );
224 | }
225 |
226 | return repo;
227 | } ) )
228 | .then( repos => {
229 | settings.set( 'repos', repos );
230 |
231 | emitAll( 'repos updated', repos );
232 | } )
233 | .catch( ( error ) => {
234 | /* eslint-disable no-console */
235 | console.log( error );
236 | } );
237 | } );
238 |
239 | rpc.on( 'remove repo', ( repoPath ) => {
240 | const repos = settings.get( 'repos' ).reduce( ( repos, storedRepo ) => {
241 | if ( storedRepo.path !== repoPath ) {
242 | repos.push( storedRepo );
243 | }
244 |
245 | return repos;
246 | }, [] );
247 |
248 | settings.set( 'repos', repos );
249 |
250 | emitAll( 'repos updated', repos );
251 | } );
252 |
253 | rpc.on( 'set terminal size', ( { uid, cols, rows } ) => {
254 | sessions.get( uid ).resize( { cols, rows } );
255 | } );
256 | } );
257 |
258 |
259 | const deleteSessions = () => {
260 | sessions.forEach( ( session, key ) => {
261 | rpc.removeAllListeners( 'data' );
262 | rpc.removeAllListeners( 'update app settings' );
263 | rpc.removeAllListeners( 'add repo' );
264 | rpc.removeAllListeners( 'update repo' );
265 | rpc.removeAllListeners( 'remove repo' );
266 | rpc.removeAllListeners( 'set terminal size' );
267 |
268 | session.removeAllListeners();
269 | session.destroy();
270 | sessions.delete( key );
271 | } );
272 | };
273 |
274 | newWindow.on( 'close', () => {
275 | windowSet.delete( newWindow );
276 | rpcSet.delete( rpc );
277 |
278 | rpc.destroy();
279 | deleteSessions();
280 | } );
281 |
282 | // we reset the rpc channel only upon
283 | // subsequent refreshes (ie: F5)
284 | newWindow.webContents.on( 'did-navigate', deleteSessions );
285 |
286 | if ( windowSet.size === 1 ) {
287 | menu.init( {
288 | createWindow,
289 | openStaticWindow
290 | } );
291 | }
292 |
293 | /* eslint-disable no-console */
294 | console.log( 'window opened' );
295 | }
296 |
297 | function emitAll() {
298 | rpcSet.forEach( rpc => rpc.emit.apply( rpc, arguments ) );
299 | }
300 |
301 | function initSession( opts, fn ) {
302 | fn( uuid.v4(), new Session( opts ) );
303 | }
304 |
305 | if ( process.env.NODE_ENV !== 'development' ) {
306 | github.repos.getReleases(
307 | { user : 'stefanjudis', repo : 'forrest' },
308 | ( error, releases ) => {
309 | if ( error ) {
310 | return;
311 | }
312 |
313 | if ( releases.length && releases[ 0 ].tag_name !== `v${ app.getVersion() }` ) {
314 | openStaticWindow( 'updateAvailable' );
315 | }
316 | }
317 | );
318 | }
319 |
320 | app.on( 'ready', createWindow );
321 |
322 | app.on( 'window-all-closed', () => {
323 | if ( process.platform !== 'darwin' ) {
324 | app.quit();
325 | }
326 | } );
327 |
328 | app.on( 'activate', () => {
329 | if ( ! windowSet.size ) {
330 | createWindow();
331 | }
332 | } );
333 |
--------------------------------------------------------------------------------
/app/main.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%= htmlWebpackPlugin.options.title %>
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/main/menu.js:
--------------------------------------------------------------------------------
1 | const electron = require( 'electron' );
2 | const shell = electron.shell;
3 | const Menu = electron.Menu;
4 |
5 | module.exports = {
6 | init( options ) {
7 | const template = [
8 | {
9 | label : 'Edit',
10 | submenu : [
11 | {
12 | role : 'undo'
13 | },
14 | {
15 | role : 'redo'
16 | },
17 | {
18 | type : 'separator'
19 | },
20 | {
21 | role : 'cut'
22 | },
23 | {
24 | role : 'copy'
25 | },
26 | {
27 | role : 'paste'
28 | },
29 | {
30 | role : 'delete'
31 | },
32 | {
33 | role : 'selectall'
34 | }
35 | ]
36 | },
37 | {
38 | label : 'View',
39 | submenu : [
40 | {
41 | label : 'Reload',
42 | accelerator : 'CmdOrCtrl+R',
43 | click( item, focusedWindow ) {
44 | if ( focusedWindow ) focusedWindow.reload();
45 | }
46 | },
47 | {
48 | role : 'togglefullscreen'
49 | },
50 | {
51 | label : 'Toggle Developer Tools',
52 | accelerator : process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
53 | click( item, focusedWindow ) {
54 | if ( focusedWindow ) {
55 | focusedWindow.webContents.toggleDevTools();
56 | }
57 | }
58 | }
59 | ]
60 | },
61 | {
62 | role : 'window',
63 | submenu : [
64 | {
65 | role : 'minimize'
66 | },
67 | {
68 | role : 'close'
69 | }
70 | ]
71 | },
72 | {
73 | role : 'help',
74 | submenu : [
75 | {
76 | label : 'Shortcuts',
77 | accelerator : 'CmdOrCtrl+/',
78 | click() { options.openStaticWindow( 'help' ); }
79 | },
80 | {
81 | label : 'Report an Issue',
82 | click() { shell.openExternal( 'https://github.com/stefanjudis/forrest/issues' ); }
83 | }
84 | ]
85 | }
86 | ];
87 |
88 | if ( process.platform === 'darwin' ) {
89 | const name = electron.app.getName();
90 | template.unshift( {
91 | label : name,
92 | submenu : [
93 | {
94 | label : 'About Forrest',
95 | click() { options.openStaticWindow( 'about' ); }
96 | },
97 | {
98 | type : 'separator'
99 | },
100 | {
101 | label : 'Open New Window',
102 | accelerator : 'CmdOrCtrl+N',
103 | click() { options.createWindow(); }
104 | },
105 | {
106 | type : 'separator'
107 | },
108 | {
109 | label : 'Quit',
110 | role : 'quit'
111 | }
112 | ]
113 | } );
114 | // Window menu.
115 | template[ 3 ].submenu = [
116 | {
117 | label : 'Close',
118 | accelerator : 'CmdOrCtrl+W',
119 | role : 'close'
120 | },
121 | {
122 | label : 'Minimize',
123 | accelerator : 'CmdOrCtrl+M',
124 | role : 'minimize'
125 | },
126 | {
127 | label : 'Zoom',
128 | role : 'zoom'
129 | }
130 | ];
131 | }
132 |
133 | const menu = Menu.buildFromTemplate( template );
134 |
135 | Menu.setApplicationMenu( menu );
136 | }
137 | };
138 |
--------------------------------------------------------------------------------
/app/main/repoUtils.js:
--------------------------------------------------------------------------------
1 | const fs = require( 'fs' );
2 |
3 | /**
4 | * Evaluate repo url from a read package data
5 | *
6 | * @param {Object} repoData
7 | *
8 | * @returns {null|Object}
9 | */
10 | function getRepoUrl( repoData ) {
11 | let { repository : repo } = repoData;
12 |
13 | if ( repo ) {
14 | if ( repo.url ) {
15 | return `https://${ repo.url.replace( /((git)?\+?(https)?:\/\/|\.git)/g, '' ) }`;
16 | }
17 |
18 | if ( /^.*?\/.*?$/.test( repo ) ) {
19 | return `https://github.com/${ repo }`;
20 | }
21 | }
22 |
23 | return null;
24 | }
25 |
26 |
27 | /**
28 | * Read repo data
29 | *
30 | * @param {String} repo path
31 | *
32 | * @returns {Promise}
33 | */
34 | function readRepoData( repoPath ) {
35 | return new Promise( ( resolve, reject ) => {
36 | fs.readFile( `${ repoPath }/package.json`, ( error, data ) => {
37 | if ( error ) {
38 | return reject( error );
39 | }
40 |
41 | try {
42 | var packageJSON = JSON.parse( data );
43 | } catch( error ) {
44 | return reject( error );
45 | }
46 |
47 | let repo = {
48 | path : repoPath,
49 | name : packageJSON.name,
50 | description : packageJSON.description,
51 | url : getRepoUrl( packageJSON )
52 | };
53 |
54 | resolve( repo );
55 | } );
56 | } );
57 | }
58 |
59 | module.exports = {
60 | readRepoData
61 | };
62 |
--------------------------------------------------------------------------------
/app/main/rpc.js:
--------------------------------------------------------------------------------
1 | const { EventEmitter } = require( 'events' );
2 | const { ipcMain } = require( 'electron' );
3 | const uuid = require( 'uuid' );
4 |
5 | class Server extends EventEmitter {
6 |
7 | constructor ( win ) {
8 | super();
9 | this.win = win;
10 | this.ipcListener = this.ipcListener.bind( this );
11 |
12 | if ( this.destroyed ) {
13 | return;
14 | }
15 |
16 | const uid = uuid.v4();
17 | this.id = uid;
18 |
19 | ipcMain.on( uid, this.ipcListener );
20 |
21 | // we intentionally subscribe to `on` instead of `once`
22 | // to support reloading the window and re-initializing
23 | // the channel
24 | this.wc.on( 'did-finish-load', () => {
25 | this.wc.send( 'init', uid );
26 | } );
27 | }
28 |
29 | get wc () {
30 | return this.win.webContents;
31 | }
32 |
33 | ipcListener ( event, { ev, data } ) {
34 | super.emit( ev, data );
35 | }
36 |
37 | emit ( ch, data ) {
38 | this.wc.send( this.id, { ch, data } );
39 | }
40 |
41 | destroy () {
42 | this.removeAllListeners();
43 | this.wc.removeAllListeners();
44 |
45 | if ( this.id ) {
46 | ipcMain.removeListener( this.id, this.ipcListener );
47 | } else {
48 | // mark for `genUid` in constructor
49 | this.destroyed = true;
50 | }
51 | }
52 |
53 | }
54 |
55 | module.exports = function createRPC ( win ) {
56 | return new Server( win );
57 | };
58 |
--------------------------------------------------------------------------------
/app/main/session.js:
--------------------------------------------------------------------------------
1 | const { app } = require( 'electron' );
2 | const { EventEmitter } = require( 'events' );
3 | const defaultShell = require( 'default-shell' );
4 |
5 | let spawn = require( 'child_pty' ).spawn;
6 |
7 | module.exports = class Session extends EventEmitter {
8 | constructor () {
9 | super();
10 |
11 | const baseEnv = Object.assign( {}, process.env, {
12 | LANG : app.getLocale().replace( '-', '_' ) + '.UTF-8',
13 | TERM : 'xterm-256color'
14 | } );
15 |
16 | this.pty = spawn( defaultShell, [ '--login' ], {
17 | env : baseEnv
18 | } );
19 |
20 | this.pty.stdout.on( 'data', ( data ) => {
21 | this.emit( 'data', data.toString( 'utf8' ) );
22 | } );
23 |
24 | this.pty.on( 'exit', () => {
25 | if ( ! this.ended ) {
26 | this.ended = true;
27 | this.emit( 'exit' );
28 | }
29 | } );
30 |
31 | this.write( ' PS1=__FORREST_START__\n RPROMPT=\'\'\n' );
32 |
33 | this.shell = defaultShell;
34 | }
35 |
36 | exit () {
37 | this.destroy();
38 | }
39 |
40 | write ( data ) {
41 | this.pty.stdin.write( data );
42 | }
43 |
44 | resize ( { cols: columns, rows } ) {
45 | try {
46 | this.pty.stdout.resize( { columns, rows } );
47 | } catch ( error ) {
48 | console.error( error.stack );
49 | }
50 | }
51 |
52 | destroy () {
53 | try {
54 | this.pty.kill( 'SIGHUP' );
55 | } catch ( error ) {
56 | console.error( 'exit error', error.stack );
57 | }
58 | this.emit( 'exit' );
59 | this.ended = true;
60 | }
61 | };
62 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "forrest",
3 | "productName": "Forrest",
4 | "version": "1.0.0-zeta",
5 | "description": "The npm script desktop client",
6 | "main": "electron.js",
7 | "dependencies": {
8 | "child_pty": "3.0.1",
9 | "default-shell": "^1.0.1",
10 | "electron-settings": "^1.0.4",
11 | "electron-window-state": "^3.0.3",
12 | "github": "~2.3.0",
13 | "hterm-umdjs": "^1.1.3",
14 | "uuid": "^2.0.2",
15 | "vue": "^1.0.24",
16 | "vue-electron": "^1.0.0",
17 | "vue-router": "^0.7.13",
18 | "vuex": "^0.6.3"
19 | },
20 | "author": "stefan judis ",
21 | "license": "MIT"
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/App.vue:
--------------------------------------------------------------------------------
1 |
144 |
145 |
146 |
148 |
149 |
153 |
154 |
155 |
156 |
157 |
158 |
208 |
--------------------------------------------------------------------------------
/app/src/components/AboutView.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
69 |
70 |
71 |
Forrest {{ version }}
72 |
Brought to you by
73 |
74 |
Check out the project on .
75 |
76 |
77 |
Shout out to these projects/people:
78 |
79 |
80 | -
81 |
82 | Evan You
83 |
92 |
93 | for creation and maintenance of
94 | -
95 |
96 | Greg Holguin
97 |
106 |
107 | for creation of the
108 | -
109 |
110 | Guillermo Rauch
111 |
120 |
121 | for creation of which helped a lot resolving problems with terminal emulation
122 |
-
123 |
124 | Sindre Sorhus
125 |
134 |
135 | for all this great work and especially for solving the issues around environments and the holy PATH.
136 | -
137 |
138 | Michael Kühnel
139 |
148 |
149 | for giving Forrest its name
150 | -
151 |
Calvin Goodman
152 | for creation of the "Home" icon from the Noun Project
153 |
-
154 |
Michal Beno
155 | for creation of the "Settings" icon from the Noun Project
156 |
-
157 |
Danin Polshin
158 | for creation of the "Running man" icon from the Noun Project
159 |
160 |
161 |
162 |
163 |
172 |
--------------------------------------------------------------------------------
/app/src/components/AppView/CustomCommandsInput.vue:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/app/src/components/AppView/CustomCommandsInput.vue
--------------------------------------------------------------------------------
/app/src/components/AppView/CustomCommandsList.vue:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/app/src/components/AppView/CustomCommandsList.vue
--------------------------------------------------------------------------------
/app/src/components/AppView/HeaderBar.vue:
--------------------------------------------------------------------------------
1 |
75 |
76 |
77 |
100 |
101 |
102 |
111 |
--------------------------------------------------------------------------------
/app/src/components/AppView/Settings.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
67 |
68 |
69 |
123 |
--------------------------------------------------------------------------------
/app/src/components/HelpView.vue:
--------------------------------------------------------------------------------
1 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
90 |
91 |
92 |
Shortcuts
93 |
94 |
95 |
96 |
97 |
General
98 |
99 |
100 | -
101 | ⌘ n
102 |
103 | - Open another Forrest window
104 | -
105 | ⌘ w
106 |
107 | - Close current window
108 | -
109 | ⌘ q
110 |
111 | - Quit forrest
112 | -
113 | ⌘ ,
114 |
115 | - Toggle settings
116 |
117 |
118 |
119 |
Home
120 |
121 |
122 | -
123 |
128 |
129 | - Show more project options
130 |
131 | -
132 |
136 |
137 | - Open project
138 |
139 | -
140 |
144 |
148 |
149 | - Navigate through projects
150 |
151 |
152 |
153 |
Projects
154 |
155 |
156 | -
157 |
161 |
162 | - Go Home / Terminate running command
163 |
164 | -
165 |
169 |
173 |
174 | - Navigate through commands
175 |
176 |
177 |
178 |
179 |
180 | To reach other actions use the tab key when needed.
181 |
182 |
183 |
184 |
--------------------------------------------------------------------------------
/app/src/components/RepoListView.vue:
--------------------------------------------------------------------------------
1 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
66 |
--------------------------------------------------------------------------------
/app/src/components/RepoListView/KnownRepos.vue:
--------------------------------------------------------------------------------
1 |
74 |
75 |
76 |
81 |
82 |
86 |
projects so far
87 |
Wanna add one?
88 |
92 |
93 |
135 |
136 |
139 |
140 |
141 |
142 |
275 |
--------------------------------------------------------------------------------
/app/src/components/RepoListView/OpenRepoButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
28 |
--------------------------------------------------------------------------------
/app/src/components/RepoView.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
37 |
--------------------------------------------------------------------------------
/app/src/components/RepoView/Command.vue:
--------------------------------------------------------------------------------
1 |
36 |
37 |
38 |
39 |
64 |
65 |
$ {{ script.command }}
66 |
67 |
68 |
69 |
70 |
87 |
--------------------------------------------------------------------------------
/app/src/components/RepoView/CommandOutput.vue:
--------------------------------------------------------------------------------
1 |
2 |
106 |
107 |
108 |
136 |
137 |
138 |
280 |
--------------------------------------------------------------------------------
/app/src/components/RepoView/Repo.vue:
--------------------------------------------------------------------------------
1 |
70 |
71 |
72 |
78 |
88 |
116 |
117 |
118 |
119 |
120 |
121 |
266 |
--------------------------------------------------------------------------------
/app/src/components/UpdateAvailableView.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
57 |
58 |
59 |
Forrest {{ version }}
60 |
61 |
There is a new version available.
62 |
We're working on the auto update but for now you have to get the new version at the yourself. :)
63 |
64 |
65 |
66 |
67 |
68 |
77 |
--------------------------------------------------------------------------------
/app/src/defaults.js:
--------------------------------------------------------------------------------
1 | export default {
2 | commands : [
3 | {
4 | name : 'install',
5 | slug : 'default-install',
6 | command : 'npm install',
7 | flags : [],
8 | docs : 'https://docs.npmjs.com/cli/install'
9 | },
10 | {
11 | name : 'drop & install',
12 | slug : 'default-drop-&-install',
13 | command : 'rm -rf ./node_modules && npm install',
14 | flags : [],
15 | docs : 'https://docs.npmjs.com/cli/install'
16 | },
17 | {
18 | name : 'shrinkwrap',
19 | slug : 'default-shrinkwrap',
20 | command : 'npm shrinkwrap',
21 | flags : [],
22 | docs : 'https://docs.npmjs.com/cli/shrinkwrap'
23 | },
24 | {
25 | name : 'list',
26 | slug : 'default-list',
27 | command : 'npm ls',
28 | flags : [],
29 | docs : 'https://docs.npmjs.com/cli/ls'
30 | },
31 | {
32 | name : 'prune',
33 | slug : 'default-prune',
34 | command : 'npm prune',
35 | flags : [],
36 | docs : 'https://docs.npmjs.com/cli/prune'
37 | },
38 | {
39 | name : 'outdated',
40 | slug : 'default-outdated',
41 | command : 'npm outdated',
42 | flags : [],
43 | docs : 'https://docs.npmjs.com/cli/outdated'
44 | }
45 | ],
46 |
47 | settings : [
48 | {
49 | label : 'Stay on top',
50 | desc : 'Should Forrest stay on top of other windows',
51 | name : 'alwaysOnTop',
52 | type : 'checkbox'
53 | },
54 | {
55 | label : 'Display notifications',
56 | desc : 'Display notifications when a script exits',
57 | name : 'displayNotifications',
58 | type : 'checkbox'
59 | },
60 | {
61 | label : 'Show project path',
62 | desc : 'Display project path in list view',
63 | name : 'showProjectPath',
64 | type : 'checkbox'
65 | },
66 | {
67 | label : 'Terminal output font size',
68 | desc : 'Increase/decrease the font size of the terminal output',
69 | name : 'terminalFontSize',
70 | type : 'number',
71 | unit : 'px'
72 | }
73 | ]
74 | };
75 |
--------------------------------------------------------------------------------
/app/src/directives/KeyTracker.vue:
--------------------------------------------------------------------------------
1 |
37 |
--------------------------------------------------------------------------------
/app/src/directives/OpenExternal.vue:
--------------------------------------------------------------------------------
1 |
24 |
--------------------------------------------------------------------------------
/app/src/directives/StayDown.vue:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------
/app/src/fonts/lato/FONTLOG.txt:
--------------------------------------------------------------------------------
1 |
2 | Lato font family
3 |
4 | ================
5 |
6 | Version 1.104; Western+Polish opensource
7 |
8 | Created by: tyPoland Lukasz Dziedzic
9 | Creation year: 2011
10 |
11 | Copyright (c) 2010-2011 by tyPoland Lukasz Dziedzic with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1.
12 |
13 | Lato is a trademark of tyPoland Lukasz Dziedzic.
14 |
15 | Source URL: http://www.latofonts.com/
16 | License URL: http://scripts.sil.org/OFL
17 |
18 | ================
19 |
20 | Lato is a sanserif typeface family designed in the Summer 2010 by Warsaw-based designer Łukasz Dziedzic (“Lato” means “Summer” in Polish). In December 2010 the Lato family was published under the open-source Open Font License by his foundry tyPoland, with support from Google.
21 |
22 | In the last ten or so years, during which Łukasz has been designing type, most of his projects were rooted in a particular design task that he needed to solve. With Lato, it was no different. Originally, the family was conceived as a set of corporate fonts for a large client — who in the end decided to go in different stylistic direction, so the family became available for a public release.
23 |
24 | When working on Lato, Łukasz tried to carefully balance some potentially conflicting priorities. He wanted to create a typeface that would seem quite “transparent” when used in body text but would display some original traits when used in larger sizes. He used classical proportions (particularly visible in the uppercase) to give the letterforms familiar harmony and elegance. At the same time, he created a sleek sanserif look, which makes evident the fact that Lato was designed in 2010 — even though it does not follow any current trend.
25 |
26 | The semi-rounded details of the letters give Lato a feeling of warmth, while the strong structure provides stability and seriousness. “Male and female, serious but friendly. With the feeling of the Summer,” says Łukasz.
27 |
28 | Lato consists of five weights (plus corresponding italics), including a beautiful hairline style.
29 |
30 | ================
31 |
32 | REVISION LOG:
33 |
34 | # Version 1.104 (2011-11-08)
35 | Merged the distribution again
36 | Autohinted with updated ttfautohint 0.4 (which no longer causes Adobe and iOS problems)
37 | except the Hai and Lig weights which are hinted in FLS 5.1.
38 |
39 | # Version 1.102 (2011-10-28)
40 | Added OpenType Layout features
41 | Ssplit between desktop and web versions
42 | Desktop version: all weights autohinted with FontLab Studio
43 | Web version autohinted with ttfautohint 0.4 except the Hai and Lig weights
44 |
45 | # Version 1.101 (2011-09-30)
46 | Fixed OS/2 table Unicode and codepage entries
47 |
48 | # Version 1.100 (2011-09-12)
49 | Added Polish diacritics to the character set
50 | Weights Hai and Lig autohinted with FontLab Studio
51 | Other weights autohinted with ttfautohint 0.3
52 |
53 | # Version 1.011 (2010-12-29)
54 | Added the soft hyphen glyph
55 |
56 | # Version 1.010 (2010-12-13)
57 | Initial version released under SIL Open Font License
58 | Western character set
59 |
60 | ================
61 |
--------------------------------------------------------------------------------
/app/src/fonts/lato/Lato-Black.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/app/src/fonts/lato/Lato-Black.woff2
--------------------------------------------------------------------------------
/app/src/fonts/lato/Lato-BlackItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/app/src/fonts/lato/Lato-BlackItalic.woff2
--------------------------------------------------------------------------------
/app/src/fonts/lato/Lato-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/app/src/fonts/lato/Lato-Bold.woff2
--------------------------------------------------------------------------------
/app/src/fonts/lato/Lato-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/app/src/fonts/lato/Lato-BoldItalic.woff2
--------------------------------------------------------------------------------
/app/src/fonts/lato/Lato-Hairline.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/app/src/fonts/lato/Lato-Hairline.woff2
--------------------------------------------------------------------------------
/app/src/fonts/lato/Lato-HairlineItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/app/src/fonts/lato/Lato-HairlineItalic.woff2
--------------------------------------------------------------------------------
/app/src/fonts/lato/Lato-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/app/src/fonts/lato/Lato-Italic.woff2
--------------------------------------------------------------------------------
/app/src/fonts/lato/Lato-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/app/src/fonts/lato/Lato-Light.woff2
--------------------------------------------------------------------------------
/app/src/fonts/lato/Lato-LightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/app/src/fonts/lato/Lato-LightItalic.woff2
--------------------------------------------------------------------------------
/app/src/fonts/lato/Lato-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/app/src/fonts/lato/Lato-Regular.woff2
--------------------------------------------------------------------------------
/app/src/fonts/lato/OFL.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato"
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/app/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Electron from 'vue-electron';
3 | import Router from 'vue-router';
4 | import App from './App';
5 | import routes from './routes';
6 |
7 | Vue.use( Electron );
8 | Vue.use( Router );
9 |
10 | // TODO make this generic
11 | import OpenExternal from './directives/OpenExternal';
12 | Vue.directive( 'openExternal', OpenExternal );
13 |
14 | import StayDown from './directives/StayDown';
15 | Vue.directive( 'stayDown', StayDown );
16 |
17 | import KeyTracker from './directives/KeyTracker';
18 | Vue.directive( 'keyTracker', KeyTracker );
19 |
20 | Vue.config.debug = true;
21 |
22 | const router = new Router();
23 |
24 | router.map( routes );
25 | router.beforeEach( () => {
26 | window.scrollTo( 0, 0 );
27 | } );
28 | router.redirect( {
29 | '*' : '/'
30 | } );
31 |
32 | router.start( App, 'app' );
33 |
--------------------------------------------------------------------------------
/app/src/modules/DomUtils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Get parent of dom element with
3 | * given class
4 | *
5 | * @param {Object} el element
6 | * @param {String} className className
7 | * @return {Object} parent element with given class
8 | */
9 | export function getParentWithClass( el, className ) {
10 | let parent = null;
11 | let p = el.parentElement;
12 |
13 | while ( p !== null ) {
14 | var o = p;
15 |
16 | if ( o.classList && o.classList.contains( className ) ) {
17 | parent = o;
18 | break;
19 | }
20 |
21 | p = o.parentNode;
22 | }
23 |
24 | return parent;
25 | }
--------------------------------------------------------------------------------
/app/src/modules/Hterm.js:
--------------------------------------------------------------------------------
1 | import { hterm, lib } from 'hterm-umdjs';
2 |
3 | hterm.defaultStorage = new lib.Storage.Memory();
4 |
5 | hterm.Terminal.prototype.overlaySize = function () {};
6 |
7 | // passthrough all the commands that are meant to control
8 | // hyperterm and not the terminal itself
9 | const oldKeyDown = hterm.Keyboard.prototype.onKeyDown_;
10 | hterm.Keyboard.prototype.onKeyDown_ = function ( event ) {
11 | if ( event.metaKey || event.altKey ) {
12 | return;
13 | }
14 | return oldKeyDown.call( this, event );
15 | };
16 |
17 |
18 | // fixes a bug in hterm, where the shorthand hex
19 | // is not properly converted to rgb
20 | //
21 | // thx. @rauchg https://github.com/zeit/hyperterm/blob/master/lib/hterm.js#L91
22 | lib.colors.hexToRGB = function ( arg ) {
23 | var hex16 = lib.colors.re_.hex16;
24 | var hex24 = lib.colors.re_.hex24;
25 |
26 | function convert ( hex ) {
27 | if ( hex.length === 4 ) {
28 | hex = hex.replace( hex16, function( h, r, g, b ) {
29 | return '#' + r + r + g + g + b + b;
30 | } );
31 | }
32 | var ary = hex.match( hex24 );
33 | if ( !ary ) return null;
34 |
35 | return 'rgb(' +
36 | parseInt( ary[ 1 ], 16 ) + ', ' +
37 | parseInt( ary[ 2 ], 16 ) + ', ' +
38 | parseInt( ary[ 3 ], 16 ) +
39 | ')';
40 | }
41 |
42 | if ( arg instanceof Array ) {
43 | for ( var i = 0; i < arg.length; i++ ) {
44 | arg[ i ] = convert( arg[ i ] );
45 | }
46 | } else {
47 | arg = convert( arg );
48 | }
49 |
50 | return arg;
51 | };
52 |
53 |
54 | export default hterm;
55 | export { lib };
56 |
--------------------------------------------------------------------------------
/app/src/modules/Rpc.js:
--------------------------------------------------------------------------------
1 | export default class Client {
2 | constructor () {
3 | const electron = window.require( 'electron' );
4 | const EventEmitter = window.require( 'events' );
5 | this.emitter = new EventEmitter();
6 | this.ipc = electron.ipcRenderer;
7 | this.ipcListener = this.ipcListener.bind( this );
8 |
9 | if ( window.__rpcId ) {
10 | setTimeout( () => {
11 | this.id = window.__rpcId;
12 | this.ipc.on( this.id, this.ipcListener );
13 | this.emitter.emit( 'ready' );
14 | }, 0 );
15 | } else {
16 | this.ipc.on( 'init', ( ev, uid ) => {
17 | // we cache so that if the object
18 | // gets re-instantiated we don't
19 | // wait for a `init` event
20 | window.__rpcId = uid;
21 | this.id = uid;
22 |
23 | this.ipc.on( uid, this.ipcListener );
24 | this.emitter.emit( 'ready' );
25 | } );
26 | }
27 | }
28 |
29 | ipcListener( event, { ch, data } ) {
30 | this.emitter.emit( ch, data );
31 | }
32 |
33 | on ( ev, fn ) {
34 | this.emitter.on( ev, fn );
35 | }
36 |
37 | once ( ev, fn ) {
38 | this.emitter.once( ev, fn );
39 | }
40 |
41 | emit ( ev, data ) {
42 | if ( ! this.id ) {
43 | throw new Error( 'Not ready' );
44 | }
45 |
46 | this.ipc.send( this.id, { ev, data } );
47 | }
48 |
49 | removeListener ( ev, fn ) {
50 | this.emitter.removeListener( ev, fn );
51 | }
52 |
53 | removeAllListeners () {
54 | this.emitter.removeAllListeners();
55 | }
56 |
57 | destroy () {
58 | this.removeAllListeners();
59 | this.ipc.removeAllListeners();
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/modules/WindowKeyManager.js:
--------------------------------------------------------------------------------
1 | document.addEventListener( 'keydown', keyDownHandler );
2 |
3 | export const keyCodes = {
4 | esc : 27,
5 | left : 37,
6 | up : 38,
7 | right : 39,
8 | down : 40,
9 | comma : 188
10 | };
11 |
12 | const customCommands = [ 'cmdComma' ];
13 |
14 | const handlerKeys = [ ...Object.keys( keyCodes ), ...customCommands ];
15 | const handlers = handlerKeys.reduce( ( handlers, key ) => {
16 | handlers[ key ] = {
17 | keyCode : /^cmd.*$/.test( key ) ?
18 | keyCodes[ key.replace( 'cmd', '' ).toLowerCase() ] :
19 | keyCodes[ key ],
20 | handlers : []
21 | };
22 |
23 | return handlers;
24 | }, {} );
25 |
26 |
27 | /**
28 | * Handle keydown events and fire only the last(!) attached event handler
29 | * for given keyCode
30 | *
31 | * @param {Object} event
32 | */
33 | function keyDownHandler( event ) {
34 | handlerKeys.forEach( key => {
35 | if ( /^cmd.*$/.test( key ) && ! event.metaKey ) {
36 | return;
37 | }
38 |
39 | if (
40 | handlers[ key ].keyCode === event.keyCode &&
41 | handlers[ key ].handlers.length
42 | ) {
43 | handlers[ key ].handlers[ handlers[ key ].handlers.length - 1 ]( event.target );
44 |
45 | event.preventDefault();
46 | }
47 | } );
48 | }
49 |
50 |
51 | /**
52 | * Push a new event handler into the handlers queue
53 | *
54 | * @param {String} key
55 | * @param {Funcation} fn
56 | */
57 | function add( key, fn ) {
58 | handlers[ key ].handlers.push( fn );
59 | }
60 |
61 |
62 | /**
63 | * Remove handler from handlers queue
64 | *
65 | * @param {String} key
66 | * @param {Function} fn
67 | */
68 | function remove( key, fn ) {
69 | handlers[ key ].handlers = handlers[ key ].handlers.reduce( ( handlers, handler ) => {
70 | if ( handler !== fn ) {
71 | handlers.push( handler );
72 | }
73 |
74 | return handlers;
75 | }, [] );
76 | }
77 |
78 | export default {
79 | add,
80 | remove
81 | };
--------------------------------------------------------------------------------
/app/src/routes.js:
--------------------------------------------------------------------------------
1 | export default {
2 | '/' : {
3 | component : require( './components/RepoListView' ),
4 | name : 'repo-list-page'
5 | },
6 | '/repos/:repoName' : {
7 | component : require( './components/RepoView' ),
8 | name : 'repo-page'
9 | },
10 | '/about' : {
11 | component : require( './components/AboutView' ),
12 | name : 'about-page',
13 | isStatic : true
14 | },
15 | '/help' : {
16 | component : require( './components/HelpView' ),
17 | name : 'help-page',
18 | isStatic : true
19 | },
20 | '/update-available' : {
21 | component : require( './components/UpdateAvailableView' ),
22 | name : 'update-available-page',
23 | isStatic : true
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/app/src/styles/_utils.scss:
--------------------------------------------------------------------------------
1 | .u-paddingDefault {
2 | padding : 1em;
3 | }
4 |
5 | .u-noPadding {
6 | padding : 0;
7 | }
8 |
9 | .u-marginHorizontalSmall {
10 | margin-left : .5em;
11 | margin-right : .5em;
12 | }
13 |
14 | .u-negativeMarginHorizontalSmall {
15 | margin-left : -.5em;
16 | margin-right : -.5em;
17 | }
18 |
19 | .u-marginTopSmall {
20 | margin-top : .5em;
21 | }
22 |
23 | .u-marginBottom {
24 | margin-bottom : 1em;
25 | }
26 |
27 | .u-noMarginBottom {
28 | margin-bottom : 0;
29 | }
30 |
31 | .u-negativeBottomMarginTiny {
32 | margin-bottom : -.25em;
33 | }
34 |
35 | .u-marginBottomSmall {
36 | margin-bottom : .5em;
37 | }
38 |
39 | .u-marginRightSmall {
40 | margin-right : .5em;
41 | }
42 |
43 | .u-marginLeftSmall {
44 | margin-left : .5em;
45 | }
46 |
47 | .u-marginTopSmall {
48 | margin-top : .5em;
49 | }
50 |
51 | .u-marginTop {
52 | margin-top : 1em;
53 | }
54 |
55 | .u-marginTopLarge {
56 | margin-top : 2em;
57 | }
58 |
59 | .u-marginLeftAuto {
60 | margin-left : auto;
61 | }
62 |
63 | .u-flex {
64 | display : flex;
65 | }
66 |
67 | .u-flexCenter {
68 | display : flex;
69 |
70 | align-items : center;
71 | justify-content : center;
72 | }
73 |
74 | .u-flexVerticalCenter {
75 | display : flex;
76 |
77 | align-items : center;
78 | }
79 |
80 | .u-flexSpaceBetween {
81 | justify-content : space-between;
82 | }
83 |
84 | .u-flex-50-50 {
85 | display : flex;
86 |
87 | > * {
88 | width : 50%;
89 | }
90 | }
91 |
92 | .u-flex-33-33-33 {
93 | display : flex;
94 |
95 | > * {
96 | width : 33.333%;
97 | }
98 | }
99 |
100 | .u-fillRed {
101 | fill : var(--svg-fill-red);
102 | }
103 |
104 | .u-fillGreen {
105 | fill : var(--svg-fill-green);
106 | }
107 |
108 | .u-fillNone {
109 | fill : none;
110 | }
111 |
112 | .u-fullHeight {
113 | height : 100%;
114 | }
115 |
116 | .u-widthTwoChar {
117 | display : inline-block;
118 | width : 2ch;
119 | }
120 |
121 | .u-visuallyHidden {
122 | border : 0;
123 | clip : rect(0 0 0 0);
124 | height : 1px;
125 | margin : -1px;
126 | overflow : hidden;
127 | padding : 0;
128 | position : absolute;
129 | width : 1px;
130 | }
131 |
132 | .u-positionRelative {
133 | position : relative;
134 | }
135 |
136 | .u-noBorderTop {
137 | border-top : none !important;
138 | }
139 |
140 | .u-fontSizeSmall {
141 | font-size : .9em;
142 | }
143 |
144 | .u-fontWeightBold {
145 | font-weight : bold;
146 | }
147 |
148 | .u-textTransformUppercase {
149 | text-transform : uppercase;
150 | }
151 |
--------------------------------------------------------------------------------
/app/src/styles/animations/_move-down.scss:
--------------------------------------------------------------------------------
1 | @keyframes moveDown {
2 | 0% {
3 | transform : translate( 0, 0 );
4 | }
5 |
6 | 50% {
7 | transform : translate( 0, 25% );
8 | }
9 |
10 | 100% {
11 | transform : translate( 0, 0 );
12 | }
13 | }
14 |
15 | .a-moveDown {
16 | animation : .75s moveDown 0s 3 ease-in-out;
17 | }
--------------------------------------------------------------------------------
/app/src/styles/components/_drag-handle.scss:
--------------------------------------------------------------------------------
1 | .c-dragHandle {
2 | position : absolute;
3 |
4 | top : 0;
5 | right : 0;
6 | left : 0;
7 |
8 | height : 2em;
9 |
10 | -webkit-user-select : none;
11 | -webkit-app-region : drag;
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/styles/components/_logo.scss:
--------------------------------------------------------------------------------
1 | .c-logo {
2 | display : inline-block;
3 |
4 | width : 5em;
5 | height : 5em;
6 |
7 | margin-bottom : .5em;
8 |
9 | svg {
10 | max-width : 100%;
11 | max-height : 100%;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/styles/fonts/_lato.scss:
--------------------------------------------------------------------------------
1 | /* latin-ext */
2 | @font-face {
3 | font-family: 'Lato';
4 | font-style: normal;
5 | font-weight: 300;
6 | src: local('Lato Light'), local('Lato-Light'), url(./fonts/lato/Lato-Light.woff2) format('woff2');
7 | }
8 |
9 | /* latin-ext */
10 | @font-face {
11 | font-family: 'Lato';
12 | font-style: italic;
13 | font-weight: 300;
14 | src: local('Lato Light Italic'), local('Lato-LightItalic'), url(./fonts/lato/Lato-LightItalic.woff2) format('woff2');
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/styles/objects/_buttons.scss:
--------------------------------------------------------------------------------
1 | .o-linkBtn {
2 | color : inherit;
3 | text-decoration : underline;
4 | line-height : inherit;
5 | font-size : inherit;
6 | font-weight : inherit;
7 | font-family : inherit;
8 |
9 | background : transparent;
10 |
11 | cursor : pointer;
12 |
13 | border : none;
14 | display : inline-block;
15 | }
16 |
17 | .o-primaryBtn {
18 | display : block;
19 | width : 100%;
20 |
21 | padding : .5em;
22 |
23 | color : var(--main-bg-color);
24 | background : var(--npm-red);
25 |
26 | border : none;
27 |
28 | font-size : 1em;
29 | font-family : inherit;
30 | }
--------------------------------------------------------------------------------
/app/src/styles/objects/_checkbox.scss:
--------------------------------------------------------------------------------
1 | .o-checkbox {
2 | display : block;
3 |
4 | width : 1.25em;
5 | height : 1.25em;
6 |
7 | background : var(--main-bg-color);
8 |
9 | svg {
10 | width : 100%;
11 | height : 100%;
12 |
13 | transform : scale( 0, 0 );
14 | transition : transform .125s ease-in-out;
15 |
16 | fill : var(--npm-red);
17 | }
18 |
19 | input:checked + & {
20 | svg {
21 | transition : transform .125s cubic-bezier( 0, 0, 0.25, 1.75 );
22 |
23 | transform : scale( 1, 1 );
24 | }
25 | }
26 |
27 | input:focus + & {
28 | outline : .1875em solid var(--npm-red-dark);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/styles/objects/_code.scss:
--------------------------------------------------------------------------------
1 | .o-code {
2 | font-size : .8em;
3 |
4 | padding : .5em;
5 |
6 | background-color : var(--code-background);
7 | color : var(--code-color);
8 |
9 | border-radius : .25em;
10 |
11 | overflow : auto;
12 |
13 | &, & > code {
14 | display : block;
15 | }
16 | }
--------------------------------------------------------------------------------
/app/src/styles/objects/_headlines.scss:
--------------------------------------------------------------------------------
1 | .o-headline {
2 | &-1 {
3 | }
4 |
5 | &-2 {
6 | font-size : 1.5em;
7 | font-weight : 600;
8 | }
9 |
10 | &-3 {
11 | font-size : 1.25em;
12 | font-weight : 600;
13 | }
14 |
15 | &-4 {
16 | font-size : 1.125em;
17 | font-weight : bold;
18 | }
19 |
20 | &-5 {
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/styles/objects/_icon.scss:
--------------------------------------------------------------------------------
1 | .o-icon {
2 | width : 2.5em;
3 | height : 2.5em;
4 | border : none;
5 | background : none;
6 | padding : .25em;
7 |
8 | svg {
9 | width : 100%;
10 | height : 100%;
11 |
12 | fill : var(--svg-fill);
13 | }
14 |
15 | &:hover {
16 | svg {
17 | fill : var(--npm-red);
18 | }
19 | }
20 |
21 | &:active {
22 | svg {
23 | fill : var(--npm-red-dark);
24 | }
25 | }
26 |
27 | &:disabled {
28 | svg {
29 | fill : #ccc;
30 | }
31 | }
32 |
33 | & .o-pathNoFill {
34 | fill : none;
35 | }
36 |
37 | &.o-icon__npm {
38 | width : 4em;
39 | }
40 |
41 | &.o-icon__fillBright {
42 | svg {
43 | fill : var(--svg-fill-bright);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/styles/objects/_input.scss:
--------------------------------------------------------------------------------
1 | .o-input {
2 | display : block;
3 | width : 100%;
4 | padding : .5em;
5 | border : 1px solid var(--npm-red-dark);
6 | font-size : 1em;
7 | font-family : monospace;
8 |
9 | &:focus {
10 | outline : .1875em solid var(--npm-red-dark);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/styles/objects/_lists.scss:
--------------------------------------------------------------------------------
1 | .o-list {
2 | list-style : none;
3 |
4 | margin : 0;
5 | padding : 0;
6 |
7 | &--item {
8 | padding : .5em .75em;
9 |
10 | &:hover {
11 | background : rgba( 0, 0, 0, .05 );
12 | }
13 |
14 | & + & {
15 | border-top : 1px solid var(--border-color);
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/styles/objects/_paragraphs.scss:
--------------------------------------------------------------------------------
1 | .o-paragraph {
2 | font-size : 1em;
3 | margin-bottom : .5em;
4 | }
--------------------------------------------------------------------------------
/app/src/styles/objects/_small.scss:
--------------------------------------------------------------------------------
1 | .o-small {
2 | display : block;
3 |
4 | font-style : italic;
5 | font-weight : 300;
6 | font-size : .8rem;
7 | line-height : 1.375;
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/styles/transitions/_slide-down--slide-up.scss:
--------------------------------------------------------------------------------
1 |
2 | .t-slideDown--slideUp-transition {
3 | transition :
4 | transform .275s ease-in-out,
5 | opacity .3s ease-in-out;
6 |
7 | will-change : transform;
8 | transform : translate( 0, 0 );
9 | opacity : 1;
10 | }
11 |
12 | .t-slideDown--slideUp-enter, .t-slideDown--slideUp-leave {
13 | transform : translate( 0, -100% );
14 | opacity : .75;
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/styles/transitions/_slide-left--slide-right.scss:
--------------------------------------------------------------------------------
1 | .t-slideLeft--slideRight {
2 | &-transition {
3 | transition :
4 | transform .275s ease-in-out,
5 | opacity .3s ease-in-out;
6 | }
7 |
8 | &-enter, &-leave {
9 | transform : translate( 100%, 0 );
10 | opacity : .3;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/styles/transitions/_slide-right--slide-left.scss:
--------------------------------------------------------------------------------
1 | .t-slideRight--slideLeft {
2 | &-transition {
3 | transition :
4 | transform .275s ease-in-out,
5 | opacity .3s ease-in-out;
6 | }
7 |
8 | &-enter, &-leave {
9 | transform : translate( -100%, 0 );
10 | opacity : .3;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/styles/transitions/_slide-up--slide-down.scss:
--------------------------------------------------------------------------------
1 | .t-slideUp--slideDown-transition {
2 | transition :
3 | transform .275s ease-in-out,
4 | opacity .3s ease-in-out;
5 |
6 | transform : translate( 0, 0 );
7 | opacity : 1;
8 | }
9 |
10 | .t-slideUp--slideDown-enter, .t-slideUp--slideDown-leave {
11 | transform : translate( 0, 100% );
12 | opacity : .75;
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/vuex/actions.js:
--------------------------------------------------------------------------------
1 | export const addRepoWithPath = function( { dispatch, state }, repoPath ) {
2 | let repoIsAlreadyAdded = state.repos.all.some( repo => repo.path === repoPath );
3 |
4 | if ( ! repoIsAlreadyAdded ) {
5 | window.rpc.emit(
6 | 'add repo',
7 | repoPath
8 | );
9 | } else {
10 | new Notification(
11 | 'Project is already in the list',
12 | {
13 | body : `-> ${ repoPath }`
14 | }
15 | );
16 | }
17 | };
18 |
19 | export const reloadRepo = function( { dispatch, state }, repo ) {
20 | window.rpc.emit(
21 | 'update repo',
22 | repo.path
23 | );
24 | };
25 |
26 | export const removeRepo = function( { dispatch, state }, repo ) {
27 | window.rpc.emit(
28 | 'remove repo',
29 | repo.path
30 | );
31 | };
32 |
33 | export const updateAppSetting = function( { dispatch }, name, setting ) {
34 | window.rpc.emit(
35 | 'update app settings',
36 | { name, setting }
37 | );
38 | };
39 |
40 | export const handleUpdatedRepos = function( { dispatch }, repos ) {
41 | dispatch( 'UPDATED_REPOS', repos );
42 | };
43 |
44 | export const writeSessionData = function( { dispatch, state }, data ) {
45 | window.rpc.emit( 'data', { uid : state.session.uid, data } );
46 | };
47 |
48 | export const execSessionCmd = function( { dispatch, state }, data ) {
49 | // by starting with a space
50 | // we keep it out of the history
51 | data = ` ${ data }\n`;
52 | window.rpc.emit( 'data', { uid : state.session.uid, data } );
53 | };
54 |
55 | export const clearSessionData = function( { dispatch, state }, data ) {
56 | dispatch( 'CLEAR_SESSION_OUTPUT', data );
57 | };
58 |
59 | export const updateSessionOutput = function( { dispatch }, data ) {
60 | dispatch( 'UPDATE_SESSION_OUTPUT', data );
61 | };
62 |
63 | export const setTerminalSize = function( { dispatch, state }, cols, rows ) {
64 | window.rpc.emit(
65 | 'set terminal size', { uid : state.session.uid, cols, rows }
66 | );
67 | };
68 |
--------------------------------------------------------------------------------
/app/src/vuex/getters.js:
--------------------------------------------------------------------------------
1 | export function getRepos( state ) {
2 | return state.repos.all;
3 | }
4 |
5 | export function getAppSettings( state ) {
6 | return state.app.settings;
7 | }
8 |
9 | export function isAppReady( state ) {
10 | return state.app.ready;
11 | }
12 |
13 | export function getConfigSettings( state ) {
14 | return state.defaults.settings;
15 | }
16 |
17 | export function getDefaultCommands( state ) {
18 | return state.defaults.commands;
19 | }
20 |
21 | export function getSessionOutput( state ) {
22 | return state.session.output;
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/vuex/modules/app.js:
--------------------------------------------------------------------------------
1 | const state = {
2 | settings : {},
3 | ready : false
4 | };
5 |
6 | const mutations = {
7 | APP_READY ( state, ready ) {
8 | state.ready = ready;
9 | },
10 | SETTINGS_LOADED ( state, settings ) {
11 | state.settings = settings;
12 | },
13 |
14 | UPDATE_APP_SETTING ( state, name, setting ) {
15 | const newSettings = {};
16 |
17 | newSettings[ name ] = setting;
18 |
19 | state.settings = Object.assign( state.settings, newSettings );
20 | }
21 | };
22 |
23 | export default {
24 | state,
25 | mutations
26 | };
27 |
--------------------------------------------------------------------------------
/app/src/vuex/modules/defaults.js:
--------------------------------------------------------------------------------
1 | import defaultSettings from '../../defaults';
2 |
3 | const state = defaultSettings;
4 |
5 | const mutations = {};
6 |
7 | export default {
8 | state,
9 | mutations
10 | };
11 |
--------------------------------------------------------------------------------
/app/src/vuex/modules/index.js:
--------------------------------------------------------------------------------
1 | const files = require.context( '.', false, /\.js$/ );
2 | let modules = {};
3 |
4 | files.keys().forEach( ( key ) => {
5 | if ( key === './index.js' ) return;
6 | modules[ key.replace( /(\.\/|\.js)/g, '' ) ] = files( key ).default;
7 | } );
8 |
9 | export default modules;
10 |
--------------------------------------------------------------------------------
/app/src/vuex/modules/repos.js:
--------------------------------------------------------------------------------
1 | const state = {
2 | all : []
3 | };
4 |
5 | const mutations = {
6 | REPOS_LOADED ( state, repos ) {
7 | state.all = repos;
8 | },
9 |
10 | REPOS_UPDATED ( state, repos ) {
11 | state.all = repos;
12 | }
13 | };
14 |
15 | export default {
16 | state,
17 | mutations
18 | };
19 |
--------------------------------------------------------------------------------
/app/src/vuex/modules/session.js:
--------------------------------------------------------------------------------
1 | const state = {
2 | id : null,
3 | output : ''
4 | };
5 |
6 | const mutations = {
7 | CLEAR_SESSION_OUTPUT ( state ) {
8 | state.output = '';
9 | },
10 | SET_SESSION ( state, session ) {
11 | state.uid = session.uid;
12 | },
13 | UPDATE_SESSION_OUTPUT ( state, data ) {
14 | state.output = state.output + data.data;
15 | }
16 | };
17 |
18 | export default {
19 | state,
20 | mutations
21 | };
22 |
--------------------------------------------------------------------------------
/app/src/vuex/store.js:
--------------------------------------------------------------------------------
1 | import RPC from '../modules/Rpc';
2 | import Vue from 'vue';
3 | import Vuex from 'vuex';
4 | import modules from './modules';
5 |
6 | Vue.use( Vuex );
7 |
8 | const rpc = new RPC();
9 | const store = new Vuex.Store( {
10 | modules,
11 | strict : true
12 | } );
13 |
14 | console.log( window.rpc );
15 |
16 | window.__defineGetter__( 'rpc', () => rpc );
17 |
18 |
19 | // all JS is loaded now it's
20 | // time to spawn the session
21 | rpc.on( 'ready', () => {
22 | rpc.emit( 'create session' );
23 | } );
24 |
25 | rpc.on( 'repos loaded', ( repos ) => {
26 | store.dispatch( 'REPOS_LOADED', repos );
27 | store.dispatch( 'APP_READY', true );
28 | } );
29 |
30 | rpc.on( 'repos updated', ( repos ) => {
31 | store.dispatch( 'REPOS_UPDATED', repos );
32 | } );
33 |
34 | rpc.on( 'session set', ( session ) => {
35 | store.dispatch( 'SET_SESSION', session );
36 | } );
37 |
38 | rpc.on( 'session data', ( data ) => {
39 | store.dispatch( 'UPDATE_SESSION_OUTPUT', data );
40 | } );
41 |
42 | rpc.on( 'setting set', ( { name, setting } ) => {
43 | store.dispatch( 'UPDATE_APP_SETTING', name, setting );
44 | } );
45 |
46 | rpc.on( 'settings loaded', ( settings ) => {
47 | store.dispatch( 'SETTINGS_LOADED', settings );
48 | } );
49 |
50 | export default store;
51 |
--------------------------------------------------------------------------------
/build/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/build/background.png
--------------------------------------------------------------------------------
/build/background@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/build/background@2x.png
--------------------------------------------------------------------------------
/build/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/build/icon.icns
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const pkg = require( './app/package.json' );
4 | const getDependencies = require( 'dependency-list' );
5 |
6 | const config = {
7 | name : pkg.name,
8 | devtron : true,
9 | eslint : true,
10 | port : 9080,
11 | vueDevTools : false,
12 | build : {
13 | 'app-version' : pkg.version,
14 | packagesToBeIncluded : [ 'electron-settings', 'electron-window-state', 'github', 'default-shell', 'child_pty', 'uuid' ],
15 | overwrite : true,
16 | asar : false,
17 | dmg : {
18 | 'background-color' : '#E1E1E1',
19 | contents : [
20 | {
21 | x : 485,
22 | y : 240,
23 | type : 'link',
24 | path : '/Applications'
25 | },
26 | {
27 | x : 120,
28 | y : 240,
29 | type : 'file'
30 | }
31 | ],
32 | 'icon-size' : 100,
33 | window : {
34 | width : 600,
35 | height : 500
36 | }
37 | },
38 | }
39 | };
40 |
41 | /**
42 | *
43 | */
44 | function getPackConfig( callback ) {
45 |
46 | let versionMap = getPackageVersionsFromApp( config.build.packagesToBeIncluded );
47 |
48 | // that can be prettier
49 | getDependencies( versionMap, ( error, result ) => {
50 | if ( error ) {
51 | return console.error( error );
52 | }
53 |
54 | config.build.files = [
55 | 'electron.js',
56 | 'package.json',
57 | 'main/*',
58 | 'dist/**/*',
59 | '!node_modules/*',
60 | ...Object.keys( result ).map( dep => `node_modules/${ dep }` )
61 | ];
62 |
63 | callback( null, config );
64 | } );
65 | }
66 |
67 |
68 | /**
69 | *
70 | */
71 | function getPackageVersionsFromApp( packages ) {
72 | const appPackageJson = require( './app/package' );
73 |
74 | return packages.reduce( ( versionMap, packageName ) => {
75 | versionMap[ packageName ] = appPackageJson.dependencies[ packageName ];
76 |
77 | return versionMap;
78 | }, {} );
79 | }
80 |
81 |
82 | module.exports = {
83 | config : config,
84 | getPackConfig : getPackConfig
85 | };
86 |
--------------------------------------------------------------------------------
/devtools/devtools.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | vue-devtools
6 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/devtools/hook.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 |
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 |
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId])
10 | /******/ return installedModules[moduleId].exports;
11 |
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ exports: {},
15 | /******/ id: moduleId,
16 | /******/ loaded: false
17 | /******/ };
18 |
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 |
22 | /******/ // Flag the module as loaded
23 | /******/ module.loaded = true;
24 |
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 |
29 |
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 |
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 |
36 | /******/ // __webpack_public_path__
37 | /******/ __webpack_require__.p = "/build/";
38 |
39 | /******/ // Load entry module and return exports
40 | /******/ return __webpack_require__(0);
41 | /******/ })
42 | /************************************************************************/
43 | /******/ ({
44 |
45 | /***/ 0:
46 | /***/ function(module, exports, __webpack_require__) {
47 |
48 | eval("'use strict';\n\nvar _hook = __webpack_require__(162);\n\n(0, _hook.installHook)(window);\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9zcmMvaG9vay5qcz80YTAwIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUE7O0FBRUEsdUJBQVksTUFBWiIsImZpbGUiOiIwLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgaW5zdGFsbEhvb2sgfSBmcm9tICcuLi8uLi8uLi9zcmMvYmFja2VuZC9ob29rJ1xuXG5pbnN0YWxsSG9vayh3aW5kb3cpXG5cblxuXG4vKiogV0VCUEFDSyBGT09URVIgKipcbiAqKiAuL3NyYy9ob29rLmpzXG4gKiovIl0sInNvdXJjZVJvb3QiOiIifQ==");
49 |
50 | /***/ },
51 |
52 | /***/ 162:
53 | /***/ function(module, exports) {
54 |
55 | eval("'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.installHook = installHook;\n// this script is injected into every page.\n\n/**\n * Install the hook on window, which is an event emitter.\n * Note because Chrome content scripts cannot directly modify the window object,\n * we are evaling this function by inserting a script tag. That's why we have\n * to inline the whole event emitter implementation here.\n *\n * @param {Window} window\n */\n\nfunction installHook(window) {\n var listeners = {};\n\n var hook = {\n Vue: null,\n\n on: function on(event, fn) {\n event = '$' + event;(listeners[event] || (listeners[event] = [])).push(fn);\n },\n once: function once(event, fn) {\n event = '$' + event;\n function on() {\n this.off(event, on);\n fn.apply(this, arguments);\n }\n ;(listeners[event] || (listeners[event] = [])).push(on);\n },\n off: function off(event, fn) {\n event = '$' + event;\n if (!arguments.length) {\n listeners = {};\n } else {\n var cbs = listeners[event];\n if (cbs) {\n if (!fn) {\n listeners[event] = null;\n } else {\n for (var i = 0, l = cbs.length; i < l; i++) {\n var cb = cbs[i];\n if (cb === fn || cb.fn === fn) {\n cbs.splice(i, 1);\n break;\n }\n }\n }\n }\n }\n },\n emit: function emit(event) {\n event = '$' + event;\n var cbs = listeners[event];\n if (cbs) {\n var args = [].slice.call(arguments, 1);\n cbs = cbs.slice();\n for (var i = 0, l = cbs.length; i < l; i++) {\n cbs[i].apply(this, args);\n }\n }\n }\n };\n\n hook.once('init', function (Vue) {\n hook.Vue = Vue;\n });\n\n hook.once('vuex:init', function (store) {\n hook.store = store;\n });\n\n Object.defineProperty(window, '__VUE_DEVTOOLS_GLOBAL_HOOK__', {\n get: function get() {\n return hook;\n }\n });\n}\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vL1VzZXJzL2Jyc3Rld2FyL1JlcG9zaXRvcmllcy9lbGVjdHJvbi1ib2lsZXJwbGF0ZS12dWUvdG9vbHMvdnVlLWRldnRvb2xzL3NyYy9iYWNrZW5kL2hvb2suanM/MjdkNSJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztRQVdnQjs7Ozs7Ozs7Ozs7O0FBQVQsU0FBUyxXQUFULENBQXNCLE1BQXRCLEVBQThCO0FBQ25DLE1BQUksWUFBWSxFQUFaLENBRCtCOztBQUduQyxNQUFNLE9BQU87QUFDWCxTQUFLLElBQUw7O0FBRUEsb0JBQUksT0FBTyxJQUFJO0FBQ2IsY0FBUSxNQUFNLEtBQU4sQ0FESyxDQUVYLFVBQVUsS0FBVixNQUFxQixVQUFVLEtBQVYsSUFBbUIsRUFBbkIsQ0FBckIsQ0FBRCxDQUE4QyxJQUE5QyxDQUFtRCxFQUFuRCxFQUZZO0tBSEo7QUFRWCx3QkFBTSxPQUFPLElBQUk7QUFDZixjQUFRLE1BQU0sS0FBTixDQURPO0FBRWYsZUFBUyxFQUFULEdBQWU7QUFDYixhQUFLLEdBQUwsQ0FBUyxLQUFULEVBQWdCLEVBQWhCLEVBRGE7QUFFYixXQUFHLEtBQUgsQ0FBUyxJQUFULEVBQWUsU0FBZixFQUZhO09BQWY7QUFJQSxPQU5lLENBTWIsVUFBVSxLQUFWLE1BQXFCLFVBQVUsS0FBVixJQUFtQixFQUFuQixDQUFyQixDQUFELENBQThDLElBQTlDLENBQW1ELEVBQW5ELEVBTmM7S0FSTjtBQWlCWCxzQkFBSyxPQUFPLElBQUk7QUFDZCxjQUFRLE1BQU0sS0FBTixDQURNO0FBRWQsVUFBSSxDQUFDLFVBQVUsTUFBVixFQUFrQjtBQUNyQixvQkFBWSxFQUFaLENBRHFCO09BQXZCLE1BRU87QUFDTCxZQUFNLE1BQU0sVUFBVSxLQUFWLENBQU4sQ0FERDtBQUVMLFlBQUksR0FBSixFQUFTO0FBQ1AsY0FBSSxDQUFDLEVBQUQsRUFBSztBQUNQLHNCQUFVLEtBQVYsSUFBbUIsSUFBbkIsQ0FETztXQUFULE1BRU87QUFDTCxpQkFBSyxJQUFJLElBQUksQ0FBSixFQUFPLElBQUksSUFBSSxNQUFKLEVBQVksSUFBSSxDQUFKLEVBQU8sR0FBdkMsRUFBNEM7QUFDMUMsa0JBQUksS0FBSyxJQUFJLENBQUosQ0FBTCxDQURzQztBQUUxQyxrQkFBSSxPQUFPLEVBQVAsSUFBYSxHQUFHLEVBQUgsS0FBVSxFQUFWLEVBQWM7QUFDN0Isb0JBQUksTUFBSixDQUFXLENBQVgsRUFBYyxDQUFkLEVBRDZCO0FBRTdCLHNCQUY2QjtlQUEvQjthQUZGO1dBSEY7U0FERjtPQUpGO0tBbkJTO0FBdUNYLHdCQUFNLE9BQU87QUFDWCxjQUFRLE1BQU0sS0FBTixDQURHO0FBRVgsVUFBSSxNQUFNLFVBQVUsS0FBVixDQUFOLENBRk87QUFHWCxVQUFJLEdBQUosRUFBUztBQUNQLFlBQU0sT0FBTyxHQUFHLEtBQUgsQ0FBUyxJQUFULENBQWMsU0FBZCxFQUF5QixDQUF6QixDQUFQLENBREM7QUFFUCxjQUFNLElBQUksS0FBSixFQUFOLENBRk87QUFHUCxhQUFLLElBQUksSUFBSSxDQUFKLEVBQU8sSUFBSSxJQUFJLE1BQUosRUFBWSxJQUFJLENBQUosRUFBTyxHQUF2QyxFQUE0QztBQUMxQyxjQUFJLENBQUosRUFBTyxLQUFQLENBQWEsSUFBYixFQUFtQixJQUFuQixFQUQwQztTQUE1QztPQUhGO0tBMUNTO0dBQVAsQ0FINkI7O0FBdURuQyxPQUFLLElBQUwsQ0FBVSxNQUFWLEVBQWtCLGVBQU87QUFDdkIsU0FBSyxHQUFMLEdBQVcsR0FBWCxDQUR1QjtHQUFQLENBQWxCLENBdkRtQzs7QUEyRG5DLE9BQUssSUFBTCxDQUFVLFdBQVYsRUFBdUIsaUJBQVM7QUFDOUIsU0FBSyxLQUFMLEdBQWEsS0FBYixDQUQ4QjtHQUFULENBQXZCLENBM0RtQzs7QUErRG5DLFNBQU8sY0FBUCxDQUFzQixNQUF0QixFQUE4Qiw4QkFBOUIsRUFBOEQ7QUFDNUQsd0JBQU87QUFDTCxhQUFPLElBQVAsQ0FESztLQURxRDtHQUE5RCxFQS9EbUMiLCJmaWxlIjoiMTYyLmpzIiwic291cmNlc0NvbnRlbnQiOlsiLy8gdGhpcyBzY3JpcHQgaXMgaW5qZWN0ZWQgaW50byBldmVyeSBwYWdlLlxuXG4vKipcbiAqIEluc3RhbGwgdGhlIGhvb2sgb24gd2luZG93LCB3aGljaCBpcyBhbiBldmVudCBlbWl0dGVyLlxuICogTm90ZSBiZWNhdXNlIENocm9tZSBjb250ZW50IHNjcmlwdHMgY2Fubm90IGRpcmVjdGx5IG1vZGlmeSB0aGUgd2luZG93IG9iamVjdCxcbiAqIHdlIGFyZSBldmFsaW5nIHRoaXMgZnVuY3Rpb24gYnkgaW5zZXJ0aW5nIGEgc2NyaXB0IHRhZy4gVGhhdCdzIHdoeSB3ZSBoYXZlXG4gKiB0byBpbmxpbmUgdGhlIHdob2xlIGV2ZW50IGVtaXR0ZXIgaW1wbGVtZW50YXRpb24gaGVyZS5cbiAqXG4gKiBAcGFyYW0ge1dpbmRvd30gd2luZG93XG4gKi9cblxuZXhwb3J0IGZ1bmN0aW9uIGluc3RhbGxIb29rICh3aW5kb3cpIHtcbiAgbGV0IGxpc3RlbmVycyA9IHt9XG5cbiAgY29uc3QgaG9vayA9IHtcbiAgICBWdWU6IG51bGwsXG5cbiAgICBvbiAoZXZlbnQsIGZuKSB7XG4gICAgICBldmVudCA9ICckJyArIGV2ZW50XG4gICAgICA7KGxpc3RlbmVyc1tldmVudF0gfHwgKGxpc3RlbmVyc1tldmVudF0gPSBbXSkpLnB1c2goZm4pXG4gICAgfSxcblxuICAgIG9uY2UgKGV2ZW50LCBmbikge1xuICAgICAgZXZlbnQgPSAnJCcgKyBldmVudFxuICAgICAgZnVuY3Rpb24gb24gKCkge1xuICAgICAgICB0aGlzLm9mZihldmVudCwgb24pXG4gICAgICAgIGZuLmFwcGx5KHRoaXMsIGFyZ3VtZW50cylcbiAgICAgIH1cbiAgICAgIDsobGlzdGVuZXJzW2V2ZW50XSB8fCAobGlzdGVuZXJzW2V2ZW50XSA9IFtdKSkucHVzaChvbilcbiAgICB9LFxuXG4gICAgb2ZmIChldmVudCwgZm4pIHtcbiAgICAgIGV2ZW50ID0gJyQnICsgZXZlbnRcbiAgICAgIGlmICghYXJndW1lbnRzLmxlbmd0aCkge1xuICAgICAgICBsaXN0ZW5lcnMgPSB7fVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgY29uc3QgY2JzID0gbGlzdGVuZXJzW2V2ZW50XVxuICAgICAgICBpZiAoY2JzKSB7XG4gICAgICAgICAgaWYgKCFmbikge1xuICAgICAgICAgICAgbGlzdGVuZXJzW2V2ZW50XSA9IG51bGxcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgZm9yIChsZXQgaSA9IDAsIGwgPSBjYnMubGVuZ3RoOyBpIDwgbDsgaSsrKSB7XG4gICAgICAgICAgICAgIGxldCBjYiA9IGNic1tpXVxuICAgICAgICAgICAgICBpZiAoY2IgPT09IGZuIHx8IGNiLmZuID09PSBmbikge1xuICAgICAgICAgICAgICAgIGNicy5zcGxpY2UoaSwgMSlcbiAgICAgICAgICAgICAgICBicmVha1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcblxuICAgIGVtaXQgKGV2ZW50KSB7XG4gICAgICBldmVudCA9ICckJyArIGV2ZW50XG4gICAgICBsZXQgY2JzID0gbGlzdGVuZXJzW2V2ZW50XVxuICAgICAgaWYgKGNicykge1xuICAgICAgICBjb25zdCBhcmdzID0gW10uc2xpY2UuY2FsbChhcmd1bWVudHMsIDEpXG4gICAgICAgIGNicyA9IGNicy5zbGljZSgpXG4gICAgICAgIGZvciAobGV0IGkgPSAwLCBsID0gY2JzLmxlbmd0aDsgaSA8IGw7IGkrKykge1xuICAgICAgICAgIGNic1tpXS5hcHBseSh0aGlzLCBhcmdzKVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgaG9vay5vbmNlKCdpbml0JywgVnVlID0+IHtcbiAgICBob29rLlZ1ZSA9IFZ1ZVxuICB9KVxuXG4gIGhvb2sub25jZSgndnVleDppbml0Jywgc3RvcmUgPT4ge1xuICAgIGhvb2suc3RvcmUgPSBzdG9yZVxuICB9KVxuXG4gIE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh3aW5kb3csICdfX1ZVRV9ERVZUT09MU19HTE9CQUxfSE9PS19fJywge1xuICAgIGdldCAoKSB7XG4gICAgICByZXR1cm4gaG9va1xuICAgIH1cbiAgfSlcbn1cblxuXG5cbi8qKiBXRUJQQUNLIEZPT1RFUiAqKlxuICoqIC9Vc2Vycy9icnN0ZXdhci9SZXBvc2l0b3JpZXMvZWxlY3Ryb24tYm9pbGVycGxhdGUtdnVlL3Rvb2xzL3Z1ZS1kZXZ0b29scy9zcmMvYmFja2VuZC9ob29rLmpzXG4gKiovIl0sInNvdXJjZVJvb3QiOiIifQ==");
56 |
57 | /***/ }
58 |
59 | /******/ });
60 |
--------------------------------------------------------------------------------
/dist/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/dist/.gitkeep
--------------------------------------------------------------------------------
/media/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/media/logo.jpg
--------------------------------------------------------------------------------
/media/preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest-app/forrest/8a4b3ffe7e64ebbaa29100f2a2bdb15088b03646/media/preview.jpg
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Forrest",
3 | "scripts": {
4 | "build": "node tasks/release.js",
5 | "build:clean": "del dist/* !.gitkeep",
6 | "build:darwin": "node tasks/release.js",
7 | "dev": "node tasks/runner.js",
8 | "lint": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter app",
9 | "pack": "cross-env NODE_ENV=production webpack -p --progress --colors",
10 | "test": "npm run lint",
11 | "postinstall": "install-app-deps",
12 | "vue:route": "node tasks/vue/route.js",
13 | "vuex:module": "node tasks/vuex/module.js"
14 | },
15 | "author": "Stefan Judis ",
16 | "repository": "stefanjudis/forrest",
17 | "license": "MIT",
18 | "devDependencies": {
19 | "babel-core": "^6.8.0",
20 | "babel-loader": "^6.2.4",
21 | "babel-plugin-transform-runtime": "^6.8.0",
22 | "babel-preset-es2015": "^6.6.0",
23 | "babel-preset-stage-0": "^6.5.0",
24 | "babel-runtime": "^6.11.0",
25 | "cross-env": "^2.0.0",
26 | "css-loader": "^0.23.1",
27 | "dependency-list": "^0.2.2",
28 | "devtron": "^1.1.0",
29 | "electron-builder": "^5.17.0",
30 | "electron-prebuilt": "^1.3.0",
31 | "eslint": "^3.1.1",
32 | "eslint-friendly-formatter": "^2.0.5",
33 | "eslint-loader": "^1.3.0",
34 | "eslint-plugin-html": "^1.4.0",
35 | "eslint-plugin-promise": "^2.0.0",
36 | "extract-text-webpack-plugin": "^1.0.1",
37 | "file-loader": "^0.9.0",
38 | "html-webpack-plugin": "^2.16.1",
39 | "json-loader": "^0.5.4",
40 | "node-sass": "^3.7.0",
41 | "sass-loader": "^4.0.0",
42 | "style-loader": "^0.13.1",
43 | "url-loader": "^0.5.7",
44 | "vue-hot-reload-api": "^1.3.3",
45 | "vue-html-loader": "^1.2.2",
46 | "vue-loader": "^8.3.1",
47 | "vue-style-loader": "^1.0.0",
48 | "webpack": "^1.13.1",
49 | "webpack-dev-server": "^1.14.1"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tasks/release.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const exec = require( 'child_process' ).exec;
4 | const builder = require( 'electron-builder' );
5 | const Platform = builder.Platform;
6 |
7 | pack();
8 |
9 | /**
10 | * Build webpack in production
11 | */
12 | function pack () {
13 | console.log( 'Building webpack in production mode...\n' );
14 | let pack = exec( 'npm run pack' );
15 |
16 | pack.stdout.on( 'data', data => console.log( data ) );
17 | pack.stderr.on( 'data', data => console.error( data ) );
18 | pack.on( 'exit', code => build() );
19 | }
20 |
21 |
22 | /**
23 | * Use electron-packager to build electron app
24 | */
25 | function build () {
26 | require( '../config' ).getPackConfig( ( error, config ) => {
27 | builder.build( {
28 | targets : Platform.MAC.createTarget(),
29 | devMetadata : {
30 | build : config.build
31 | }
32 | } )
33 | .then( () => {
34 | console.log( 'Build(s) successful!' );
35 | console.log( 'DONE\n' );
36 | } )
37 | .catch( error => {
38 | console.error( error );
39 | } );
40 | } );
41 | }
42 |
--------------------------------------------------------------------------------
/tasks/runner.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Credits to https://github.com/bradstewart/electron-boilerplate-vue/blob/master/build/dev-runner.js
3 | */
4 | 'use strict';
5 |
6 | const config = require( '../config' ).config;
7 | const exec = require( 'child_process' ).exec;
8 |
9 | let YELLOW = '\x1b[33m';
10 | let BLUE = '\x1b[34m';
11 | let END = '\x1b[0m';
12 |
13 | let isElectronOpen = false;
14 |
15 | function format ( command, data, color ) {
16 | return color + command + END +
17 | ' ' + // Two space offset
18 | data.toString().trim().replace( /\n/g, '\n' + repeat( ' ', command.length + 2 ) ) +
19 | '\n';
20 | }
21 |
22 | function repeat ( str, times ) {
23 | return ( new Array( times + 1 ) ).join( str );
24 | }
25 |
26 | let children = [];
27 |
28 |
29 | /**
30 | *
31 | *
32 | * @param {any} command
33 | * @param {any} color
34 | * @param {any} name
35 | */
36 | function run ( command, color, name ) {
37 | let child = exec( command );
38 |
39 | child.stdout.on( 'data', data => {
40 | console.log( format( name, data, color ) );
41 |
42 | /**
43 | * Start electron after VALID build
44 | * (prevents electron from opening a blank window that requires refreshing)
45 | *
46 | * NOTE: needs more testing for stability
47 | */
48 | if (/VALID/g.test(data.toString().trim().replace(/\n/g, '\n' + repeat(' ', command.length + 2))) && !isElectronOpen) {
49 | console.log( `${BLUE}Starting electron...\n${END}` );
50 | run('cross-env NODE_ENV=development electron app/electron.js', BLUE, 'electron')
51 | isElectronOpen = true;
52 | }
53 | } );
54 |
55 | child.stderr.on( 'data', data => console.error( format( name, data, color ) ) );
56 | child.on( 'exit', code => exit( code ) );
57 |
58 | children.push( child );
59 | }
60 |
61 | /**
62 | *
63 | *
64 | * @param {any} code
65 | */
66 | function exit ( code ) {
67 | children.forEach( child => {
68 | child.kill();
69 | } );
70 | process.exit( code );
71 | }
72 |
73 | console.log( `${YELLOW}Starting webpack-dev-server...\n${END}` );
74 |
75 | run(
76 | `webpack-dev-server --inline --hot --colors --port ${config.port} --content-base app/dist`,
77 | YELLOW,
78 | 'webpack'
79 | );
80 |
--------------------------------------------------------------------------------
/tasks/vue/route.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const fs = require('fs')
4 | const path = require('path')
5 |
6 | let routeName = process.argv[2]
7 | let routes = fs.readFileSync(
8 | path.join(__dirname, '../../app/src/routes.js'),
9 | 'utf8'
10 | ).split('\n')
11 | let routeTemplate = fs.readFileSync(
12 | path.join(__dirname, 'route.template.txt'),
13 | 'utf8'
14 | )
15 | let routesTemplate = fs.readFileSync(
16 | path.join(__dirname, 'routes.template.txt'),
17 | 'utf8'
18 | )
19 |
20 | routes[routes.length - 3] = routes[routes.length - 3] + ','
21 | routes.splice(
22 | routes.length - 2,
23 | 0,
24 | routesTemplate
25 | .replace(/{{routeName}}/g, routeName)
26 | .replace(/\n$/, '')
27 | )
28 |
29 | fs.writeFileSync(
30 | path.join(__dirname, `../../app/src/components/${routeName}View.vue`),
31 | routeTemplate
32 | )
33 |
34 | fs.mkdirSync(path.join(__dirname, `../../app/src/components/${routeName}View`))
35 |
36 | fs.writeFileSync(
37 | path.join(__dirname, '../../app/src/routes.js'),
38 | routes.join('\n')
39 | )
40 |
41 | console.log(`\n\x1b[33m[vue]\x1b[0m route "${routeName}" has been created`)
42 | console.log(' [ \n' + [
43 | ' ' + path.join(__dirname, `../../app/src/components/${routeName}View.vue`),
44 | path.join(__dirname, `../../app/src/components/${routeName}View`),
45 | path.join(__dirname, '../../app/src/routes.js'),
46 | ].join(',\n ') + '\n ]')
47 |
--------------------------------------------------------------------------------
/tasks/vue/route.template.txt:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
--------------------------------------------------------------------------------
/tasks/vue/routes.template.txt:
--------------------------------------------------------------------------------
1 | '/{{routeName}}': {
2 | component: require('./components/{{routeName}}View'),
3 | name: '{{routeName}}'
4 | }
5 |
--------------------------------------------------------------------------------
/tasks/vuex/module.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const fs = require('fs')
4 | const path = require('path')
5 |
6 | let moduleName = process.argv[2]
7 | let template = fs.readFileSync(
8 | path.join(__dirname, 'module.template.txt'),
9 | 'utf8'
10 | )
11 |
12 | fs.writeFileSync(
13 | path.join(__dirname, `../../app/src/vuex/modules/${moduleName}.js`),
14 | template
15 | )
16 |
17 | console.log(`\n\x1b[33m[vuex]\x1b[0m module "${moduleName}" has been created`)
18 | console.log(path.join(__dirname, `../../app/src/vuex/modules/${moduleName}.js`))
19 |
--------------------------------------------------------------------------------
/tasks/vuex/module.template.txt:
--------------------------------------------------------------------------------
1 | import {} from '../mutation-types'
2 |
3 | const state = {
4 | all: []
5 | }
6 |
7 | const mutations = {
8 |
9 | }
10 |
11 | export default {
12 | state,
13 | mutations
14 | }
15 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require( 'path' );
4 | const settings = require( './config.js' ).config;
5 | const webpack = require( 'webpack' );
6 |
7 | const ExtractTextPlugin = require( 'extract-text-webpack-plugin' );
8 | const HtmlWebpackPlugin = require( 'html-webpack-plugin' );
9 |
10 | let config = {
11 | devtool : '#eval-source-map',
12 | eslint : {
13 | formatter : require( 'eslint-friendly-formatter' )
14 | },
15 | entry : {
16 | build : [ path.join( __dirname, 'app/src/main.js' ) ]
17 | },
18 | module : {
19 | preLoaders : [],
20 | loaders : [
21 | {
22 | test : /\.css$/,
23 | loader : ExtractTextPlugin.extract( 'style-loader', 'css-loader' )
24 | },
25 | {
26 | test : /\.html$/,
27 | loader : 'vue-html-loader'
28 | },
29 | {
30 | test : /\.js$/,
31 | loader : 'babel',
32 | exclude : /node_modules/
33 | },
34 | {
35 | test : /\.json$/,
36 | loader : 'json-loader'
37 | },
38 | {
39 | test : /\.vue$/,
40 | loader : 'vue-loader'
41 | },
42 | {
43 | test : /\.(png|jpe?g|gif|svg)(\?.*)?$/,
44 | loader : 'url-loader',
45 | query : {
46 | limit : 10000,
47 | name : 'imgs/[name].[hash:7].[ext]'
48 | }
49 | },
50 | {
51 | test : /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
52 | loader : 'url-loader',
53 | query : {
54 | limit : 10000,
55 | name : 'fonts/[name].[hash:7].[ext]'
56 | }
57 | }
58 | ]
59 | },
60 | plugins : [
61 | new ExtractTextPlugin( 'styles.css' ),
62 | new HtmlWebpackPlugin( {
63 | excludeChunks : [ 'devtools' ],
64 | filename : 'index.html',
65 | template : './app/main.ejs',
66 | title : settings.name
67 | } ),
68 | new webpack.NoErrorsPlugin()
69 | ],
70 | output : {
71 | filename : '[name].js',
72 | path : path.join( __dirname, 'app/dist' )
73 | },
74 | resolve : {
75 | alias : {
76 | components : path.join( __dirname, 'app/src/components' ),
77 | src : path.join( __dirname, 'app/src' )
78 | },
79 | extensions : [ '', '.js', '.vue', '.json', '.css' ],
80 | fallback : [ path.join( __dirname, 'app/node_modules' ) ]
81 | },
82 | resolveLoader : {
83 | root : path.join( __dirname, 'node_modules' )
84 | },
85 | target : 'electron-renderer',
86 | vue : {
87 | autoprefixer : {
88 | browsers : [ 'last 2 Chrome versions' ]
89 | },
90 | loaders : {
91 | sass : 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
92 | scss : 'vue-style-loader!css-loader!sass-loader'
93 | }
94 | },
95 | // TODO check this
96 | externals : [ 'app' ]
97 | };
98 |
99 | if ( process.env.NODE_ENV !== 'production' ) {
100 | /**
101 | * Apply ESLint
102 | */
103 | if ( settings.eslint ) {
104 | config.module.preLoaders.push(
105 | {
106 | test : /\.js$/,
107 | loader : 'eslint-loader',
108 | exclude : /node_modules|devtools/
109 | },
110 | {
111 | test : /\.vue$/,
112 | loader : 'eslint-loader'
113 | }
114 | );
115 | }
116 |
117 | /**
118 | * Credits to
119 | * https://github.com/bradstewart/electron-boilerplate-vue/pull/17
120 | *
121 | * Apply vue-devtools window. Is ignored in production mode when building
122 | */
123 | if ( settings.vueDevTools ) {
124 | config.entry.build.unshift(
125 | path.join( __dirname, 'devtools/hook.js' ),
126 | path.join( __dirname, 'devtools/backend.js' )
127 | );
128 |
129 | config.entry.devtools = [
130 | path.join( __dirname, 'devtools/devtools.js' )
131 | ];
132 |
133 | config.plugins.push( new HtmlWebpackPlugin( {
134 | filename : 'devtools.html',
135 | template : path.join( __dirname, 'devtools/devtools.html' ),
136 | excludeChunks : [ 'build' ]
137 | } ) );
138 | }
139 | }
140 |
141 | /**
142 | * Adjust config for production settings
143 | */
144 | if ( process.env.NODE_ENV === 'production' ) {
145 | config.devtool = '';
146 |
147 | config.plugins.push(
148 | new webpack.DefinePlugin( {
149 | 'process.env.NODE_ENV' : '"production"'
150 | } ),
151 | new webpack.optimize.OccurenceOrderPlugin(),
152 | new webpack.optimize.UglifyJsPlugin( {
153 | compress : {
154 | warnings : false
155 | }
156 | } )
157 | );
158 | }
159 |
160 | module.exports = config;
161 |
--------------------------------------------------------------------------------