├── .eslintignore ├── .babelrc ├── templates ├── app │ ├── src │ │ ├── scss │ │ │ └── index.scss │ │ └── js │ │ │ ├── messages │ │ │ ├── en-US.js │ │ │ └── pt-BR.js │ │ │ ├── store.js │ │ │ ├── reducers │ │ │ ├── root.js │ │ │ ├── utils.js │ │ │ ├── dashboard.js │ │ │ ├── session.js │ │ │ ├── tasks.js │ │ │ └── nav.js │ │ │ ├── api │ │ │ ├── dashboard.js │ │ │ ├── session.js │ │ │ ├── utils.js │ │ │ ├── tasks.js │ │ │ └── request-watcher.js │ │ │ ├── actions │ │ │ ├── nav.js │ │ │ ├── dashboard.js │ │ │ ├── index.js │ │ │ ├── tasks.js │ │ │ └── session.js │ │ │ ├── index.js │ │ │ ├── screens │ │ │ ├── utils.js │ │ │ ├── NotFound.js │ │ │ ├── Login.js │ │ │ ├── Task.js │ │ │ ├── Tasks.js │ │ │ └── Dashboard.js │ │ │ ├── App.js │ │ │ └── components │ │ │ ├── NavControl.js │ │ │ ├── SessionMenu.js │ │ │ ├── Main.js │ │ │ └── NavSidebar.js │ ├── server │ │ ├── dev.js │ │ ├── api.js │ │ ├── server.js │ │ ├── data.js │ │ └── notifier.js │ ├── .eslintignore │ ├── public │ │ ├── img │ │ │ ├── splash.png │ │ │ ├── shortcut-icon.png │ │ │ ├── mobile-app-icon.png │ │ │ └── mobile-app-icon.svg │ │ └── index.html │ ├── .babelrc │ ├── .editorconfig │ ├── README.md │ ├── .gitignore │ ├── __tests__ │ │ └── components │ │ │ └── NavSidebar-test.js │ ├── .eslintrc │ ├── package.json │ ├── .sass-lint.yml │ └── webpack.config.babel.js ├── basic │ ├── src │ │ ├── scss │ │ │ └── index.scss │ │ └── js │ │ │ ├── index.js │ │ │ └── App.js │ ├── .eslintignore │ ├── public │ │ ├── img │ │ │ ├── shortcut-icon.png │ │ │ ├── mobile-app-icon.png │ │ │ └── mobile-app-icon.svg │ │ └── index.html │ ├── .babelrc │ ├── .editorconfig │ ├── __tests__ │ │ └── components │ │ │ └── App-test.js │ ├── README.md │ ├── .gitignore │ ├── .eslintrc │ ├── package.json │ ├── .sass-lint.yml │ └── webpack.config.babel.js └── docs │ ├── src │ ├── scss │ │ └── index.scss │ ├── js │ │ ├── App.js │ │ ├── index.js │ │ ├── routes.js │ │ ├── components │ │ │ ├── Page1.js │ │ │ └── Dashboard.js │ │ ├── static.js │ │ └── Main.js │ └── template.ejs │ ├── .eslintignore │ ├── public │ ├── img │ │ ├── shortcut-icon.png │ │ ├── mobile-app-icon.png │ │ └── mobile-app-icon.svg │ └── index.html │ ├── .babelrc │ ├── .editorconfig │ ├── __tests__ │ └── components │ │ └── Dashboard-test.js │ ├── README.md │ ├── .gitignore │ ├── .eslintrc │ ├── package.json │ ├── .sass-lint.yml │ └── webpack.config.babel.js ├── src ├── commands │ ├── version.js │ ├── eslint.js │ ├── scsslint.js │ ├── check.js │ ├── utils.js │ ├── new.js │ └── pack.js ├── grommet.js └── utils │ └── cli.js ├── .gitignore ├── .npmignore ├── .eslintrc ├── README.md ├── package.json └── LICENSE /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015" ] 3 | } 4 | -------------------------------------------------------------------------------- /templates/app/src/scss/index.scss: -------------------------------------------------------------------------------- 1 | @import '~<%= appTheme %>'; 2 | -------------------------------------------------------------------------------- /templates/basic/src/scss/index.scss: -------------------------------------------------------------------------------- 1 | @import '~<%= appTheme %>'; 2 | -------------------------------------------------------------------------------- /templates/docs/src/scss/index.scss: -------------------------------------------------------------------------------- 1 | @import '~<%= appTheme %>'; 2 | -------------------------------------------------------------------------------- /templates/docs/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | dist/** 3 | coverage/** 4 | -------------------------------------------------------------------------------- /templates/app/server/dev.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | require('./server'); 3 | -------------------------------------------------------------------------------- /templates/app/src/js/messages/en-US.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Tasks: 'Tasks' 3 | }; 4 | -------------------------------------------------------------------------------- /templates/app/src/js/messages/pt-BR.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Tasks: 'Tarefas' 3 | }; 4 | -------------------------------------------------------------------------------- /templates/app/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | dist/** 3 | dist-server/** 4 | coverage/** 5 | -------------------------------------------------------------------------------- /templates/basic/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | dist/** 3 | dist-server/** 4 | coverage/** 5 | -------------------------------------------------------------------------------- /templates/app/public/img/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grommet/grommet-cli/master/templates/app/public/img/splash.png -------------------------------------------------------------------------------- /templates/app/public/img/shortcut-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grommet/grommet-cli/master/templates/app/public/img/shortcut-icon.png -------------------------------------------------------------------------------- /templates/docs/public/img/shortcut-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grommet/grommet-cli/master/templates/docs/public/img/shortcut-icon.png -------------------------------------------------------------------------------- /templates/app/public/img/mobile-app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grommet/grommet-cli/master/templates/app/public/img/mobile-app-icon.png -------------------------------------------------------------------------------- /templates/basic/public/img/shortcut-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grommet/grommet-cli/master/templates/basic/public/img/shortcut-icon.png -------------------------------------------------------------------------------- /templates/docs/public/img/mobile-app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grommet/grommet-cli/master/templates/docs/public/img/mobile-app-icon.png -------------------------------------------------------------------------------- /templates/basic/public/img/mobile-app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grommet/grommet-cli/master/templates/basic/public/img/mobile-app-icon.png -------------------------------------------------------------------------------- /templates/basic/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015", "react" ], 3 | "env": { 4 | "development": { 5 | "presets": ["react-hmre"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /templates/app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015", "react" ], 3 | "plugins": [ "transform-object-rest-spread" ], 4 | "env": { 5 | "development": { 6 | "presets": ["react-hmre"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /templates/docs/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015", "react" ], 3 | "plugins": [ "transform-object-rest-spread" ], 4 | "env": { 5 | "development": { 6 | "presets": ["react-hmre"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /templates/app/src/js/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compose, applyMiddleware } from 'redux'; 2 | import root from './reducers/root'; 3 | import thunk from 'redux-thunk'; 4 | 5 | export default compose(applyMiddleware(thunk))(createStore)(root); 6 | -------------------------------------------------------------------------------- /templates/docs/src/js/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Router, browserHistory 4 | } from 'react-router'; 5 | 6 | import routes from './routes'; 7 | 8 | export default () => ; 9 | -------------------------------------------------------------------------------- /templates/basic/src/js/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import '../scss/index.scss'; 5 | 6 | import App from './App'; 7 | 8 | const element = document.getElementById('content'); 9 | ReactDOM.render(, element); 10 | 11 | document.body.classList.remove('loading'); 12 | -------------------------------------------------------------------------------- /templates/docs/src/js/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import '../scss/index.scss'; 5 | 6 | import App from './App'; 7 | 8 | const element = document.getElementById('content'); 9 | ReactDOM.render(, element); 10 | 11 | document.body.classList.remove('loading'); 12 | -------------------------------------------------------------------------------- /templates/app/src/js/reducers/root.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import dashboard from './dashboard'; 4 | import nav from './nav'; 5 | import session from './session'; 6 | import tasks from './tasks'; 7 | 8 | export default combineReducers({ 9 | dashboard, 10 | nav, 11 | session, 12 | tasks 13 | }); 14 | -------------------------------------------------------------------------------- /templates/app/src/js/reducers/utils.js: -------------------------------------------------------------------------------- 1 | export function createReducer(initialState, handlers) { 2 | return (state = initialState, action) => { 3 | const handler = handlers[action.type]; 4 | if (!handler) return state; 5 | return { ...state, ...handler(state, action) }; 6 | }; 7 | } 8 | 9 | export default { createReducer }; 10 | -------------------------------------------------------------------------------- /templates/docs/src/js/routes.js: -------------------------------------------------------------------------------- 1 | import Main from './Main'; 2 | import Dashboard from './components/Dashboard'; 3 | import Page1 from './components/Page1'; 4 | 5 | export default { 6 | path: '/', 7 | component: Main, 8 | indexRoute: { component: Dashboard }, 9 | childRoutes: [ 10 | { path: 'page1', component: Page1 } 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /templates/app/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | max_line_length = 80 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | max_line_length = 0 14 | trim_trailing_whitespace = false 15 | 16 | [COMMIT_EDITMSG] 17 | max_line_length = 0 18 | -------------------------------------------------------------------------------- /templates/basic/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | max_line_length = 80 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | max_line_length = 0 14 | trim_trailing_whitespace = false 15 | 16 | [COMMIT_EDITMSG] 17 | max_line_length = 0 18 | -------------------------------------------------------------------------------- /templates/docs/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | max_line_length = 80 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | max_line_length = 0 14 | trim_trailing_whitespace = false 15 | 16 | [COMMIT_EDITMSG] 17 | max_line_length = 0 18 | -------------------------------------------------------------------------------- /templates/app/src/js/api/dashboard.js: -------------------------------------------------------------------------------- 1 | import { requestWatcher } from './utils'; 2 | 3 | let dashboardWatcher; 4 | 5 | export function watchDashboard() { 6 | dashboardWatcher = requestWatcher.watch('/api/task?status=Running'); 7 | return dashboardWatcher; 8 | } 9 | 10 | export function unwatchDashboard() { 11 | if (dashboardWatcher) { 12 | dashboardWatcher.stop(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /templates/basic/src/js/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import App from 'grommet/components/App'; 4 | import Title from 'grommet/components/Title'; 5 | 6 | export default class BasicApp extends Component { 7 | render() { 8 | return ( 9 | 10 | Hello World 11 |

Hello from a Grommet page!

12 |
13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /templates/app/src/js/actions/nav.js: -------------------------------------------------------------------------------- 1 | import { 2 | NAV_ACTIVATE, NAV_ENABLE, NAV_RESPONSIVE 3 | } from '../actions'; 4 | 5 | export function navActivate(active) { 6 | return { type: NAV_ACTIVATE, active }; 7 | } 8 | 9 | export function navEnable(enabled) { 10 | return { type: NAV_ENABLE, enabled }; 11 | } 12 | 13 | export function navResponsive(responsive) { 14 | return { type: NAV_RESPONSIVE, responsive }; 15 | } 16 | -------------------------------------------------------------------------------- /templates/docs/src/js/components/Page1.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Heading from 'grommet/components/Heading'; 3 | import Paragraph from 'grommet/components/Paragraph'; 4 | import Section from 'grommet/components/Section'; 5 | 6 | export default () => ( 7 |
8 | Page 1 9 | This is a paragraph in the first page 10 |
11 | ); 12 | -------------------------------------------------------------------------------- /templates/docs/src/js/components/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Heading from 'grommet/components/Heading'; 3 | import Paragraph from 'grommet/components/Paragraph'; 4 | import Section from 'grommet/components/Section'; 5 | 6 | export default () => ( 7 |
8 | Dashboard 9 | This is a paragraph in the dashboard 10 |
11 | ); 12 | -------------------------------------------------------------------------------- /templates/app/src/js/index.js: -------------------------------------------------------------------------------- 1 | import 'whatwg-fetch'; 2 | import { polyfill as promisePolyfill } from 'es6-promise'; 3 | 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | 7 | import '../scss/index.scss'; 8 | 9 | import App from './App'; 10 | 11 | promisePolyfill(); 12 | 13 | const element = document.getElementById('content'); 14 | ReactDOM.render(, element); 15 | 16 | document.body.classList.remove('loading'); 17 | -------------------------------------------------------------------------------- /src/commands/version.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Node core dependencies 3 | **/ 4 | import path from 'path'; 5 | 6 | const cliInstance = require(path.join(__dirname, '../..', 'package.json')); 7 | 8 | export default function (vorpal) { 9 | vorpal 10 | .command('version', 'Check the current version of this CLI') 11 | .action((args, cb) => { 12 | vorpal.activeCommand.log( 13 | cliInstance.version 14 | ); 15 | cb(); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /templates/basic/__tests__/components/App-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | 4 | import App from '../../src/js/App'; 5 | 6 | // needed because this: 7 | // https://github.com/facebook/jest/issues/1353 8 | jest.mock('react-dom'); 9 | 10 | test('App renders', () => { 11 | const component = renderer.create( 12 | 13 | ); 14 | const tree = component.toJSON(); 15 | expect(tree).toMatchSnapshot(); 16 | }); 17 | -------------------------------------------------------------------------------- /templates/app/src/js/screens/utils.js: -------------------------------------------------------------------------------- 1 | import { announcePageLoaded } from 'grommet/utils/Announcer'; 2 | 3 | const DEFAULT_TITLE = '<%= appTitle %>'; 4 | 5 | export function pageLoaded(title) { 6 | if (document) { 7 | if (title && typeof title === 'string') { 8 | title = `${title} | ${DEFAULT_TITLE}`; 9 | } else { 10 | title = DEFAULT_TITLE; 11 | } 12 | announcePageLoaded(title); 13 | document.title = title; 14 | } 15 | } 16 | 17 | export default { pageLoaded }; 18 | -------------------------------------------------------------------------------- /templates/docs/__tests__/components/Dashboard-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | 4 | import Dashboard from '../../src/js/components/Dashboard'; 5 | 6 | // needed because this: 7 | // https://github.com/facebook/jest/issues/1353 8 | jest.mock('react-dom'); 9 | 10 | test('Dashboard renders', () => { 11 | const component = renderer.create( 12 | 13 | ); 14 | const tree = component.toJSON(); 15 | expect(tree).toMatchSnapshot(); 16 | }); 17 | -------------------------------------------------------------------------------- /templates/app/src/js/reducers/dashboard.js: -------------------------------------------------------------------------------- 1 | import { DASHBOARD_LOAD, DASHBOARD_UNLOAD } from '../actions'; 2 | import { createReducer } from './utils'; 3 | 4 | const initialState = { 5 | tasks: [] 6 | }; 7 | 8 | const handlers = { 9 | [DASHBOARD_LOAD]: (state, action) => { 10 | if (!action.error) { 11 | action.payload.error = undefined; 12 | return action.payload; 13 | } 14 | return { error: action.payload }; 15 | }, 16 | [DASHBOARD_UNLOAD]: () => initialState 17 | }; 18 | 19 | export default createReducer(initialState, handlers); 20 | -------------------------------------------------------------------------------- /templates/app/src/js/reducers/session.js: -------------------------------------------------------------------------------- 1 | import { 2 | SESSION_LOAD, SESSION_LOGIN, SESSION_LOGOUT 3 | } from '../actions'; 4 | import { createReducer } from './utils'; 5 | 6 | const initialState = {}; 7 | 8 | const handlers = { 9 | [SESSION_LOAD]: (state, action) => action.payload, 10 | [SESSION_LOGIN]: (state, action) => { 11 | if (!action.error) { 12 | return action.payload; 13 | } 14 | return { error: action.payload.message }; 15 | }, 16 | [SESSION_LOGOUT]: () => ({}) 17 | }; 18 | 19 | export default createReducer(initialState, handlers); 20 | -------------------------------------------------------------------------------- /templates/app/src/js/api/session.js: -------------------------------------------------------------------------------- 1 | import { headers, parseJSON } from './utils'; 2 | 3 | export function postSession(email, password) { 4 | const options = { 5 | headers: headers(), 6 | method: 'POST', 7 | body: JSON.stringify({ email, password }) 8 | }; 9 | 10 | return fetch('/api/sessions', options) 11 | .then(parseJSON); 12 | } 13 | 14 | export function deleteSession(session) { 15 | const options = { 16 | headers: headers(), 17 | method: 'DELETE' 18 | }; 19 | 20 | return fetch(session.uri, options) 21 | .then(parseJSON); 22 | } 23 | -------------------------------------------------------------------------------- /src/commands/eslint.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NPM dependencies 3 | **/ 4 | import Linter from 'eslint-parallel'; 5 | import yargs from 'yargs'; 6 | 7 | process.on('message', (assets) => { 8 | new Linter(Object.assign({ 9 | cache: true, 10 | cwd: process.cwd() 11 | }, yargs(process.argv.slice(2)).argv)).execute([assets]).then((result) => { 12 | const failed = result.errorCount || result.warningCount; 13 | if (failed) { 14 | process.exit(1); 15 | } else { 16 | process.exit(0); 17 | } 18 | }, (err) => { 19 | console.log(err); 20 | process.exit(1); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /templates/basic/README.md: -------------------------------------------------------------------------------- 1 | # <%= appName %> 2 | 3 | This is a sample Grommet application for reference. 4 | 5 | To run this application, execute the following commands: 6 | 7 | 1. Install NPM modules 8 | 9 | ``` 10 | $ npm install (or yarn install) 11 | ``` 12 | 13 | 2. Start the front-end dev server: 14 | 15 | ``` 16 | $ npm run dev 17 | ``` 18 | 19 | 3. Create the app distribution to be used by a back-end server 20 | 21 | ``` 22 | $ NODE_ENV=production grommet pack 23 | ``` 24 | 25 | 4. Test and run linters: 26 | 27 | ``` 28 | $ npm test 29 | ``` 30 | -------------------------------------------------------------------------------- /templates/docs/README.md: -------------------------------------------------------------------------------- 1 | # <%= appName %> 2 | 3 | This is a sample Grommet application for reference. 4 | 5 | To run this application, execute the following commands: 6 | 7 | 1. Install NPM modules 8 | 9 | ``` 10 | $ npm install (or yarn install) 11 | ``` 12 | 13 | 2. Start the front-end dev server: 14 | 15 | ``` 16 | $ npm run dev 17 | ``` 18 | 19 | 3. Create the app distribution to be used by a back-end server 20 | 21 | ``` 22 | $ NODE_ENV=production grommet pack 23 | ``` 24 | 25 | 4. Test and run linters: 26 | 27 | ``` 28 | $ npm test 29 | ``` 30 | -------------------------------------------------------------------------------- /templates/app/src/js/actions/dashboard.js: -------------------------------------------------------------------------------- 1 | import { DASHBOARD_LOAD, DASHBOARD_UNLOAD } from '../actions'; 2 | import { watchDashboard, unwatchDashboard } from '../api/dashboard'; 3 | 4 | export function loadDashboard() { 5 | return dispatch => ( 6 | watchDashboard() 7 | .on('success', 8 | payload => dispatch({ type: DASHBOARD_LOAD, payload }) 9 | ) 10 | .on('error', 11 | payload => dispatch({ type: DASHBOARD_LOAD, error: true, payload }) 12 | ) 13 | .start() 14 | ); 15 | } 16 | 17 | export function unloadDashboard() { 18 | unwatchDashboard(); 19 | return { type: DASHBOARD_UNLOAD }; 20 | } 21 | -------------------------------------------------------------------------------- /templates/app/src/js/api/utils.js: -------------------------------------------------------------------------------- 1 | import RequestWatcher from './request-watcher'; 2 | 3 | let _headers = { 4 | Accept: 'application/json', 5 | 'Content-Type': 'application/json' 6 | }; 7 | 8 | export function headers() { 9 | return _headers; 10 | } 11 | 12 | export function parseJSON(response) { 13 | if (response.ok) { 14 | return response.json(); 15 | } 16 | return Promise.reject(response); 17 | } 18 | 19 | export function updateHeaders(newHeaders) { 20 | _headers = { ..._headers, newHeaders }; 21 | Object.keys(_headers).forEach((key) => { 22 | if (undefined === _headers[key]) { 23 | delete _headers[key]; 24 | } 25 | }); 26 | } 27 | 28 | export const requestWatcher = new RequestWatcher(); 29 | -------------------------------------------------------------------------------- /templates/app/src/js/actions/index.js: -------------------------------------------------------------------------------- 1 | // See https://github.com/acdlite/flux-standard-action 2 | 3 | // Session 4 | export const SESSION_LOAD = 'SESSION_LOAD'; 5 | export const SESSION_LOGIN = 'SESSION_LOGIN'; 6 | export const SESSION_LOGOUT = 'SESSION_LOGOUT'; 7 | 8 | // Dashboard 9 | export const DASHBOARD_LOAD = 'DASHBOARD_LOAD'; 10 | export const DASHBOARD_UNLOAD = 'DASHBOARD_UNLOAD'; 11 | 12 | // Tasks 13 | export const TASKS_LOAD = 'TASKS_LOAD'; 14 | export const TASKS_UNLOAD = 'TASKS_UNLOAD'; 15 | export const TASK_LOAD = 'TASK_LOAD'; 16 | export const TASK_UNLOAD = 'TASK_UNLOAD'; 17 | 18 | // Nav 19 | export const NAV_ACTIVATE = 'NAV_ACTIVATE'; 20 | export const NAV_ENABLE = 'NAV_ENABLE'; 21 | export const NAV_RESPONSIVE = 'NAV_RESPONSIVE'; 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | bin/** 40 | -------------------------------------------------------------------------------- /src/commands/scsslint.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Node dependencies 3 | **/ 4 | import path from 'path'; 5 | import fs from 'fs'; 6 | 7 | /** 8 | * NPM dependencies 9 | **/ 10 | import lint from 'sass-lint'; 11 | 12 | process.on('message', (assets) => { 13 | let scssLintPath = path.resolve(process.cwd(), '.sass-lint.yml'); 14 | try { 15 | fs.accessSync(scssLintPath, fs.F_OK); 16 | } catch (e) { 17 | console.warn('.sass-lint.yml not found. skipping lint.'); 18 | process.exit(0); 19 | } 20 | 21 | const result = lint.lintFiles(assets, {}, scssLintPath); 22 | lint.outputResults(result, {}, scssLintPath); 23 | const failed = lint.resultCount(result); 24 | 25 | if (failed) { 26 | process.exit(1); 27 | } else { 28 | process.exit(0); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | src/** 40 | .gitignore 41 | -------------------------------------------------------------------------------- /templates/app/README.md: -------------------------------------------------------------------------------- 1 | # <%= appName %> 2 | 3 | This is a sample Grommet application for reference. 4 | 5 | To run this application, execute the following commands: 6 | 7 | 1. Install NPM modules 8 | 9 | ``` 10 | $ npm install (or yarn install) 11 | ``` 12 | 13 | 2. Start the back-end server: 14 | 15 | ``` 16 | $ npm run dev-server 17 | ``` 18 | 19 | 3. Start the front-end dev server: 20 | 21 | ``` 22 | $ npm run dev 23 | ``` 24 | 25 | 4. Create the app distribution to be used by a back-end server 26 | 27 | ``` 28 | $ NODE_ENV=production grommet pack 29 | ``` 30 | 31 | 5. Start the server in production mode: 32 | 33 | ``` 34 | $ npm start 35 | ``` 36 | 37 | 6. Test and run linters: 38 | 39 | ``` 40 | $ npm test 41 | ``` 42 | -------------------------------------------------------------------------------- /templates/docs/src/js/static.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOMServer from 'react-dom/server'; 3 | import { 4 | match, RouterContext, useRouterHistory 5 | } from 'react-router'; 6 | import { createMemoryHistory } from 'history'; 7 | import template from '../template.ejs'; 8 | 9 | import routes from './routes'; 10 | 11 | export default (locals, callback) => { 12 | const history = useRouterHistory(createMemoryHistory)(); 13 | const location = history.createLocation(locals.path); 14 | 15 | match({ routes, history, location }, 16 | (error, redirectLocation, renderProps) => { 17 | callback( 18 | null, template({ 19 | html: ReactDOMServer.renderToString( 20 | 21 | ) 22 | }) 23 | ); 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /templates/basic/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | bin/** 40 | dist/** 41 | .eslintcache 42 | .DS_Store 43 | .vscode -------------------------------------------------------------------------------- /templates/docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | bin/** 40 | dist/** 41 | .eslintcache 42 | .DS_Store 43 | .vscode -------------------------------------------------------------------------------- /templates/app/src/js/reducers/tasks.js: -------------------------------------------------------------------------------- 1 | import { TASKS_LOAD, TASKS_UNLOAD, TASK_LOAD, TASK_UNLOAD } from '../actions'; 2 | import { createReducer } from './utils'; 3 | 4 | const initialState = { 5 | tasks: [], 6 | task: undefined 7 | }; 8 | 9 | const handlers = { 10 | [TASKS_LOAD]: (state, action) => { 11 | if (!action.error) { 12 | action.payload.error = undefined; 13 | return action.payload; 14 | } 15 | return { error: action.payload }; 16 | }, 17 | [TASKS_UNLOAD]: () => initialState, 18 | [TASK_LOAD]: (state, action) => { 19 | if (!action.error) { 20 | action.payload.error = undefined; 21 | return action.payload; 22 | } 23 | return { error: action.payload }; 24 | }, 25 | [TASK_UNLOAD]: () => initialState 26 | }; 27 | 28 | export default createReducer(initialState, handlers); 29 | -------------------------------------------------------------------------------- /templates/app/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | bin/** 40 | dist/** 41 | dist-server/** 42 | .eslintcache 43 | .DS_Store 44 | .vscode -------------------------------------------------------------------------------- /templates/docs/src/template.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Grommet 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
<%- html %>
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /templates/app/public/img/mobile-app-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | mobile-app-icon 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /templates/basic/public/img/mobile-app-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | mobile-app-icon 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /templates/docs/public/img/mobile-app-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | mobile-app-icon 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /templates/app/__tests__/components/NavSidebar-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import { Provider } from 'react-redux'; 4 | import { Router } from 'react-router'; 5 | 6 | import createMemoryHistory from 'history/createMemoryHistory'; 7 | 8 | import NavSidebar from '../../src/js/components/NavSidebar'; 9 | import store from '../../src/js/store'; 10 | 11 | const history = createMemoryHistory('/'); 12 | 13 | const routes = [{ 14 | path: '/', 15 | component: () => 16 | }]; 17 | 18 | // needed because this: 19 | // https://github.com/facebook/jest/issues/1353 20 | jest.mock('react-dom'); 21 | 22 | test('NavSidebar renders', () => { 23 | const component = renderer.create( 24 | 25 | 26 | 27 | ); 28 | const tree = component.toJSON(); 29 | expect(tree).toMatchSnapshot(); 30 | }); 31 | -------------------------------------------------------------------------------- /src/grommet.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('babel-register'); 3 | require('dotenv').config({silent: true}); 4 | 5 | /** 6 | * NPM dependencies 7 | **/ 8 | import vorpal from 'vorpal'; 9 | 10 | /** 11 | * Local dependencies 12 | **/ 13 | import checkCommand from './commands/check'; 14 | import newCommand from './commands/new'; 15 | import packCommand from './commands/pack'; 16 | import versionCommand from './commands/version'; 17 | 18 | const cli = vorpal(); 19 | cli.use(checkCommand); 20 | cli.use(newCommand); 21 | cli.use(packCommand); 22 | cli.use(versionCommand); 23 | 24 | if (process.argv.length === 2) { 25 | // this means that only 'grommet' has been typed 26 | // we should launch the CLI in this case 27 | cli 28 | .delimiter(cli.chalk.magenta('grommet~$')) 29 | .show(); 30 | } else { 31 | // this means that more than 'grommet' has been typed 32 | // we should execute the command instead 33 | cli.parse(process.argv); 34 | } 35 | -------------------------------------------------------------------------------- /templates/app/src/js/api/tasks.js: -------------------------------------------------------------------------------- 1 | import RequestWatcher from './request-watcher'; 2 | 3 | let protocol = 'ws:'; 4 | if (window.location.protocol === 'https:') { 5 | protocol = 'wss:'; 6 | } 7 | const host = ((process.env.NODE_ENV === 'development') ? 8 | 'localhost:8102' : `${window.location.host}`); 9 | const webSocketUrl = `${protocol}//${host}`; 10 | 11 | const socketWatcher = new RequestWatcher({ webSocketUrl }); 12 | 13 | let tasksWatcher; 14 | 15 | export function watchTasks() { 16 | tasksWatcher = socketWatcher.watch('/api/task'); 17 | return tasksWatcher; 18 | } 19 | 20 | export function unwatchTasks() { 21 | if (tasksWatcher) { 22 | tasksWatcher.stop(); 23 | } 24 | } 25 | 26 | const taskWatcher = {}; 27 | 28 | export function watchTask(id) { 29 | taskWatcher[id] = socketWatcher.watch(`/api/task/${id}`); 30 | return taskWatcher[id]; 31 | } 32 | 33 | export function unwatchTask(id) { 34 | if (taskWatcher[id]) { 35 | taskWatcher[id].stop(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | parser: babel-eslint 3 | 4 | parserOptions: 5 | ecmaFeatures: 6 | experimentalObjectRestSpread: true 7 | sourceType: "module" 8 | 9 | env: 10 | browser: true 11 | node: true 12 | es6: true 13 | 14 | rules: 15 | # ERRORS 16 | space-before-blocks: 2 17 | indent: [2, 2, { SwitchCase: 1 }] 18 | brace-style: 2 19 | comma-dangle: 2 20 | no-unused-expressions: 2 21 | eol-last: 2 22 | dot-notation: 2 23 | no-unused-vars: [2, args: none] 24 | semi: [2, "always"] 25 | 26 | # DISABLED 27 | max-len: 0 28 | #change soon back to max-len: [1, 80] 29 | no-underscore-dangle: 0 30 | new-cap: 0 31 | no-use-before-define: 0 32 | key-spacing: 0 33 | eqeqeq: 0 34 | strict: 0 35 | space-unary-ops: 0 36 | yoda: 0 37 | no-loop-func: 0 38 | no-trailing-spaces: 0 39 | no-multi-spaces: 0 40 | no-shadow: 0 41 | no-alert: 0 42 | no-process-exit: 0 43 | no-extend-native: 0 44 | block-scoped-var: 0 45 | quotes: 0 46 | jsx-quotes: 0 47 | consistent-return: 0 48 | -------------------------------------------------------------------------------- /templates/app/src/js/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { IntlProvider, addLocaleData } from 'react-intl'; 3 | import en from 'react-intl/locale-data/en'; 4 | import { getCurrentLocale, getLocaleData } from 'grommet/utils/Locale'; 5 | import { Provider } from 'react-redux'; 6 | import { initialize } from './actions/session'; 7 | import store from './store'; 8 | import Main from './components/Main'; 9 | 10 | const locale = getCurrentLocale(); 11 | addLocaleData(en); 12 | let messages; 13 | try { 14 | messages = require(`./messages/${locale}`); 15 | } catch (e) { 16 | messages = require('./messages/en-US'); 17 | } 18 | const localeData = getLocaleData(messages, locale); 19 | 20 | if (window.location.pathname !== '/login') { 21 | store.dispatch(initialize(window.location.pathname)); 22 | } 23 | 24 | export default () => ( 25 | 26 | 27 |
28 | 29 | 30 | ); 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grommet-cli 2 | Command Line interface for grommet 3 | 4 | :warning: This project has been deprecated in favor of create-react-app. This project will only support Grommet v1. For Grommet v2 use [this](https://facebook.github.io/create-react-app/). 5 | 6 | ![](http://i.imgur.com/LER7lCH.gif) 7 | 8 | ## Install 9 | 10 | ```command 11 | npm install grommet-cli -g 12 | ``` 13 | 14 | ## Access CLI 15 | 16 | ```command 17 | grommet 18 | ``` 19 | 20 | ## Commands 21 | 22 | * `new [app-name]`: generates a new application based on a given type 23 | * `version`: checks the current version of the CLI 24 | 25 | ## Help 26 | 27 | Run `--help` after the command to see the documentation. For example: 28 | 29 | ```command 30 | new --help 31 | ``` 32 | 33 | ## Inline mode 34 | 35 | This CLI allows inline mode execution 36 | 37 | `grommet version` or `grommet new app-name --type app --theme hpe` 38 | 39 | ## Credits 40 | 41 | Behind grommet-cli is the awesome [vorpal](http://vorpal.js.org) framework. 42 | 43 | Made with :heart: by the Grommet team 44 | -------------------------------------------------------------------------------- /templates/app/src/js/actions/tasks.js: -------------------------------------------------------------------------------- 1 | import { TASKS_LOAD, TASKS_UNLOAD, TASK_LOAD, TASK_UNLOAD } from '../actions'; 2 | import { 3 | watchTasks, unwatchTasks, watchTask, unwatchTask 4 | } from '../api/tasks'; 5 | 6 | export function loadTasks() { 7 | return dispatch => ( 8 | watchTasks() 9 | .on('success', 10 | payload => dispatch({ type: TASKS_LOAD, payload }) 11 | ) 12 | .on('error', 13 | payload => dispatch({ type: TASKS_LOAD, error: true, payload }) 14 | ) 15 | .start() 16 | ); 17 | } 18 | 19 | export function unloadTasks() { 20 | unwatchTasks(); 21 | return { type: TASKS_UNLOAD }; 22 | } 23 | 24 | export function loadTask(id) { 25 | return dispatch => ( 26 | watchTask(id) 27 | .on('success', 28 | payload => dispatch({ type: TASK_LOAD, payload }) 29 | ) 30 | .on('error', 31 | payload => dispatch({ type: TASK_LOAD, error: true, payload }) 32 | ) 33 | .start() 34 | ); 35 | } 36 | 37 | export function unloadTask(id) { 38 | unwatchTask(id); 39 | return { type: TASK_UNLOAD }; 40 | } 41 | -------------------------------------------------------------------------------- /templates/app/server/api.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { addSession, getTasks, getTask } from './data'; 3 | 4 | const router = express.Router(); 5 | 6 | router.post('/sessions', (req, res) => { 7 | const { email, password } = req.body; 8 | if (!email || !password || email === 'error') { 9 | res.statusMessage = 'Invalid email or password'; 10 | res.status(401).end(); 11 | } else { 12 | const name = email.split('@')[0].replace(/\.|_/, ' '); // simulated 13 | const now = new Date(); 14 | const token = `token-${now.getTime()}`; // simulated 15 | const session = { email, name, token }; 16 | addSession(token, session); 17 | res.json(session); 18 | } 19 | }); 20 | 21 | router.get('/task', (req, res) => { 22 | getTasks(req.query).then(tasks => res.json(tasks)); 23 | }); 24 | 25 | router.get('/task/:id', (req, res) => { 26 | getTask(req.params.id).then((result) => { 27 | if (!result.task) { 28 | res.status(404).end(); 29 | } else { 30 | res.json(result); 31 | } 32 | }); 33 | }); 34 | 35 | router.delete('/sessions/*', (req, res) => { 36 | res.json(undefined); 37 | }); 38 | 39 | module.exports = router; 40 | -------------------------------------------------------------------------------- /templates/app/src/js/reducers/nav.js: -------------------------------------------------------------------------------- 1 | // (C) Copyright 2014-2015 Hewlett Packard Enterprise Development LP 2 | 3 | import { 4 | NAV_ACTIVATE, NAV_ENABLE, NAV_RESPONSIVE 5 | } from '../actions'; 6 | 7 | import { createReducer } from './utils'; 8 | 9 | const initialState = { 10 | active: true, // start with nav active 11 | enabled: true, // start with nav disabled 12 | responsive: 'multiple', 13 | items: [ 14 | { path: '/dashboard', label: 'Dashboard' }, 15 | { path: '/tasks', label: 'Tasks' } 16 | ] 17 | }; 18 | 19 | const handlers = { 20 | [NAV_ACTIVATE]: (_, action) => ( 21 | { active: action.active, activateOnMultiple: undefined } 22 | ), 23 | 24 | [NAV_ENABLE]: (_, action) => ( 25 | { enabled: action.enabled } 26 | ), 27 | 28 | [NAV_RESPONSIVE]: (state, action) => { 29 | const result = { responsive: action.responsive }; 30 | if (action.responsive === 'single' && state.active) { 31 | result.active = false; 32 | result.activateOnMultiple = true; 33 | } else if (action.responsive === 'multiple' && state.activateOnMultiple) { 34 | result.active = true; 35 | } 36 | return result; 37 | } 38 | }; 39 | 40 | export default createReducer(initialState, handlers); 41 | -------------------------------------------------------------------------------- /templates/docs/src/js/Main.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | import Anchor from 'grommet/components/Anchor'; 4 | import App from 'grommet/components/App'; 5 | import Box from 'grommet/components/Box'; 6 | import Header from 'grommet/components/Header'; 7 | import Title from 'grommet/components/Title'; 8 | 9 | import { browserHistory } from 'react-router'; 10 | 11 | const Main = props => ( 12 | 13 |
14 | 15 | <Anchor 16 | href='/' 17 | onClick={(event) => { 18 | event.preventDefault(); 19 | browserHistory.push('/'); 20 | }} 21 | label='<%= appTitle %>' /> 22 | 23 | { 26 | event.preventDefault(); 27 | browserHistory.push('/page1'); 28 | }} 29 | label='Page 1' 30 | /> 31 |
32 | 33 | {props.children} 34 | 35 |
36 | ); 37 | 38 | Main.propTypes = { 39 | children: PropTypes.any.isRequired 40 | }; 41 | 42 | export default Main; 43 | -------------------------------------------------------------------------------- /templates/app/src/js/screens/NotFound.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import Box from 'grommet/components/Box'; 5 | import Headline from 'grommet/components/Headline'; 6 | import Heading from 'grommet/components/Heading'; 7 | import Paragraph from 'grommet/components/Paragraph'; 8 | 9 | import { navEnable } from '../actions/nav'; 10 | import { pageLoaded } from './utils'; 11 | 12 | class NotFound extends Component { 13 | componentDidMount() { 14 | pageLoaded('Not Found'); 15 | this.props.dispatch(navEnable(false)); 16 | } 17 | 18 | componentWillUnmount() { 19 | this.props.dispatch(navEnable(true)); 20 | } 21 | 22 | render() { 23 | return ( 24 | 25 | 404 26 | Oops... 27 | 28 | It seems that you are in the wrong route. Please check your URL and 29 | try again. 30 | 31 | 32 | ); 33 | } 34 | } 35 | 36 | NotFound.propTypes = { 37 | dispatch: PropTypes.func.isRequired 38 | }; 39 | 40 | export default connect()(NotFound); 41 | -------------------------------------------------------------------------------- /templates/app/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "globals": { 8 | "it": true, 9 | "expect": true, 10 | "describe": true, 11 | "jest": true, 12 | "document": true, 13 | "test": true, 14 | "window": true, 15 | "fetch": true, 16 | "WebSocket": true, 17 | "alert": true 18 | }, 19 | "rules": { 20 | "react/jsx-filename-extension": 0, 21 | "react/forbid-prop-types": 0, 22 | "react/jsx-boolean-value": 0, 23 | "react/jsx-first-prop-new-line": 0, 24 | "react/jsx-closing-bracket-location": 0, 25 | "react/no-multi-comp": 0, 26 | "react/prefer-stateless-function": 0, 27 | "import/no-unresolved": 0, 28 | "import/no-extraneous-dependencies": 0, 29 | "import/extensions": 0, 30 | "import/first": 0, 31 | "import/no-dynamic-require": 0, 32 | "jsx-quotes": ["error", "prefer-single"], 33 | "no-alert": 0, 34 | "no-use-before-define": 0, 35 | "no-console": 0, 36 | "no-tabs": 0, 37 | "no-param-reassign": 0, 38 | "no-underscore-dangle": 0, 39 | "comma-dangle": 0, 40 | "no-return-assign": 0, 41 | "no-plusplus": 0, 42 | "global-require": 0, 43 | "object-property-newline": 0 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /templates/basic/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "globals": { 8 | "it": true, 9 | "expect": true, 10 | "describe": true, 11 | "jest": true, 12 | "document": true, 13 | "test": true, 14 | "window": true, 15 | "fetch": true, 16 | "WebSocket": true, 17 | "alert": true 18 | }, 19 | "rules": { 20 | "react/jsx-filename-extension": 0, 21 | "react/forbid-prop-types": 0, 22 | "react/jsx-boolean-value": 0, 23 | "react/jsx-first-prop-new-line": 0, 24 | "react/jsx-closing-bracket-location": 0, 25 | "react/no-multi-comp": 0, 26 | "react/prefer-stateless-function": 0, 27 | "import/no-unresolved": 0, 28 | "import/no-extraneous-dependencies": 0, 29 | "import/extensions": 0, 30 | "import/first": 0, 31 | "import/no-dynamic-require": 0, 32 | "jsx-quotes": ["error", "prefer-single"], 33 | "no-alert": 0, 34 | "no-use-before-define": 0, 35 | "no-console": 0, 36 | "no-tabs": 0, 37 | "no-param-reassign": 0, 38 | "no-underscore-dangle": 0, 39 | "comma-dangle": 0, 40 | "no-return-assign": 0, 41 | "no-plusplus": 0, 42 | "global-require": 0, 43 | "object-property-newline": 0 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /templates/docs/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "globals": { 8 | "it": true, 9 | "expect": true, 10 | "describe": true, 11 | "jest": true, 12 | "document": true, 13 | "test": true, 14 | "window": true, 15 | "fetch": true, 16 | "WebSocket": true, 17 | "alert": true 18 | }, 19 | "rules": { 20 | "react/jsx-filename-extension": 0, 21 | "react/forbid-prop-types": 0, 22 | "react/jsx-boolean-value": 0, 23 | "react/jsx-first-prop-new-line": 0, 24 | "react/jsx-closing-bracket-location": 0, 25 | "react/no-multi-comp": 0, 26 | "react/prefer-stateless-function": 0, 27 | "import/no-unresolved": 0, 28 | "import/no-extraneous-dependencies": 0, 29 | "import/extensions": 0, 30 | "import/first": 0, 31 | "import/no-dynamic-require": 0, 32 | "jsx-quotes": ["error", "prefer-single"], 33 | "no-alert": 0, 34 | "no-use-before-define": 0, 35 | "no-console": 0, 36 | "no-tabs": 0, 37 | "no-param-reassign": 0, 38 | "no-underscore-dangle": 0, 39 | "comma-dangle": 0, 40 | "no-return-assign": 0, 41 | "no-plusplus": 0, 42 | "global-require": 0, 43 | "object-property-newline": 0 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/commands/check.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NPM dependencies 3 | **/ 4 | import chalk from 'chalk'; 5 | import emoji from 'node-emoji'; 6 | import prettyHrtime from 'pretty-hrtime'; 7 | 8 | import { delimiter, errorHandler, runLinters, runTests } from './utils'; 9 | 10 | export default function (vorpal) { 11 | vorpal 12 | .command( 13 | 'check', 14 | 'Runs Javascript/SASS linters and execute tests for your project' 15 | ) 16 | .option( 17 | '-j, --jslint', 18 | `Whether to run only js lint.` 19 | ) 20 | .option( 21 | '-s, --scsslint', 22 | `Whether to run only scss lint.` 23 | ) 24 | .option( 25 | '-t, --test', 26 | `Whether to run only tests.` 27 | ) 28 | .action((args, cb) => { 29 | const timeId = process.hrtime(); 30 | 31 | runLinters(args.options) 32 | .then(() => runTests(args.options)) 33 | .then(() => { 34 | console.log( 35 | `${delimiter}: ${chalk.green('success')}` 36 | ); 37 | 38 | const t = process.hrtime(timeId); 39 | console.log(`${emoji.get('sparkles')} ${prettyHrtime(t)}`); 40 | cb(); 41 | }).catch(err => { 42 | errorHandler(err); 43 | process.exit(1); 44 | }); 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /templates/app/src/js/components/NavControl.js: -------------------------------------------------------------------------------- 1 | // (C) Copyright 2014-2015 Hewlett Packard Enterprise Development LP 2 | 3 | import React, { Component, PropTypes } from 'react'; 4 | import { connect } from 'react-redux'; 5 | 6 | import Box from 'grommet/components/Box'; 7 | import Button from 'grommet/components/Button'; 8 | import Title from 'grommet/components/Title'; 9 | import Logo from 'grommet/components/icons/Grommet'; 10 | 11 | import { navActivate } from '../actions/nav'; 12 | 13 | class NavControl extends Component { 14 | render() { 15 | const { name, nav: { active } } = this.props; 16 | 17 | let result; 18 | const title = {name || '<%= appTitle %>'}; 19 | if (!active) { 20 | result = ( 21 | 31 | ); 32 | } else { 33 | result = title; 34 | } 35 | return result; 36 | } 37 | } 38 | 39 | NavControl.defaultProps = { 40 | name: undefined, 41 | nav: { 42 | active: true, // start with nav active 43 | enabled: true, // start with nav disabled 44 | responsive: 'multiple' 45 | } 46 | }; 47 | 48 | NavControl.propTypes = { 49 | dispatch: PropTypes.func.isRequired, 50 | name: PropTypes.string, 51 | nav: PropTypes.object 52 | }; 53 | 54 | const select = state => ({ 55 | nav: state.nav 56 | }); 57 | 58 | export default connect(select)(NavControl); 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grommet-cli", 3 | "version": "5.1.2", 4 | "main": "src/grommet.js", 5 | "description": "Command line interface for Grommet.", 6 | "authors": [ 7 | "Alan Souza", 8 | "Bryan Jacquot", 9 | "Chris Carlozzi", 10 | "Eric Soderberg" 11 | ], 12 | "homepage": "http://grommet.io", 13 | "bugs": "https://github.com/grommet/grommet-cli/issues", 14 | "license": "Apache-2.0", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/grommet/grommet-cli.git" 18 | }, 19 | "bin": { 20 | "grommet": "./bin/grommet.js" 21 | }, 22 | "dependencies": { 23 | "babel-core": "^6.25.0", 24 | "chalk": "^2.0.1", 25 | "dotenv": "^4.0.0", 26 | "ejs": "^2.5.6", 27 | "eslint": "^4.3.0", 28 | "eslint-parallel": "^0.3.0", 29 | "fs-extra": "^4.0.0", 30 | "glob": "^7.1.2", 31 | "glob-to-regexp": "^0.3.0", 32 | "jest-cli": "^20.0.4", 33 | "jest-file-exists": "^19.0.0", 34 | "mkdirp": "^0.5.1", 35 | "node-emoji": "^1.8.1", 36 | "opener": "^1.4.3", 37 | "pretty-hrtime": "^1.0.3", 38 | "rimraf": "^2.6.1", 39 | "sass-lint": "^1.10.2", 40 | "shelljs": "^0.7.8", 41 | "tarball-extract": "^0.0.6", 42 | "vorpal": "https://github.com/alansouzati/vorpal/tarball/EXIT_ON_PARSE", 43 | "walk": "^2.3.9", 44 | "webpack": "^3.4.1", 45 | "webpack-dev-server": "^2.6.1", 46 | "yargs": "^8.0.2" 47 | }, 48 | "devDependencies": { 49 | "babel-cli": "^6.24.1", 50 | "babel-eslint": "^7.2.3", 51 | "babel-preset-es2015": "^6.24.1" 52 | }, 53 | "scripts": { 54 | "build": "node_modules/.bin/babel src --out-dir bin --copy-files --loose-mode" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /templates/app/src/js/components/SessionMenu.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import Menu from 'grommet/components/Menu'; 5 | import Anchor from 'grommet/components/Anchor'; 6 | import Box from 'grommet/components/Box'; 7 | import Heading from 'grommet/components/Heading'; 8 | import UserIcon from 'grommet/components/icons/base/User'; 9 | 10 | import { logout } from '../actions/session'; 11 | 12 | class SessionMenu extends Component { 13 | constructor() { 14 | super(); 15 | this._onLogout = this._onLogout.bind(this); 16 | } 17 | 18 | _onLogout(event) { 19 | const { session } = this.props; 20 | event.preventDefault(); 21 | this.props.dispatch(logout(session)); 22 | } 23 | 24 | render() { 25 | const { dropAlign, colorIndex, session: { name: userName } } = this.props; 26 | return ( 27 | } 29 | dropAlign={dropAlign} 30 | colorIndex={colorIndex} 31 | a11yTitle='Session' 32 | > 33 | 34 | {userName} 35 | 36 | 37 | 38 | ); 39 | } 40 | } 41 | 42 | SessionMenu.defaultProps = { 43 | colorIndex: undefined, 44 | dropAlign: undefined, 45 | }; 46 | 47 | SessionMenu.propTypes = { 48 | colorIndex: PropTypes.string, 49 | dispatch: PropTypes.func.isRequired, 50 | dropAlign: Menu.propTypes.dropAlign, 51 | session: PropTypes.object.isRequired 52 | }; 53 | 54 | const select = state => ({ 55 | session: state.session 56 | }); 57 | 58 | export default connect(select)(SessionMenu); 59 | -------------------------------------------------------------------------------- /templates/app/server/server.js: -------------------------------------------------------------------------------- 1 | import compression from 'compression'; 2 | import express from 'express'; 3 | import http from 'http'; 4 | import morgan from 'morgan'; 5 | import bodyParser from 'body-parser'; 6 | import cookieParser from 'cookie-parser'; 7 | import path from 'path'; 8 | import api from './api'; 9 | import { addNotifier, getTasks, getTask } from './data'; 10 | import Notifier from './notifier'; 11 | 12 | const PORT = process.env.PORT || 8102; 13 | 14 | const notifier = new Notifier(); 15 | 16 | addNotifier( 17 | 'task', 18 | (task) => { 19 | // this can be invoked multiple times as new requests happen 20 | notifier.test((request) => { 21 | // we should skip notify if the id of the task does not match the payload 22 | if (request.path === '/api/task/:id' && request.params.id !== task.id) { 23 | return false; 24 | } 25 | return true; 26 | }); 27 | } 28 | ); 29 | 30 | notifier.use('/api/task', () => getTasks()); 31 | notifier.use('/api/task/:id', param => ( 32 | getTask(param.id).then((result) => { 33 | if (!result.task) { 34 | return Promise.reject({ statusCode: 404, message: 'Not Found' }); 35 | } 36 | return Promise.resolve(result); 37 | }) 38 | )); 39 | 40 | const app = express() 41 | .use(compression()) 42 | .use(cookieParser()) 43 | .use(morgan('tiny')) 44 | .use(bodyParser.json()); 45 | 46 | // REST API 47 | app.use('/api', api); 48 | 49 | // UI 50 | app.use('/', express.static(path.join(__dirname, '/../dist'))); 51 | app.get('/*', (req, res) => { 52 | res.sendFile(path.resolve(path.join(__dirname, '/../dist/index.html'))); 53 | }); 54 | 55 | const server = http.createServer(app); 56 | server.listen(PORT); 57 | notifier.listen(server); 58 | 59 | console.log(`Server started at http://localhost:${PORT}`); 60 | -------------------------------------------------------------------------------- /templates/app/src/js/actions/session.js: -------------------------------------------------------------------------------- 1 | import { SESSION_LOAD, SESSION_LOGIN, SESSION_LOGOUT } from '../actions'; 2 | import { deleteSession, postSession } from '../api/session'; 3 | import { updateHeaders } from '../api/utils'; 4 | 5 | const localStorage = window.localStorage; 6 | 7 | export function initialize() { 8 | return (dispatch) => { 9 | const { email, name, token } = localStorage; 10 | if (email && token) { 11 | dispatch({ 12 | type: SESSION_LOAD, payload: { email, name, token } 13 | }); 14 | } else { 15 | window.location = '/login'; 16 | } 17 | }; 18 | } 19 | 20 | export function login(email, password, done) { 21 | return dispatch => ( 22 | postSession(email, password) 23 | .then((payload) => { 24 | updateHeaders({ Auth: payload.token }); 25 | dispatch({ type: SESSION_LOGIN, payload }); 26 | try { 27 | localStorage.email = payload.email; 28 | localStorage.name = payload.name; 29 | localStorage.token = payload.token; 30 | } catch (e) { 31 | alert( 32 | 'Unable to preserve session, probably due to being in private ' + 33 | 'browsing mode.' 34 | ); 35 | } 36 | done(); 37 | }) 38 | .catch(payload => dispatch({ 39 | type: SESSION_LOGIN, 40 | error: true, 41 | payload: { 42 | statusCode: payload.status, message: payload.statusText 43 | } 44 | })) 45 | ); 46 | } 47 | 48 | export function logout(session) { 49 | return (dispatch) => { 50 | dispatch({ type: SESSION_LOGOUT }); 51 | deleteSession(session); 52 | updateHeaders({ Auth: undefined }); 53 | try { 54 | localStorage.removeItem('email'); 55 | localStorage.removeItem('name'); 56 | localStorage.removeItem('token'); 57 | } catch (e) { 58 | // ignore 59 | } 60 | window.location.href = '/login'; // reload fully 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /templates/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= appName %>", 3 | "version": "0.1.0", 4 | "main": "src/js/index.js", 5 | "description": "<%= appDescription %>", 6 | "repository": "<%= appRepository %>", 7 | "license": "<%= appLicense %>", 8 | "scripts": { 9 | "test": "grommet check", 10 | "dev": "cross-env NODE_ENV=development grommet pack", 11 | "dist": "cross-env NODE_ENV=production grommet pack" 12 | }, 13 | "dependencies": { 14 | "grommet": "^1.3.4", 15 | "react": "^15.4.0", 16 | "react-dom": "^15.4.0" 17 | }, 18 | "devDependencies": { 19 | "babel-cli": "^6.22.2", 20 | "babel-core": "^6.5.2", 21 | "babel-eslint": "^7.1.1", 22 | "babel-jest": "^20.0.3", 23 | "babel-loader": "^7.1.1", 24 | "babel-preset-es2015": "^6.18.0", 25 | "babel-preset-react": "^6.16.0", 26 | "babel-preset-react-hmre": "^1.1.1", 27 | "copy-webpack-plugin": "^4.0.1", 28 | "cross-env": "^5.0.1", 29 | "eslint": "^4.3.0", 30 | "eslint-config-airbnb": "^15.1.0", 31 | "eslint-parallel": "^0.3.0", 32 | "eslint-plugin-import": "^2.2.0", 33 | "eslint-plugin-jsx-a11y": "^5.1.1", 34 | "eslint-plugin-react": "^7.1.0", 35 | "file-loader": "^0.11.2", 36 | "grommet-cli": "^5.0.0", 37 | "jest-cli": "^20.0.4", 38 | "json-loader": "^0.5.4", 39 | "node-sass": "^4.9.0", 40 | "pre-commit": "^1.2.2", 41 | "react-dev-utils": "^0.4.2", 42 | "react-test-renderer": "^15.4.1", 43 | "sass-lint": "^1.10.2", 44 | "sass-loader": "^6.0.3", 45 | "webpack": "^3.4.1" 46 | }, 47 | "jest": { 48 | "collectCoverage": true, 49 | "coverageReporters": [ 50 | "lcov" 51 | ], 52 | "collectCoverageFrom": [ 53 | "src/**/*.{js}" 54 | ], 55 | "modulePathIgnorePatterns": [ 56 | "/dist/", 57 | "/templates/" 58 | ], 59 | "testPathIgnorePatterns": [ 60 | "[/\\\\](dist|templates|node_modules)[/\\\\]" 61 | ] 62 | }, 63 | "pre-commit": [ 64 | "test" 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /templates/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= appName %>", 3 | "version": "0.1.0", 4 | "main": "src/js/index.js", 5 | "description": "<%= appDescription %>", 6 | "repository": "<%= appRepository %>", 7 | "license": "<%= appLicense %>", 8 | "scripts": { 9 | "test": "grommet check", 10 | "dev": "cross-env NODE_ENV=development grommet pack", 11 | "dist": "cross-env NODE_ENV=production grommet pack" 12 | }, 13 | "dependencies": { 14 | "grommet": "^1.3.4", 15 | "react": "^15.4.0", 16 | "react-dom": "^15.4.0", 17 | "react-router": "^3.0.0" 18 | }, 19 | "devDependencies": { 20 | "babel-cli": "^6.22.2", 21 | "babel-core": "^6.5.2", 22 | "babel-eslint": "^7.1.1", 23 | "babel-jest": "^20.0.3", 24 | "babel-loader": "^7.1.1", 25 | "babel-plugin-transform-object-rest-spread": "^6.19.0", 26 | "babel-preset-es2015": "^6.18.0", 27 | "babel-preset-react": "^6.16.0", 28 | "babel-preset-react-hmre": "^1.1.1", 29 | "babel-register": "^6.18.0", 30 | "copy-webpack-plugin": "^4.0.1", 31 | "cross-env": "^5.0.1", 32 | "ejs-compiled-loader": "^2.1.1", 33 | "eslint": "^4.3.0", 34 | "eslint-config-airbnb": "^15.1.0", 35 | "eslint-parallel": "^0.3.0", 36 | "eslint-plugin-import": "^2.2.0", 37 | "eslint-plugin-jsx-a11y": "^5.1.1", 38 | "eslint-plugin-react": "^7.1.0", 39 | "file-loader": "^0.11.2", 40 | "grommet-cli": "^5.0.0", 41 | "jest-cli": "^20.0.4", 42 | "json-loader": "^0.5.4", 43 | "node-sass": "^4.9.0", 44 | "nodemon": "^1.11.0", 45 | "pre-commit": "^1.2.2", 46 | "react-dev-utils": "^0.4.2", 47 | "react-router-to-array": "^0.1.1", 48 | "react-test-renderer": "^15.4.1", 49 | "static-site-generator-webpack-plugin": "^2.1.0", 50 | "sass-lint": "^1.10.2", 51 | "sass-loader": "^6.0.3", 52 | "webpack": "^3.4.1" 53 | }, 54 | "jest": { 55 | "collectCoverage": true, 56 | "coverageReporters": [ 57 | "lcov" 58 | ], 59 | "collectCoverageFrom": [ 60 | "src/**/*.{js}" 61 | ], 62 | "modulePathIgnorePatterns": [ 63 | "/dist/", 64 | "/templates/" 65 | ], 66 | "testPathIgnorePatterns": [ 67 | "[/\\\\](dist|templates|node_modules)[/\\\\]" 68 | ] 69 | }, 70 | "pre-commit": [ 71 | "test" 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /templates/app/src/js/components/Main.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; 4 | 5 | import App from 'grommet/components/App'; 6 | import Split from 'grommet/components/Split'; 7 | 8 | import NavSidebar from './NavSidebar'; 9 | import { navResponsive } from '../actions/nav'; 10 | 11 | import Login from '../screens/Login'; 12 | import Dashboard from '../screens/Dashboard'; 13 | import Tasks from '../screens/Tasks'; 14 | import Task from '../screens/Task'; 15 | import NotFound from '../screens/NotFound'; 16 | 17 | class Main extends Component { 18 | constructor() { 19 | super(); 20 | this._onResponsive = this._onResponsive.bind(this); 21 | } 22 | 23 | _onResponsive(responsive) { 24 | this.props.dispatch(navResponsive(responsive)); 25 | } 26 | 27 | render() { 28 | const { 29 | nav: { active: navActive, enabled: navEnabled, responsive } 30 | } = this.props; 31 | const includeNav = (navActive && navEnabled); 32 | let nav; 33 | if (includeNav) { 34 | nav = ; 35 | } 36 | const priority = (includeNav && responsive === 'single' ? 'left' : 'right'); 37 | 38 | return ( 39 | 40 | 41 | 46 | {nav} 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | ); 59 | } 60 | } 61 | 62 | Main.defaultProps = { 63 | nav: { 64 | active: true, // start with nav active 65 | enabled: true, // start with nav disabled 66 | responsive: 'multiple' 67 | } 68 | }; 69 | 70 | Main.propTypes = { 71 | dispatch: PropTypes.func.isRequired, 72 | nav: PropTypes.shape({ 73 | active: PropTypes.bool, 74 | enabled: PropTypes.bool, 75 | responsive: PropTypes.string 76 | }) 77 | }; 78 | 79 | const select = state => ({ 80 | nav: state.nav 81 | }); 82 | 83 | export default connect(select)(Main); 84 | -------------------------------------------------------------------------------- /templates/app/src/js/components/NavSidebar.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import Sidebar from 'grommet/components/Sidebar'; 5 | import Header from 'grommet/components/Header'; 6 | import Footer from 'grommet/components/Footer'; 7 | import Title from 'grommet/components/Title'; 8 | import Menu from 'grommet/components/Menu'; 9 | import Button from 'grommet/components/Button'; 10 | import CloseIcon from 'grommet/components/icons/base/Close'; 11 | import Logo from 'grommet/components/icons/Grommet'; 12 | import Anchor from 'grommet/components/Anchor'; 13 | 14 | import SessionMenu from './SessionMenu'; 15 | import { navActivate } from '../actions/nav'; 16 | 17 | class NavSidebar extends Component { 18 | constructor() { 19 | super(); 20 | this._onClose = this._onClose.bind(this); 21 | } 22 | 23 | _onClose() { 24 | this.props.dispatch(navActivate(false)); 25 | } 26 | 27 | render() { 28 | const { nav: { items } } = this.props; 29 | 30 | const links = items.map(page => ( 31 | 32 | )); 33 | 34 | return ( 35 | 36 |
37 | 38 | <Logo /> 39 | <span><%= appTitle %></span> 40 | 41 |
48 | 49 | {links} 50 | 51 |
52 | 53 |
54 |
55 | ); 56 | } 57 | } 58 | 59 | NavSidebar.defaultProps = { 60 | nav: { 61 | active: true, // start with nav active 62 | enabled: true, // start with nav disabled 63 | responsive: 'multiple' 64 | } 65 | }; 66 | 67 | NavSidebar.propTypes = { 68 | dispatch: PropTypes.func.isRequired, 69 | nav: PropTypes.shape({ 70 | items: PropTypes.arrayOf(PropTypes.shape({ 71 | path: PropTypes.string, 72 | label: PropTypes.string 73 | })) 74 | }) 75 | }; 76 | 77 | const select = state => ({ 78 | nav: state.nav 79 | }); 80 | 81 | export default connect(select)(NavSidebar); 82 | -------------------------------------------------------------------------------- /templates/app/server/data.js: -------------------------------------------------------------------------------- 1 | const _sessions = {}; 2 | const _notifiers = { 3 | task: [] 4 | }; 5 | 6 | export const tasks = [ 7 | { 8 | id: 'task-1', 9 | name: 'Initializing instance', 10 | percentComplete: 0, 11 | status: 'Waiting' 12 | }, 13 | { 14 | id: 'task-2', 15 | name: 'Adding components', 16 | percentComplete: 0, 17 | status: 'Waiting' 18 | }, 19 | { 20 | id: 'task-3', 21 | name: 'Testing infrastructure', 22 | percentComplete: 0, 23 | status: 'Waiting' 24 | }, 25 | { 26 | id: 'task-4', 27 | name: 'Removing instance', 28 | percentComplete: 0, 29 | status: 'Waiting' 30 | } 31 | ]; 32 | 33 | const increments = [5, 10, 20, 25]; 34 | 35 | setInterval( 36 | () => { 37 | const task = tasks[ 38 | Math.floor(Math.random() * tasks.length) 39 | ]; 40 | 41 | if (!task.percentComplete) { 42 | task.status = 'Running'; 43 | } 44 | 45 | _notifiers.task.forEach(notifier => notifier(task)); 46 | }, 47 | 2000 48 | ); 49 | 50 | setInterval( 51 | () => { 52 | tasks.forEach((task) => { 53 | if (task.status === 'Running') { 54 | if (task.percentComplete < 100) { 55 | task.percentComplete = Math.min(100, task.percentComplete + 56 | increments[ 57 | Math.floor(Math.random() * increments.length) 58 | ] 59 | ); 60 | } else { 61 | task.percentComplete = 0; 62 | task.status = 'Waiting'; 63 | } 64 | _notifiers.task.forEach(notifier => notifier(task)); 65 | } 66 | }); 67 | }, 68 | 1000 69 | ); 70 | 71 | export function addSession(token, data) { 72 | _sessions[token] = data; 73 | } 74 | 75 | export function getSession(token) { 76 | return _sessions[token]; 77 | } 78 | 79 | export function addNotifier(type, cb) { 80 | _notifiers[type].push(cb); 81 | } 82 | 83 | export function getTasks(filters) { 84 | if (filters) { 85 | return Promise.resolve({ 86 | tasks: tasks.filter(task => 87 | Object.keys(filters).some(filter => task[filter] === filters[filter]) 88 | ) 89 | }); 90 | } 91 | return Promise.resolve({ tasks }); 92 | } 93 | 94 | export function getTask(id) { 95 | let task; 96 | tasks.some((t) => { 97 | if (t.id === id) { 98 | task = t; 99 | return true; 100 | } 101 | return false; 102 | }); 103 | return Promise.resolve({ task }); 104 | } 105 | 106 | export default { addNotifier, addSession, getSession, getTask, getTasks }; 107 | -------------------------------------------------------------------------------- /templates/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= appName %>", 3 | "version": "0.1.0", 4 | "main": "src/js/index.js", 5 | "description": "<%= appDescription %>", 6 | "repository": "<%= appRepository %>", 7 | "license": "<%= appLicense %>", 8 | "scripts": { 9 | "test": "grommet check", 10 | "dev-server": "nodemon ./server/dev", 11 | "dev": "cross-env NODE_ENV=development grommet pack", 12 | "dist": "cross-env NODE_ENV=production grommet pack", 13 | "dist-server": "babel -d ./dist-server ./server -s", 14 | "start": "npm run dist-server && npm run dist && node ./dist-server/server.js" 15 | }, 16 | "dependencies": { 17 | "body-parser": "^1.15.2", 18 | "compression": "^1.6.2", 19 | "cookie-parser": "^1.4.3", 20 | "express": "^4.14.0", 21 | "grommet": "^1.3.4", 22 | "morgan": "^1.7.0", 23 | "path-to-regexp": "^1.7.0", 24 | "react": "^15.4.0", 25 | "react-dom": "^15.4.0", 26 | "react-intl": "^2.1.5", 27 | "react-redux": "^4.4.5", 28 | "react-router-dom": "^4.0.0", 29 | "redux": "^3.6.0", 30 | "redux-thunk": "^2.1.0", 31 | "ws": "^1.1.1" 32 | }, 33 | "devDependencies": { 34 | "babel-cli": "^6.22.2", 35 | "babel-core": "^6.5.2", 36 | "babel-eslint": "^7.1.1", 37 | "babel-jest": "^20.0.3", 38 | "babel-loader": "^7.1.1", 39 | "babel-plugin-transform-object-rest-spread": "^6.19.0", 40 | "babel-preset-es2015": "^6.18.0", 41 | "babel-preset-react": "^6.16.0", 42 | "babel-preset-react-hmre": "^1.1.1", 43 | "babel-register": "^6.18.0", 44 | "copy-webpack-plugin": "^4.0.1", 45 | "cross-env": "^5.0.1", 46 | "es6-promise": "^4.0.5", 47 | "eslint": "^4.3.0", 48 | "eslint-config-airbnb": "^15.1.0", 49 | "eslint-parallel": "^0.3.0", 50 | "eslint-plugin-import": "^2.2.0", 51 | "eslint-plugin-jsx-a11y": "^5.1.1", 52 | "eslint-plugin-react": "^7.1.0", 53 | "file-loader": "^0.11.2", 54 | "grommet-cli": "^5.0.0", 55 | "jest-cli": "^20.0.4", 56 | "json-loader": "^0.5.4", 57 | "node-sass": "^4.9.0", 58 | "nodemon": "^1.11.0", 59 | "pre-commit": "^1.2.2", 60 | "react-dev-utils": "^0.4.2", 61 | "react-test-renderer": "^15.4.1", 62 | "sass-lint": "^1.10.2", 63 | "sass-loader": "^6.0.3", 64 | "webpack": "^3.4.1" 65 | }, 66 | "jest": { 67 | "collectCoverage": true, 68 | "coverageReporters": [ 69 | "lcov" 70 | ], 71 | "collectCoverageFrom": [ 72 | "src/**/*.{js}" 73 | ], 74 | "modulePathIgnorePatterns": [ 75 | "/dist/", 76 | "/templates/" 77 | ], 78 | "testPathIgnorePatterns": [ 79 | "[/\\\\](dist|templates|node_modules)[/\\\\]" 80 | ] 81 | }, 82 | "pre-commit": [ 83 | "test" 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /templates/app/.sass-lint.yml: -------------------------------------------------------------------------------- 1 | files: 2 | include: './src/**/*.s+(a|c)ss' 3 | ignore: 4 | - 'node_modules/**/*' 5 | options: 6 | formatter: stylish 7 | merge-default-rules: false 8 | rules: 9 | border-zero: 10 | - 1 11 | - convention: none 12 | brace-style: 13 | - 1 14 | - allow-single-line: true 15 | class-name-format: 16 | - 1 17 | - convention: hyphenatedbem 18 | clean-import-paths: 19 | - 1 20 | - filename-extension: false 21 | leading-underscore: false 22 | empty-line-between-blocks: 23 | - 1 24 | - ignore-single-line-rulesets: true 25 | extends-before-declarations: 0 26 | extends-before-mixins: 0 27 | final-newline: 28 | - 1 29 | - include: true 30 | force-attribute-nesting: 0 31 | force-element-nesting: 0 32 | force-pseudo-nesting: 0 33 | function-name-format: 34 | - 1 35 | - allow-leading-underscore: true 36 | convention: hyphenatedlowercase 37 | hex-length: 38 | - 0 39 | - style: short 40 | hex-notation: 41 | - 0 42 | - style: lowercase 43 | id-name-format: 44 | - 1 45 | - convention: hyphenatedbem 46 | indentation: 47 | - 0 48 | - size: 2 49 | leading-zero: 50 | - 1 51 | - include: true 52 | mixin-name-format: 53 | - 1 54 | - allow-leading-underscore: true 55 | convention: hyphenatedlowercase 56 | mixins-before-declarations: 0 57 | nesting-depth: 58 | - 0 59 | - max-depth: 6 60 | no-color-literals: 0 61 | no-css-comments: 1 62 | no-debug: 1 63 | no-duplicate-properties: 1 64 | no-empty-rulesets: 1 65 | no-ids: 0 66 | no-important: 1 67 | no-invalid-hex: 1 68 | no-mergeable-selectors: 1 69 | no-misspelled-properties: 70 | - 1 71 | - extra-properties: 72 | - animate 73 | - overflow-scrolling 74 | no-qualifying-elements: 75 | - 1 76 | - allow-element-with-attribute: true 77 | allow-element-with-class: true 78 | allow-element-with-id: false 79 | no-trailing-zero: 1 80 | no-url-protocols: 0 81 | no-vendor-prefixes: 0 82 | placeholder-in-extend: 1 83 | placeholder-name-format: 84 | - 1 85 | - convention: hyphenatedbem 86 | property-sort-order: 0 87 | quotes: 88 | - 0 89 | - style: single 90 | shorthand-values: 1 91 | single-line-per-selector: 1 92 | space-after-bang: 93 | - 1 94 | - include: false 95 | space-after-colon: 1 96 | space-after-comma: 1 97 | space-before-bang: 98 | - 1 99 | - include: true 100 | space-before-brace: 101 | - 1 102 | - include: true 103 | space-before-colon: 1 104 | space-between-parens: 105 | - 1 106 | - include: false 107 | trailing-semicolon: 2 108 | url-quotes: 0 109 | variable-for-property: 110 | - 0 111 | - properties: [] 112 | variable-name-format: 113 | - 1 114 | - allow-leading-underscore: true 115 | convention: hyphenatedlowercase 116 | zero-unit: 0 117 | -------------------------------------------------------------------------------- /templates/docs/.sass-lint.yml: -------------------------------------------------------------------------------- 1 | files: 2 | include: './src/**/*.s+(a|c)ss' 3 | ignore: 4 | - 'node_modules/**/*' 5 | options: 6 | formatter: stylish 7 | merge-default-rules: false 8 | rules: 9 | border-zero: 10 | - 1 11 | - convention: none 12 | brace-style: 13 | - 1 14 | - allow-single-line: true 15 | class-name-format: 16 | - 1 17 | - convention: hyphenatedbem 18 | clean-import-paths: 19 | - 1 20 | - filename-extension: false 21 | leading-underscore: false 22 | empty-line-between-blocks: 23 | - 1 24 | - ignore-single-line-rulesets: true 25 | extends-before-declarations: 0 26 | extends-before-mixins: 0 27 | final-newline: 28 | - 1 29 | - include: true 30 | force-attribute-nesting: 0 31 | force-element-nesting: 0 32 | force-pseudo-nesting: 0 33 | function-name-format: 34 | - 1 35 | - allow-leading-underscore: true 36 | convention: hyphenatedlowercase 37 | hex-length: 38 | - 0 39 | - style: short 40 | hex-notation: 41 | - 0 42 | - style: lowercase 43 | id-name-format: 44 | - 1 45 | - convention: hyphenatedbem 46 | indentation: 47 | - 0 48 | - size: 2 49 | leading-zero: 50 | - 1 51 | - include: true 52 | mixin-name-format: 53 | - 1 54 | - allow-leading-underscore: true 55 | convention: hyphenatedlowercase 56 | mixins-before-declarations: 0 57 | nesting-depth: 58 | - 0 59 | - max-depth: 6 60 | no-color-literals: 0 61 | no-css-comments: 1 62 | no-debug: 1 63 | no-duplicate-properties: 1 64 | no-empty-rulesets: 1 65 | no-ids: 0 66 | no-important: 1 67 | no-invalid-hex: 1 68 | no-mergeable-selectors: 1 69 | no-misspelled-properties: 70 | - 1 71 | - extra-properties: 72 | - animate 73 | - overflow-scrolling 74 | no-qualifying-elements: 75 | - 1 76 | - allow-element-with-attribute: true 77 | allow-element-with-class: true 78 | allow-element-with-id: false 79 | no-trailing-zero: 1 80 | no-url-protocols: 0 81 | no-vendor-prefixes: 0 82 | placeholder-in-extend: 1 83 | placeholder-name-format: 84 | - 1 85 | - convention: hyphenatedbem 86 | property-sort-order: 0 87 | quotes: 88 | - 0 89 | - style: single 90 | shorthand-values: 1 91 | single-line-per-selector: 1 92 | space-after-bang: 93 | - 1 94 | - include: false 95 | space-after-colon: 1 96 | space-after-comma: 1 97 | space-before-bang: 98 | - 1 99 | - include: true 100 | space-before-brace: 101 | - 1 102 | - include: true 103 | space-before-colon: 1 104 | space-between-parens: 105 | - 1 106 | - include: false 107 | trailing-semicolon: 2 108 | url-quotes: 0 109 | variable-for-property: 110 | - 0 111 | - properties: [] 112 | variable-name-format: 113 | - 1 114 | - allow-leading-underscore: true 115 | convention: hyphenatedlowercase 116 | zero-unit: 0 117 | -------------------------------------------------------------------------------- /templates/basic/.sass-lint.yml: -------------------------------------------------------------------------------- 1 | files: 2 | include: './src/**/*.s+(a|c)ss' 3 | ignore: 4 | - 'node_modules/**/*' 5 | options: 6 | formatter: stylish 7 | merge-default-rules: false 8 | rules: 9 | border-zero: 10 | - 1 11 | - convention: none 12 | brace-style: 13 | - 1 14 | - allow-single-line: true 15 | class-name-format: 16 | - 1 17 | - convention: hyphenatedbem 18 | clean-import-paths: 19 | - 1 20 | - filename-extension: false 21 | leading-underscore: false 22 | empty-line-between-blocks: 23 | - 1 24 | - ignore-single-line-rulesets: true 25 | extends-before-declarations: 0 26 | extends-before-mixins: 0 27 | final-newline: 28 | - 1 29 | - include: true 30 | force-attribute-nesting: 0 31 | force-element-nesting: 0 32 | force-pseudo-nesting: 0 33 | function-name-format: 34 | - 1 35 | - allow-leading-underscore: true 36 | convention: hyphenatedlowercase 37 | hex-length: 38 | - 0 39 | - style: short 40 | hex-notation: 41 | - 0 42 | - style: lowercase 43 | id-name-format: 44 | - 1 45 | - convention: hyphenatedbem 46 | indentation: 47 | - 0 48 | - size: 2 49 | leading-zero: 50 | - 1 51 | - include: true 52 | mixin-name-format: 53 | - 1 54 | - allow-leading-underscore: true 55 | convention: hyphenatedlowercase 56 | mixins-before-declarations: 0 57 | nesting-depth: 58 | - 0 59 | - max-depth: 6 60 | no-color-literals: 0 61 | no-css-comments: 1 62 | no-debug: 1 63 | no-duplicate-properties: 1 64 | no-empty-rulesets: 1 65 | no-ids: 0 66 | no-important: 1 67 | no-invalid-hex: 1 68 | no-mergeable-selectors: 1 69 | no-misspelled-properties: 70 | - 1 71 | - extra-properties: 72 | - animate 73 | - overflow-scrolling 74 | no-qualifying-elements: 75 | - 1 76 | - allow-element-with-attribute: true 77 | allow-element-with-class: true 78 | allow-element-with-id: false 79 | no-trailing-zero: 1 80 | no-url-protocols: 0 81 | no-vendor-prefixes: 0 82 | placeholder-in-extend: 1 83 | placeholder-name-format: 84 | - 1 85 | - convention: hyphenatedbem 86 | property-sort-order: 0 87 | quotes: 88 | - 0 89 | - style: single 90 | shorthand-values: 1 91 | single-line-per-selector: 1 92 | space-after-bang: 93 | - 1 94 | - include: false 95 | space-after-colon: 1 96 | space-after-comma: 1 97 | space-before-bang: 98 | - 1 99 | - include: true 100 | space-before-brace: 101 | - 1 102 | - include: true 103 | space-before-colon: 1 104 | space-between-parens: 105 | - 1 106 | - include: false 107 | trailing-semicolon: 2 108 | url-quotes: 0 109 | variable-for-property: 110 | - 0 111 | - properties: [] 112 | variable-name-format: 113 | - 1 114 | - allow-leading-underscore: true 115 | convention: hyphenatedlowercase 116 | zero-unit: 0 117 | -------------------------------------------------------------------------------- /templates/basic/webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import CopyWebpackPlugin from 'copy-webpack-plugin'; 4 | 5 | const env = process.env.NODE_ENV || 'production'; 6 | 7 | let plugins = [ 8 | new CopyWebpackPlugin([{ from: './public' }]), 9 | new webpack.DefinePlugin({ 10 | 'process.env': { 11 | NODE_ENV: JSON.stringify(env) 12 | } 13 | }) 14 | ]; 15 | 16 | const loaderOptionsConfig = { 17 | options: { 18 | sassLoader: { 19 | includePaths: [ 20 | './node_modules' 21 | ] 22 | } 23 | } 24 | }; 25 | 26 | const devConfig = {}; 27 | if (env === 'production') { 28 | loaderOptionsConfig.minimize = true; 29 | plugins.push( 30 | new webpack.optimize.UglifyJsPlugin({ 31 | compress: { 32 | warnings: false, 33 | screw_ie8: true, 34 | conditionals: true, 35 | unused: true, 36 | comparisons: true, 37 | sequences: true, 38 | dead_code: true, 39 | evaluate: true, 40 | if_return: true, 41 | join_vars: true, 42 | }, 43 | mangle: { 44 | screw_ie8: true 45 | }, 46 | output: { 47 | comments: false, 48 | screw_ie8: true 49 | } 50 | }) 51 | ); 52 | } else { 53 | plugins = plugins.concat([ 54 | new webpack.HotModuleReplacementPlugin() 55 | ]); 56 | devConfig.devtool = 'cheap-module-source-map'; 57 | devConfig.entry = [ 58 | require.resolve('react-dev-utils/webpackHotDevClient'), 59 | './src/js/index.js' 60 | ]; 61 | devConfig.devServer = { 62 | compress: true, 63 | clientLogLevel: 'none', 64 | contentBase: path.resolve('./dist'), 65 | publicPath: '/', 66 | quiet: true, 67 | hot: true, 68 | watchOptions: { 69 | ignored: /node_modules/ 70 | }, 71 | historyApiFallback: true 72 | }; 73 | } 74 | 75 | plugins.push(new webpack.LoaderOptionsPlugin(loaderOptionsConfig)); 76 | 77 | export default Object.assign({ 78 | entry: './src/js/index.js', 79 | output: { 80 | path: path.resolve('./dist'), 81 | filename: 'index.js', 82 | publicPath: '/' 83 | }, 84 | resolve: { 85 | extensions: ['.js', '.scss', '.css', '.json'] 86 | }, 87 | plugins, 88 | node: { 89 | fs: 'empty', 90 | net: 'empty', 91 | tls: 'empty' 92 | }, 93 | module: { 94 | rules: [ 95 | { 96 | test: /\.js/, 97 | exclude: /node_modules/, 98 | loader: 'babel-loader' 99 | }, 100 | { 101 | test: /\.scss$/, 102 | use: [ 103 | { loader: 'file-loader', options: { name: '[name].css' } }, 104 | { loader: 'sass-loader', 105 | options: { 106 | outputStyle: 'compressed', 107 | includePaths: [ 108 | './node_modules' 109 | ] 110 | } 111 | } 112 | ] 113 | } 114 | ] 115 | } 116 | }, devConfig); 117 | -------------------------------------------------------------------------------- /templates/app/webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import CopyWebpackPlugin from 'copy-webpack-plugin'; 4 | 5 | const env = process.env.NODE_ENV || 'production'; 6 | 7 | let plugins = [ 8 | new CopyWebpackPlugin([{ from: './public' }]), 9 | new webpack.DefinePlugin({ 10 | 'process.env': { 11 | NODE_ENV: JSON.stringify(env) 12 | } 13 | }) 14 | ]; 15 | 16 | const loaderOptionsConfig = { 17 | options: { 18 | sassLoader: { 19 | includePaths: [ 20 | './node_modules' 21 | ] 22 | } 23 | } 24 | }; 25 | 26 | const devConfig = {}; 27 | if (env === 'production') { 28 | loaderOptionsConfig.minimize = true; 29 | plugins.push( 30 | new webpack.optimize.UglifyJsPlugin({ 31 | compress: { 32 | warnings: false, 33 | screw_ie8: true, 34 | conditionals: true, 35 | unused: true, 36 | comparisons: true, 37 | sequences: true, 38 | dead_code: true, 39 | evaluate: true, 40 | if_return: true, 41 | join_vars: true, 42 | }, 43 | mangle: { 44 | screw_ie8: true 45 | }, 46 | output: { 47 | comments: false, 48 | screw_ie8: true 49 | } 50 | }) 51 | ); 52 | } else { 53 | plugins = plugins.concat([ 54 | new webpack.HotModuleReplacementPlugin() 55 | ]); 56 | devConfig.devtool = 'cheap-module-source-map'; 57 | devConfig.entry = [ 58 | require.resolve('react-dev-utils/webpackHotDevClient'), 59 | './src/js/index.js' 60 | ]; 61 | devConfig.devServer = { 62 | compress: true, 63 | clientLogLevel: 'none', 64 | contentBase: path.resolve('./dist'), 65 | publicPath: '/', 66 | quiet: true, 67 | hot: true, 68 | watchOptions: { 69 | ignored: /node_modules/ 70 | }, 71 | historyApiFallback: true, 72 | proxy: { 73 | '/api/*': 'http://localhost:8102' 74 | } 75 | }; 76 | } 77 | 78 | plugins.push(new webpack.LoaderOptionsPlugin(loaderOptionsConfig)); 79 | 80 | export default Object.assign({ 81 | entry: './src/js/index.js', 82 | output: { 83 | path: path.resolve('./dist'), 84 | filename: 'index.js', 85 | publicPath: '/' 86 | }, 87 | resolve: { 88 | extensions: ['.js', '.scss', '.css', '.json'] 89 | }, 90 | plugins, 91 | node: { 92 | fs: 'empty', 93 | net: 'empty', 94 | tls: 'empty' 95 | }, 96 | module: { 97 | rules: [ 98 | { 99 | test: /\.js/, 100 | exclude: /node_modules/, 101 | loader: 'babel-loader' 102 | }, 103 | { 104 | test: /\.scss$/, 105 | use: [ 106 | { loader: 'file-loader', options: { name: '[name].css' } }, 107 | { loader: 'sass-loader', 108 | options: { 109 | outputStyle: 'compressed', 110 | includePaths: [ 111 | './node_modules' 112 | ] 113 | } 114 | } 115 | ] 116 | } 117 | ] 118 | } 119 | }, devConfig); 120 | -------------------------------------------------------------------------------- /templates/app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Grommet 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 54 | 55 | 56 |
57 | 62 | 85 |
86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /templates/basic/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Grommet 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 54 | 55 | 56 |
57 | 62 | 85 |
86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /templates/docs/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Grommet 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 54 | 55 | 56 |
57 | 62 | 85 |
86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /templates/app/src/js/screens/Login.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import Split from 'grommet/components/Split'; 5 | import Sidebar from 'grommet/components/Sidebar'; 6 | import LoginForm from 'grommet/components/LoginForm'; 7 | import Article from 'grommet/components/Article'; 8 | import Section from 'grommet/components/Section'; 9 | import Heading from 'grommet/components/Heading'; 10 | import Paragraph from 'grommet/components/Paragraph'; 11 | import Footer from 'grommet/components/Footer'; 12 | import Logo from 'grommet/components/icons/Grommet'; 13 | 14 | import { login } from '../actions/session'; 15 | import { navEnable } from '../actions/nav'; 16 | import { pageLoaded } from './utils'; 17 | 18 | class Login extends Component { 19 | constructor() { 20 | super(); 21 | this._onSubmit = this._onSubmit.bind(this); 22 | } 23 | 24 | componentDidMount() { 25 | pageLoaded('Login'); 26 | this.props.dispatch(navEnable(false)); 27 | } 28 | 29 | componentWillUnmount() { 30 | this.props.dispatch(navEnable(true)); 31 | } 32 | 33 | _onSubmit(fields) { 34 | const { dispatch } = this.props; 35 | const { router } = this.context; 36 | dispatch(login(fields.username, fields.password, () => ( 37 | router.history.push('/dashboard') 38 | ))); 39 | } 40 | 41 | render() { 42 | const { session: { error } } = this.props; 43 | 44 | return ( 45 | 46 | 47 |
48 |
56 | <%= appTitle %> 57 | 58 | Development with Grommet is cool. 59 | 60 |
61 |
62 | 63 | 64 | 65 | } 68 | title='<%= appTitle %>' 69 | onSubmit={this._onSubmit} 70 | errors={[error]} 71 | usernameType='text' 72 | /> 73 |
78 | © 2017 Grommet Labs 79 |
80 |
81 | 82 |
83 | ); 84 | } 85 | } 86 | 87 | Login.defaultProps = { 88 | session: { 89 | error: undefined 90 | } 91 | }; 92 | 93 | Login.propTypes = { 94 | dispatch: PropTypes.func.isRequired, 95 | session: PropTypes.shape({ 96 | error: PropTypes.string 97 | }) 98 | }; 99 | 100 | Login.contextTypes = { 101 | router: PropTypes.object.isRequired, 102 | }; 103 | 104 | const select = state => ({ 105 | session: state.session 106 | }); 107 | 108 | export default connect(select)(Login); 109 | -------------------------------------------------------------------------------- /templates/app/src/js/screens/Task.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import Anchor from 'grommet/components/Anchor'; 5 | import Article from 'grommet/components/Article'; 6 | import Box from 'grommet/components/Box'; 7 | import Header from 'grommet/components/Header'; 8 | import Heading from 'grommet/components/Heading'; 9 | import Label from 'grommet/components/Label'; 10 | import Meter from 'grommet/components/Meter'; 11 | import Notification from 'grommet/components/Notification'; 12 | import Value from 'grommet/components/Value'; 13 | import Spinning from 'grommet/components/icons/Spinning'; 14 | import LinkPrevious from 'grommet/components/icons/base/LinkPrevious'; 15 | 16 | import { 17 | loadTask, unloadTask 18 | } from '../actions/tasks'; 19 | 20 | import { pageLoaded } from './utils'; 21 | 22 | class Task extends Component { 23 | componentDidMount() { 24 | const { match: { params }, dispatch } = this.props; 25 | pageLoaded('Task'); 26 | dispatch(loadTask(params.id)); 27 | } 28 | 29 | componentWillUnmount() { 30 | const { match: { params }, dispatch } = this.props; 31 | dispatch(unloadTask(params.id)); 32 | } 33 | 34 | render() { 35 | const { error, task } = this.props; 36 | 37 | let errorNode; 38 | let taskNode; 39 | if (error) { 40 | errorNode = ( 41 | 47 | ); 48 | } else if (!task) { 49 | taskNode = ( 50 | 55 | Loading... 56 | 57 | ); 58 | } else { 59 | taskNode = ( 60 | 61 | 62 | 67 | 73 | 74 | 75 | 76 | ); 77 | } 78 | 79 | return ( 80 |
81 |
89 | 90 | 91 | 92 | 93 | {task ? task.name : 'Task'} 94 | 95 |
96 | {errorNode} 97 | 98 | {taskNode} 99 |
100 | ); 101 | } 102 | } 103 | 104 | Task.defaultProps = { 105 | error: undefined, 106 | task: undefined 107 | }; 108 | 109 | Task.propTypes = { 110 | dispatch: PropTypes.func.isRequired, 111 | error: PropTypes.object, 112 | match: PropTypes.object.isRequired, 113 | task: PropTypes.object 114 | }; 115 | 116 | const select = state => ({ ...state.tasks }); 117 | 118 | export default connect(select)(Task); 119 | -------------------------------------------------------------------------------- /templates/docs/webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import CopyWebpackPlugin from 'copy-webpack-plugin'; 4 | import StaticSiteGeneratorPlugin from 'static-site-generator-webpack-plugin'; 5 | import reactRouterToArray from 'react-router-to-array'; 6 | import routes from './src/js/routes'; 7 | 8 | const env = process.env.NODE_ENV || 'production'; 9 | 10 | let plugins = [ 11 | new CopyWebpackPlugin([{ from: './public' }]), 12 | new webpack.DefinePlugin({ 13 | 'process.env': { 14 | NODE_ENV: JSON.stringify(env) 15 | } 16 | }) 17 | ]; 18 | 19 | const loaderOptionsConfig = { 20 | options: { 21 | sassLoader: { 22 | includePaths: [ 23 | './node_modules' 24 | ] 25 | } 26 | } 27 | }; 28 | 29 | const devConfig = {}; 30 | if (env === 'production') { 31 | loaderOptionsConfig.minimize = true; 32 | plugins.push( 33 | new webpack.optimize.UglifyJsPlugin({ 34 | compress: { 35 | screw_ie8: true, 36 | warnings: false 37 | }, 38 | mangle: { 39 | screw_ie8: true 40 | }, 41 | output: { 42 | comments: false, 43 | screw_ie8: true 44 | } 45 | }) 46 | ); 47 | } else { 48 | plugins = plugins.concat([ 49 | new webpack.HotModuleReplacementPlugin() 50 | ]); 51 | devConfig.devtool = 'cheap-module-source-map'; 52 | devConfig.entry = [ 53 | require.resolve('react-dev-utils/webpackHotDevClient'), 54 | './src/js/index.js' 55 | ]; 56 | devConfig.devServer = { 57 | compress: true, 58 | clientLogLevel: 'none', 59 | contentBase: path.resolve('./dist'), 60 | publicPath: '/', 61 | quiet: true, 62 | hot: true, 63 | watchOptions: { 64 | ignored: /node_modules/ 65 | }, 66 | historyApiFallback: true 67 | }; 68 | } 69 | 70 | plugins.push(new webpack.LoaderOptionsPlugin(loaderOptionsConfig)); 71 | 72 | const baseConfig = Object.assign({ 73 | entry: './src/js/index.js', 74 | output: { 75 | path: path.resolve('./dist'), 76 | filename: 'index.js', 77 | publicPath: '/' 78 | }, 79 | resolve: { 80 | extensions: ['.js', '.scss', '.css', '.json'] 81 | }, 82 | plugins, 83 | node: { 84 | fs: 'empty', 85 | net: 'empty', 86 | tls: 'empty' 87 | }, 88 | module: { 89 | rules: [ 90 | { 91 | test: /\.js/, 92 | exclude: /node_modules/, 93 | loader: 'babel-loader' 94 | }, 95 | { 96 | test: /\.scss$/, 97 | use: [ 98 | { loader: 'file-loader', options: { name: '[name].css' } }, 99 | { loader: 'sass-loader', 100 | options: { 101 | outputStyle: 'compressed', 102 | includePaths: [ 103 | './node_modules' 104 | ] 105 | } 106 | } 107 | ] 108 | }, 109 | { 110 | test: /\.ejs$/, 111 | loader: 'ejs-compiled-loader', 112 | options: { htmlmin: '' } 113 | } 114 | ] 115 | } 116 | }, devConfig); 117 | 118 | const webpackConfigs = [baseConfig]; 119 | if (env === 'production') { 120 | // add configuration specific to static site generation 121 | const staticConfig = { ...baseConfig }; 122 | 123 | staticConfig.entry = { 124 | main: path.resolve('./src/js/static.js') 125 | }; 126 | 127 | staticConfig.output = { 128 | filename: 'static.js', 129 | path: path.resolve('./dist'), 130 | libraryTarget: 'commonjs2' 131 | }; 132 | 133 | staticConfig.plugins = [ 134 | new StaticSiteGeneratorPlugin( 135 | 'main', reactRouterToArray(routes) 136 | ) 137 | ]; 138 | 139 | webpackConfigs.push(staticConfig); 140 | } 141 | 142 | export default webpackConfigs; 143 | -------------------------------------------------------------------------------- /templates/app/src/js/screens/Tasks.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import Anchor from 'grommet/components/Anchor'; 5 | import Article from 'grommet/components/Article'; 6 | import Box from 'grommet/components/Box'; 7 | import Header from 'grommet/components/Header'; 8 | import Label from 'grommet/components/Label'; 9 | import List from 'grommet/components/List'; 10 | import ListItem from 'grommet/components/ListItem'; 11 | import Notification from 'grommet/components/Notification'; 12 | import Meter from 'grommet/components/Meter'; 13 | import Paragraph from 'grommet/components/Paragraph'; 14 | import Value from 'grommet/components/Value'; 15 | import Spinning from 'grommet/components/icons/Spinning'; 16 | import { getMessage } from 'grommet/utils/Intl'; 17 | 18 | import NavControl from '../components/NavControl'; 19 | 20 | import { 21 | loadTasks, unloadTasks 22 | } from '../actions/tasks'; 23 | 24 | import { pageLoaded } from './utils'; 25 | 26 | class Tasks extends Component { 27 | componentDidMount() { 28 | pageLoaded('Tasks'); 29 | this.props.dispatch(loadTasks()); 30 | } 31 | 32 | componentWillUnmount() { 33 | this.props.dispatch(unloadTasks()); 34 | } 35 | 36 | render() { 37 | const { error, tasks } = this.props; 38 | const { intl } = this.context; 39 | 40 | let errorNode; 41 | let listNode; 42 | if (error) { 43 | errorNode = ( 44 | 50 | ); 51 | } else if (tasks.length === 0) { 52 | listNode = ( 53 | 58 | Loading... 59 | 60 | ); 61 | } else { 62 | const tasksNode = (tasks || []).map(task => ( 63 | 67 | 68 | 73 | 79 | 80 | 81 | 82 | )); 83 | 84 | listNode = ( 85 | 86 | {tasksNode} 87 | 88 | ); 89 | } 90 | 91 | return ( 92 |
93 |
99 | 100 |
101 | {errorNode} 102 | 103 | 104 | The backend here is using websocket. 105 | 106 | 107 | {listNode} 108 |
109 | ); 110 | } 111 | } 112 | 113 | Tasks.defaultProps = { 114 | error: undefined, 115 | tasks: [] 116 | }; 117 | 118 | Tasks.propTypes = { 119 | dispatch: PropTypes.func.isRequired, 120 | error: PropTypes.object, 121 | tasks: PropTypes.arrayOf(PropTypes.object) 122 | }; 123 | 124 | Tasks.contextTypes = { 125 | intl: PropTypes.object 126 | }; 127 | 128 | const select = state => ({ ...state.tasks }); 129 | 130 | export default connect(select)(Tasks); 131 | -------------------------------------------------------------------------------- /templates/app/src/js/screens/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import Anchor from 'grommet/components/Anchor'; 5 | import Article from 'grommet/components/Article'; 6 | import Box from 'grommet/components/Box'; 7 | import Header from 'grommet/components/Header'; 8 | import Heading from 'grommet/components/Heading'; 9 | import Label from 'grommet/components/Label'; 10 | import List from 'grommet/components/List'; 11 | import ListItem from 'grommet/components/ListItem'; 12 | import Notification from 'grommet/components/Notification'; 13 | import Paragraph from 'grommet/components/Paragraph'; 14 | import Value from 'grommet/components/Value'; 15 | import Meter from 'grommet/components/Meter'; 16 | import Spinning from 'grommet/components/icons/Spinning'; 17 | import { getMessage } from 'grommet/utils/Intl'; 18 | 19 | import NavControl from '../components/NavControl'; 20 | import { 21 | loadDashboard, unloadDashboard 22 | } from '../actions/dashboard'; 23 | 24 | import { pageLoaded } from './utils'; 25 | 26 | class Dashboard extends Component { 27 | componentDidMount() { 28 | pageLoaded('Dashboard'); 29 | this.props.dispatch(loadDashboard()); 30 | } 31 | 32 | componentWillUnmount() { 33 | this.props.dispatch(unloadDashboard()); 34 | } 35 | 36 | render() { 37 | const { error, tasks } = this.props; 38 | const { intl } = this.context; 39 | 40 | let errorNode; 41 | let listNode; 42 | if (error) { 43 | errorNode = ( 44 | 50 | ); 51 | } else if (tasks.length === 0) { 52 | listNode = ( 53 | 58 | Loading... 59 | 60 | ); 61 | } else { 62 | const tasksNode = (tasks || []).map(task => ( 63 | 67 | 68 | 73 | 79 | 80 | 81 | 82 | )); 83 | 84 | listNode = ( 85 | 86 | {tasksNode} 87 | 88 | ); 89 | } 90 | 91 | return ( 92 |
93 |
99 | 100 |
101 | {errorNode} 102 | 103 | 104 | Running Tasks 105 | 106 | 107 | The backend here is using request polling (5 second interval). 108 | See page for an example 110 | of websocket communication. 111 | 112 | 113 | {listNode} 114 |
115 | ); 116 | } 117 | } 118 | 119 | Dashboard.defaultProps = { 120 | error: undefined, 121 | tasks: [] 122 | }; 123 | 124 | Dashboard.propTypes = { 125 | dispatch: PropTypes.func.isRequired, 126 | error: PropTypes.object, 127 | tasks: PropTypes.arrayOf(PropTypes.object) 128 | }; 129 | 130 | Dashboard.contextTypes = { 131 | intl: PropTypes.object 132 | }; 133 | 134 | const select = state => ({ ...state.dashboard }); 135 | 136 | export default connect(select)(Dashboard); 137 | -------------------------------------------------------------------------------- /src/commands/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Node dependencies 3 | **/ 4 | import path from 'path'; 5 | import { fork } from 'child_process'; 6 | 7 | /** 8 | * NPM dependencies 9 | **/ 10 | import chalk from 'chalk'; 11 | import jest from 'jest-cli'; 12 | import yargs from 'yargs'; 13 | 14 | export const delimiter = chalk.magenta('[grommet]'); 15 | 16 | export function errorHandler(err) { 17 | console.log( 18 | `${delimiter}: ${chalk.red('failed')}` 19 | ); 20 | const isArray = Array.isArray(err); 21 | if (isArray) { 22 | err.forEach(e => console.error(e.message ? e.message : e)); 23 | } else { 24 | console.error(err.message ? err.message : err); 25 | } 26 | } 27 | 28 | export function runTests(options) { 29 | return new Promise((resolve, reject) => { 30 | const optionKeys = Object.keys(options).length; 31 | if (options.scsslint || options.jslint) { 32 | resolve(); 33 | } else { 34 | process.env.NODE_ENV = 'test'; 35 | console.log( 36 | `${delimiter}: Running Tests...` 37 | ); 38 | let packageJSON = require(path.resolve(process.cwd(), 'package.json')); 39 | const config = Object.assign({ 40 | rootDir: process.cwd() 41 | }, packageJSON.jest, yargs(process.argv.slice(optionKeys + 2)).argv); 42 | 43 | jest.runCLI(config, [process.cwd()], (result) => { 44 | if(result.numFailedTests || result.numFailedTestSuites) { 45 | reject('Tests Failed'); 46 | } else { 47 | resolve(); 48 | } 49 | }); 50 | } 51 | }); 52 | } 53 | 54 | export function runJsLint(options) { 55 | return new Promise((resolve, reject) => { 56 | const optionKeys = Object.keys(options).length; 57 | if (optionKeys > 0 && !options.jslint) { 58 | resolve(); 59 | } else { 60 | console.log( 61 | `${delimiter}: Running Javascript linting...` 62 | ); 63 | const eslintChild = fork( 64 | path.resolve(__dirname, 'eslint'), process.argv.slice(optionKeys + 2) 65 | ); 66 | eslintChild.on('exit', (code) => { 67 | if (code !== 0) { 68 | reject('Js Linting failed'); 69 | } else { 70 | resolve(); 71 | } 72 | }); 73 | eslintChild.send('**/*.{js,jsx}'); 74 | } 75 | 76 | }); 77 | } 78 | 79 | export function runSCSSLint(options) { 80 | return new Promise((resolve, reject) => { 81 | const optionKeys = Object.keys(options).length; 82 | if (optionKeys > 0 && !options.scsslint) { 83 | resolve(); 84 | } else { 85 | console.log( 86 | `${delimiter}: Running SCSS linting...` 87 | ); 88 | const scsslintChild = fork( 89 | path.resolve(__dirname, 'scsslint'), process.argv.slice(optionKeys + 2) 90 | ); 91 | scsslintChild.on('exit', (code) => { 92 | if (code !== 0) { 93 | reject('SCSS Linting failed'); 94 | } else { 95 | resolve(); 96 | } 97 | }); 98 | scsslintChild.send('**/*.scss'); 99 | } 100 | }); 101 | } 102 | 103 | export function runLinters(options) { 104 | return new Promise((resolve, reject) => { 105 | if (options.test) { 106 | resolve(); 107 | } else { 108 | const errors = []; 109 | let scssLintCompleted = false; 110 | let jsLintCompleted = false; 111 | runJsLint(options).then( 112 | () => { 113 | jsLintCompleted = true; 114 | if (scssLintCompleted) { 115 | if (errors.length === 0) { 116 | resolve(); 117 | } else { 118 | reject(errors); 119 | } 120 | } 121 | }, 122 | (err) => { 123 | jsLintCompleted = true; 124 | errors.push(err); 125 | if (scssLintCompleted) { 126 | reject(errors); 127 | } 128 | } 129 | ); 130 | runSCSSLint(options).then( 131 | () => { 132 | scssLintCompleted = true; 133 | if (jsLintCompleted) { 134 | if (errors.length === 0) { 135 | resolve(); 136 | } else { 137 | reject(errors); 138 | } 139 | } 140 | }, 141 | (err) => { 142 | scssLintCompleted = true; 143 | errors.push(err); 144 | if (jsLintCompleted) { 145 | reject(errors); 146 | } 147 | } 148 | ); 149 | } 150 | }); 151 | } 152 | 153 | export default { errorHandler, runJsLint, runLinters, runSCSSLint, runTests }; 154 | -------------------------------------------------------------------------------- /src/commands/new.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Node core dependencies 3 | **/ 4 | import path from 'path'; 5 | 6 | /** 7 | * NPM dependencies 8 | **/ 9 | import mkdirp from 'mkdirp'; 10 | import shelljs from 'shelljs'; 11 | 12 | /** 13 | * Local dependencies 14 | **/ 15 | import { 16 | dependenciesSupported, fileExists, generateProject, runModulesInstall, themes 17 | } from '../utils/cli'; 18 | 19 | export default function (vorpal) { 20 | const config = { 21 | appTypes: ['app', 'basic', 'docs'], 22 | appThemes: Object.keys(themes), 23 | cliPath: path.join(__dirname, '../../'), 24 | delimiter: vorpal.chalk.magenta('grommet'), 25 | npmVersion: Number( 26 | shelljs.exec( 27 | 'npm --version', { silent:true } 28 | ).stdout.toString().match(/^(\d+\.\d+)/)[1] 29 | ), 30 | nodeVersion: Number(process.version.match(/^v(\d+\.\d+)/)[1]) 31 | }; 32 | 33 | vorpal 34 | .command( 35 | 'new [app]', 36 | `Create a new grommet app. 37 | Check type option for different ways to initialize an app.` 38 | ) 39 | .option( 40 | '-t, --type [type]', 41 | `Type of the generated app (${config.appTypes.join()}). Defaults to app. 42 | (You can press tab for autocomplete)`, 43 | config.appTypes 44 | ) 45 | .option( 46 | '--theme [theme]', 47 | `Theme of the generated app (${config.appThemes.join()}). Defaults to grommet. 48 | (You can press tab for autocomplete)`, 49 | config.appThemes 50 | ) 51 | .option( 52 | '-d, --description [description]', 53 | `Quick description of the app. Defaults to empty.` 54 | ) 55 | .option( 56 | '-r, --repository [repository]', 57 | `Repository URL of the project. Defaults to empty.` 58 | ) 59 | .option( 60 | '-l, --license [license]', 61 | `Project license. Defaults to empty.` 62 | ) 63 | .option( 64 | '-i, --noInstall', 65 | `Skip installing modules after generation is completed. Defaults to false.` 66 | ) 67 | .validate((args) => { 68 | if (args.options.type && !config.appTypes.includes(args.options.type)) { 69 | return `Invalid type. Available types are: ${config.appTypes.join()}`; 70 | } 71 | 72 | if (args.options.theme && !config.appThemes.includes(args.options.theme)) { 73 | return `Invalid theme. Available themes are: ${config.appThemes.join()}`; 74 | } 75 | return true; 76 | }) 77 | .action((args, cb) => { 78 | if (!dependenciesSupported(config)) { 79 | throw 'Unsupported version.'; 80 | } 81 | const options = Object.assign({ 82 | type: 'app', 83 | theme: 'grommet', 84 | app: args.app || 'app-name', 85 | description: '', 86 | repository: '', 87 | license: '' 88 | }, args.options); 89 | 90 | vorpal.activeCommand.prompt({ 91 | type: 'input', 92 | name: 'basePath', 93 | default: process.cwd(), 94 | message: 'Destination folder (if empty, current folder will be used)?' 95 | }, (result) => { 96 | options.basePath = path.resolve(result.basePath); 97 | 98 | const newAppPath = path.join(options.basePath, options.app); 99 | 100 | if (fileExists(newAppPath)) { 101 | throw `[${config.delimiter}] Error while creating app. Directory "${newAppPath}" already exists.`; 102 | } 103 | mkdirp(newAppPath, (err) => { 104 | if (err) { 105 | throw err; 106 | } 107 | var templateFolder = path.join(config.cliPath, 'templates', options.type); 108 | 109 | try { 110 | generateProject( 111 | templateFolder, newAppPath, options, config 112 | ).then(() => { 113 | console.log( 114 | `[${config.delimiter}] App generation successfully completed` 115 | ); 116 | if (options.noInstall) { 117 | console.log( 118 | `[${config.delimiter}] Module installation has been skipped` 119 | ); 120 | return Promise.resolve(); 121 | } else { 122 | return runModulesInstall(newAppPath, config); 123 | } 124 | }) 125 | .then(cb) 126 | // if something fails during the installation of modules the cli should NOT fail. 127 | .catch(() => process.exit(0)); 128 | } catch(err) { 129 | shelljs.rm('-rf', newAppPath); 130 | throw err; 131 | } 132 | }); 133 | }); 134 | }); 135 | }; 136 | -------------------------------------------------------------------------------- /templates/app/src/js/api/request-watcher.js: -------------------------------------------------------------------------------- 1 | import { headers, parseJSON } from './utils'; 2 | 3 | function _getRequest(request) { 4 | if (!request.pollBusy) { 5 | request.pollBusy = true; 6 | 7 | const options = { method: 'GET', headers: headers() }; 8 | fetch(request.uri, options) 9 | .then(parseJSON) 10 | .then(request.success) 11 | .catch(error => request.error({ 12 | statusCode: error.status, message: error.statusText 13 | })) 14 | .then(() => request.pollBusy = false); 15 | } 16 | } 17 | 18 | const wsRegex = ( 19 | /^(wss?):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?$/i 20 | ); 21 | 22 | export default class RequestWatcher { 23 | constructor(options = {}) { 24 | this._options = Object.assign({ 25 | reconnectTimeout: 5000, // 5s 26 | pollTimeout: 5000, // 5s 27 | pingTimeout: 30000 // 30s, 28 | }, options); 29 | this._requests = []; 30 | this._nextRequestId = 1; 31 | if (this._options.webSocketUrl) { 32 | if (!wsRegex.test(this._options.webSocketUrl)) { 33 | throw new Error('Option webSocketUrl is not a valid socket url'); 34 | } 35 | if (!('WebSocket' in window || 'MozWebSocket' in window)) { 36 | console.warn( 37 | 'WebSocket not available in this browser, will fall back to polling' 38 | ); 39 | } else { 40 | this._initialize(); 41 | } 42 | } 43 | } 44 | 45 | _initialize() { 46 | this._ws = new WebSocket(this._options.webSocketUrl); 47 | this._ws.onopen = this._onOpen.bind(this); 48 | this._ws.onerror = this._onError.bind(this); 49 | this._ws.onmessage = this._onMessage.bind(this); 50 | this._ws.onclose = this._onClose.bind(this); 51 | } 52 | 53 | _onError() { 54 | this._requests.forEach(request => request.error({ 55 | statusCode: 500, 56 | message: 'Failed to connect with server' 57 | })); 58 | } 59 | 60 | _onClose() { 61 | // lost connection, retry in a bit 62 | this._ws = undefined; 63 | clearInterval(this._pingTimer); 64 | this._pollTimer = setTimeout( 65 | this._initialize.bind(this), this._options.reconnectTimeout 66 | ); 67 | } 68 | 69 | _onMessage(event) { 70 | const message = JSON.parse(event.data); 71 | // Figure out which message this corresponds to so we 72 | // know which action to deliver the response with. 73 | this._requests.some((request) => { 74 | if (request.id === message.id) { 75 | if (message.op === 'error') { 76 | request.error(message.error); 77 | } else { 78 | request.success(message.result); 79 | } 80 | return true; 81 | } 82 | return false; 83 | }); 84 | } 85 | 86 | _sendMessage(op, id, uri) { 87 | if (this._ws) { 88 | const { Auth } = headers(); 89 | this._ws.send(JSON.stringify({ op, id, uri, Auth })); 90 | } 91 | } 92 | 93 | _onOpen() { 94 | this._wsReady = true; 95 | // send any requests we have queued up 96 | this._requests.forEach(request => this._sendMessage( 97 | 'start', request.id, request.uri 98 | )); 99 | // stop polling 100 | clearInterval(this._pollTimer); 101 | // start pinging 102 | clearInterval(this._pingTimer); 103 | this._pingTimer = setInterval( 104 | this._ping.bind(this), this._options.pingTimeout 105 | ); 106 | } 107 | 108 | _ping() { 109 | this._sendMessage('ping'); 110 | } 111 | 112 | _poll() { 113 | this._requests.forEach(_getRequest); 114 | } 115 | 116 | watch(uri) { 117 | const request = { 118 | id: this._nextRequestId++, 119 | uri 120 | }; 121 | this._requests.push(request); 122 | const watcher = { 123 | on(result, cb) { 124 | if (result === 'success') { 125 | request.success = cb; 126 | } else if (result === 'error') { 127 | request.error = cb; 128 | } 129 | return watcher; 130 | }, 131 | start: function start() { 132 | if (this._wsReady) { 133 | this._sendMessage('start', request.id, request.uri); 134 | } else { 135 | // The web socket isn't ready yet, and might never be. 136 | // Proceed in polling mode until the web socket is ready. 137 | _getRequest(request); 138 | if (!this._pollTimer) { 139 | this._pollTimer = setInterval( 140 | this._poll.bind(this), this._options.pollTimeout 141 | ); 142 | } 143 | } 144 | return watcher; 145 | }.bind(this), 146 | stop: function stop() { 147 | this._requests = this._requests.filter((req) => { 148 | if (req.id === request.id) { 149 | if (this._wsReady) { 150 | this._sendMessage('stop', req.id); 151 | } else if (this._requests.length === 1) { 152 | // stop polling if request list is empty 153 | clearInterval(this._pollTimer); 154 | this._pollTimer = undefined; 155 | } 156 | } 157 | return (req.id !== request.id); 158 | }); 159 | 160 | return watcher; 161 | }.bind(this) 162 | }; 163 | 164 | return watcher; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/utils/cli.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | 3 | /** 4 | * Node core dependencies 5 | **/ 6 | import childProcess from 'child_process'; 7 | import fs from 'fs'; 8 | import path from 'path'; 9 | import os from 'os'; 10 | 11 | /** 12 | * NPM dependencies 13 | **/ 14 | import ejs from 'ejs'; 15 | import mkdirp from 'mkdirp'; 16 | import walk from 'walk'; 17 | 18 | const spawn = childProcess.spawn; 19 | 20 | const supportedNodeVersion = '4.4'; 21 | const supportedNpmVersion = '3'; 22 | 23 | export const themes = { 24 | grommet: 'grommet/scss/vanilla/index', 25 | hpe: 'grommet/scss/hpe/index', 26 | aruba: 'grommet/scss/aruba/index', 27 | hpinc: 'grommet/scss/hpinc/index', 28 | dxc: 'grommet/scss/dxc/index' 29 | }; 30 | 31 | export function capitalize(str) { 32 | var words = str.split(' '); 33 | 34 | words = words.map((word) => 35 | word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() 36 | ); 37 | 38 | return words.join(' '); 39 | } 40 | 41 | export function dependenciesSupported(config) { 42 | if (!nodeVersionSupported(config.nodeVersion) || 43 | !npmVersionSupported(config.npmVersion)) { 44 | console.error( 45 | `[${config.delimiter}] Grommet requires Node v${supportedNodeVersion}+ and NPM v${supportedNpmVersion}+.`); 46 | console.error( 47 | `[${config.delimiter}] Currently you have Node ${process.version} and NPM ${config.npmVersion}` 48 | ); 49 | return false; 50 | } 51 | 52 | return true; 53 | } 54 | 55 | export function fileExists(filePath) { 56 | try { 57 | fs.statSync(filePath); 58 | return true; 59 | } catch(error) {} 60 | return false; 61 | } 62 | 63 | export function generateProject(from, to, options, config) { 64 | return new Promise((resolve) => { 65 | const templateVars = { 66 | appName: options.app, 67 | appTitle: capitalize(options.app.replace(/-|_/g, ' ')), 68 | appDescription: options.description, 69 | appRepository: options.repository, 70 | appLicense: options.license, 71 | appTheme: themes[options.theme] 72 | }; 73 | 74 | console.log(`[${config.delimiter}] Generating app at: ${to}`); 75 | const walker = walk.walk(from, { followLinks: false }); 76 | 77 | walker.on('file', (root, stat, next) => { 78 | const source = path.join(root, stat.name); 79 | const destinationFolder = path.join(to, root.split(from)[1]); 80 | const destinationFile = path.join(destinationFolder, stat.name); 81 | 82 | const isImageOrEjs = /\.(jpg|jpeg|png|gif|ejs)$/.test(stat.name); 83 | if (isImageOrEjs) { 84 | mkdirp(destinationFolder, (err) => { 85 | if (err) { 86 | throw err; 87 | } 88 | fs.createReadStream(source).pipe( 89 | fs.createWriteStream(destinationFile) 90 | ); 91 | next(); 92 | }); 93 | } else { 94 | ejs.renderFile(source, templateVars, {}, (err, content) => { 95 | if (err) { 96 | throw err; 97 | } 98 | mkdirp(destinationFolder, (err) => { 99 | if (err) { 100 | throw err; 101 | } 102 | fs.writeFile(destinationFile, content, (err) => { 103 | if(err) { 104 | throw err; 105 | } 106 | }); 107 | next(); 108 | }); 109 | }); 110 | } 111 | }); 112 | 113 | walker.on('end', resolve); 114 | }); 115 | } 116 | 117 | export function runModulesInstall (cwd, config) { 118 | return new Promise((resolve) => { 119 | console.log( 120 | `[${config.delimiter}] Installing dependencies...` 121 | ); 122 | console.log( 123 | `[${config.delimiter}] If the install fails, make sure to delete your node_modules and run 'npm/yarn install' again...` 124 | ); 125 | 126 | // try yarn first 127 | let command = /^win/.test(os.platform()) ? 'yarn.cmd' : 'yarn'; 128 | spawn( 129 | command, ['install'], { stdio: 'inherit', cwd: cwd } 130 | ) 131 | .on('error', () => { 132 | console.log( 133 | `[${config.delimiter}] Installing can be faster if you install Yarn (https://yarnpkg.com/)...` 134 | ); 135 | command = /^win/.test(os.platform()) ? 'npm.cmd' : 'npm'; 136 | spawn( 137 | command, ['install'], { stdio: 'inherit', cwd: cwd } 138 | ).on('close', resolve); 139 | }) 140 | .on('close', (code) => { 141 | if (code === 0) { 142 | resolve(); 143 | } 144 | }); 145 | }); 146 | } 147 | 148 | export function nodeVersionSupported(nodeVersion) { 149 | return nodeVersion >= Number(supportedNodeVersion); 150 | } 151 | 152 | export function npmVersionSupported(npmVersion) { 153 | return npmVersion >= Number(supportedNpmVersion); 154 | } 155 | 156 | export function getBabelConfig() { 157 | let babelrcPath = path.resolve(process.cwd(), '.babelrc'); 158 | try { 159 | fs.accessSync(babelrcPath, fs.F_OK); 160 | } catch (e) { 161 | babelrcPath = path.resolve(__dirname, '../../.babelrc'); 162 | } 163 | 164 | return JSON.parse(fs.readFileSync(babelrcPath)); 165 | } 166 | 167 | export default { 168 | capitalize, dependenciesSupported, fileExists, generateProject, 169 | getBabelConfig, nodeVersionSupported, npmVersionSupported, 170 | runModulesInstall, themes 171 | }; 172 | -------------------------------------------------------------------------------- /templates/app/server/notifier.js: -------------------------------------------------------------------------------- 1 | import ws from 'ws'; 2 | import pathToRegexp from 'path-to-regexp'; 3 | 4 | function parseQuery(string) { 5 | const params = string.split('&'); 6 | const result = {}; 7 | params.forEach((param) => { 8 | const parts = param.split('='); 9 | // if we already have this parameter, it must be an array 10 | if (result[parts[0]]) { 11 | if (!Array.isArray(result[parts[0]])) { 12 | result[parts[0]] = [result[parts[0]]]; 13 | } 14 | result[parts[0]].push(decodeURIComponent(parts[1])); 15 | } else { 16 | result[parts[0]] = decodeURIComponent(parts[1]); 17 | } 18 | }); 19 | return result; 20 | } 21 | 22 | class Connection { 23 | constructor(socket, routes) { 24 | this._requests = []; 25 | this._socket = socket; 26 | this._routes = routes; 27 | this._listeners = {}; 28 | 29 | this._socket.on('message', this._onMessage.bind(this)); 30 | this._socket.on('close', this.close.bind(this)); 31 | } 32 | 33 | _validate(request) { 34 | // gets all routes and check if there is a match 35 | return this._routes.some((route) => { 36 | const params = []; 37 | // params will be populated by pathToRegexp with the dynamic portios of 38 | // the route 39 | const pathRegexp = pathToRegexp(route.path, params); 40 | // path needs to be a valid express route 41 | if (pathRegexp.test(request.uri)) { 42 | if (params.length > 0) { 43 | // grap the param values for the dynamic URL 44 | const groups = pathRegexp.exec(request.uri); 45 | params.forEach((param, index) => { 46 | // the resulting group has index 0 as the entire expression 47 | // this is why we have to increment index by 1 48 | request.params[param.name] = groups[index + 1]; 49 | }); 50 | } 51 | // add the route path to the request so that we can easily 52 | // reference which route was used for this request 53 | request.path = route.path; 54 | return true; 55 | } 56 | return false; 57 | }); 58 | } 59 | 60 | _onMessage(message) { 61 | const request = JSON.parse(message); 62 | if (request.op === 'start') { 63 | // Split out query parameters 64 | const parts = request.uri.split('?'); 65 | request.uri = parts[0]; 66 | if (parts[1]) { 67 | request.params = parseQuery(parts[1]); 68 | } else { 69 | request.params = []; 70 | } 71 | if (this._validate(request)) { 72 | this._requests.push(request); 73 | this._exec(request); 74 | } else { 75 | this._socket.send({ 76 | error: { statusCode: 404, message: `unknown uri ${request.uri}` } 77 | }); 78 | } 79 | } else if (request.op === 'stop') { 80 | this._requests = this._requests.filter(req => req.id !== request.id); 81 | } else if (request.op === 'ping') { 82 | this._socket.send(JSON.stringify({ op: 'ping' })); 83 | } else { 84 | this._socket.send({ 85 | error: { statusCode: 404, message: `unknown op ${request.op}` } 86 | }); 87 | this.close(); 88 | } 89 | } 90 | 91 | _exec(request) { 92 | // stop after the first matching route 93 | this._routes.some((route) => { 94 | if (request.path === route.path) { 95 | const socket = this._socket; 96 | route.cb(request.params) 97 | .then((result) => { 98 | socket.send( 99 | JSON.stringify({ op: 'update', id: request.id, result }) 100 | ); 101 | }) 102 | .catch(error => ( 103 | socket.send( 104 | JSON.stringify({ op: 'error', id: request.id, error }) 105 | ) 106 | )); 107 | return true; 108 | } 109 | return false; 110 | }, this); 111 | } 112 | 113 | close() { 114 | if (this._socket) { 115 | this._socket.close(); 116 | this._socket = undefined; 117 | } 118 | // notify possible listeners on the connection close event 119 | if (this._listeners.close) { 120 | this._listeners.close(); 121 | } 122 | } 123 | 124 | test(cb) { 125 | if (this._socket) { 126 | this._requests.forEach((request) => { 127 | if (cb(request)) { 128 | this._exec(request); 129 | } 130 | }, this); 131 | } 132 | } 133 | 134 | on(event, cb) { 135 | if (event === 'close') { 136 | this._listeners[event] = cb; 137 | } 138 | } 139 | } 140 | 141 | export default class Notifier { 142 | constructor() { 143 | this._connections = []; 144 | this._routes = []; 145 | this._notifyListeners = []; 146 | } 147 | 148 | _onConnection(socket) { 149 | const connections = this._connections; 150 | const connection = new Connection(socket, this._routes); 151 | connections.push(connection); 152 | connection.on('close', () => { 153 | const index = connections.indexOf(connection); 154 | if (index) { 155 | connections.splice(index, 1); 156 | } 157 | }); 158 | } 159 | 160 | listen(server) { 161 | this._wsServer = new ws.Server({ server }); 162 | this._wsServer.on('connection', this._onConnection.bind(this)); 163 | } 164 | 165 | use(path, cb) { 166 | if (!this._wsServer) { 167 | this._routes.push({ path, cb }); 168 | } else { 169 | console.error('Cannot add listener to Notifier after listen is active.'); 170 | } 171 | } 172 | 173 | test(cb) { 174 | this._connections.forEach( 175 | connection => connection.test(cb) 176 | ); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/commands/pack.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Node dependencies 3 | **/ 4 | import path from 'path'; 5 | import del from 'del'; 6 | 7 | /** 8 | * NPM dependencies 9 | **/ 10 | import chalk from 'chalk'; 11 | import emoji from 'node-emoji'; 12 | import fs from 'fs-extra'; 13 | import cp from 'child_process'; 14 | import tarball from 'tarball-extract'; 15 | import prettyHrtime from 'pretty-hrtime'; 16 | import opener from 'opener'; 17 | import rimraf from 'rimraf'; 18 | import webpack from 'webpack'; 19 | import WebpackDevServer from 'webpack-dev-server'; 20 | 21 | const delimiter = chalk.magenta('[grommet:pack]'); 22 | 23 | const ENV = process.env.NODE_ENV || 'production'; 24 | let port = process.env.PORT || 3000; 25 | 26 | function deleteDistributionFolder() { 27 | if (ENV === 'production') { 28 | return new Promise((resolve, reject) => { 29 | console.log( 30 | `${delimiter}: Deleting previously generated distribution folder...` 31 | ); 32 | rimraf(path.resolve('dist'), (err) => err ? reject(err) : resolve()); 33 | }); 34 | } else { 35 | // in dev mode, all resources are compiled and served from memory by 36 | // webpack-dev-server so there is no reason to delete the dist folder 37 | return Promise.resolve(); 38 | } 39 | } 40 | 41 | function runDevServer(compiler, devServerConfig) { 42 | console.log( 43 | `${delimiter}: Starting dev server...` 44 | ); 45 | const devServer = new WebpackDevServer(compiler, devServerConfig); 46 | if (!process.env.PORT && devServerConfig.port) { 47 | port = devServerConfig.port; 48 | } 49 | devServer.listen(port, (err, result) => { 50 | if (err) { 51 | throw err; 52 | } 53 | }); 54 | } 55 | 56 | function build(config, options) { 57 | return new Promise((resolve, reject) => { 58 | let handleResponse; 59 | // only handle response for production mode 60 | if (ENV === 'production') { 61 | handleResponse = (err, stats) => { 62 | const statHandler = (stat) => { 63 | if (err) { 64 | reject(err); 65 | } else if (stat.compilation.errors.length) { 66 | reject(stat.compilation.errors); 67 | } else { 68 | console.log(stat.toString({ 69 | chunks: false, 70 | colors: true 71 | })); 72 | } 73 | }; 74 | 75 | if (stats.stats) { // multiple stats 76 | stats.stats.forEach(statHandler); 77 | } else { 78 | statHandler(stats); 79 | } 80 | resolve(); 81 | }; 82 | } 83 | const compiler = webpack(config, handleResponse); 84 | 85 | if (ENV === 'development') { 86 | const startServer = (devServerConfig) => { 87 | let firstCompilation = true; 88 | compiler.plugin('done', (stats) => { 89 | const statHandler = (stat) => { 90 | if (stat.compilation.errors.length) { 91 | errorHandler(stat.compilation.errors); 92 | } else { 93 | console.log(stat.toString({ 94 | chunks: false, 95 | colors: true 96 | })); 97 | 98 | console.log( 99 | `${delimiter}: ${chalk.green('success')}` 100 | ); 101 | 102 | if (!options['skip-open'] && firstCompilation) { 103 | // https can be an object or just a boolean but either way will 104 | // be truthy when it is turned on 105 | const protocol = devServerConfig.https ? 'https' : 'http'; 106 | console.log( 107 | `${delimiter}: Opening the browser at ${protocol}://localhost:${port}` 108 | ); 109 | 110 | opener(`${protocol}://localhost:${port}`); 111 | } 112 | 113 | firstCompilation = false; 114 | } 115 | }; 116 | 117 | if (stats.stats) { // multiple stats 118 | stats.stats.forEach(statHandler); 119 | } else { 120 | statHandler(stats); 121 | } 122 | }); 123 | runDevServer(compiler, devServerConfig); 124 | }; 125 | 126 | let devServerConfig = config.devServer; 127 | if (!devServerConfig && Array.isArray(config)) { 128 | config.some((c) => { 129 | if (c.devServer) { 130 | devServerConfig = c.devServer; 131 | return true; 132 | } 133 | return false; 134 | }); 135 | } 136 | if (devServerConfig) { 137 | startServer(devServerConfig); 138 | } else { 139 | getDevServerConfig().then(startServer); 140 | } 141 | } 142 | }); 143 | } 144 | 145 | function getWebpackConfig() { 146 | return new Promise((resolve, reject) => { 147 | let webpackConfig = path.resolve(process.cwd(), 'webpack.config.js'); 148 | fs.exists(webpackConfig, (exists) => { 149 | if (exists) { 150 | resolve(require(webpackConfig)); 151 | } else { 152 | webpackConfig = path.resolve(process.cwd(), 'webpack.config.babel.js'); 153 | fs.exists(webpackConfig, (exists) => { 154 | if (exists) { 155 | resolve(require(webpackConfig).default); 156 | } else { 157 | reject('Webpack config not found'); 158 | } 159 | }); 160 | } 161 | }); 162 | }); 163 | } 164 | 165 | function getDevServerConfig() { 166 | return new Promise((resolve, reject) => { 167 | let devServerConfig = path.resolve(process.cwd(), 'devServer.config.js'); 168 | fs.exists(devServerConfig, (exists) => { 169 | if (exists) { 170 | console.warn( 171 | `${delimiter}: devServerConfig has been deprecated. Move your configuration to webpack.config devServer entry.` 172 | ); 173 | resolve(require(devServerConfig)); 174 | } else { 175 | devServerConfig = path.resolve( 176 | process.cwd(), 'devServer.config.babel.js' 177 | ); 178 | fs.exists(devServerConfig, (exists) => { 179 | if (exists) { 180 | console.warn( 181 | `${delimiter}: devServerConfig has been deprecated. Move your configuration to webpack.config devServer entry.` 182 | ); 183 | resolve(require(devServerConfig).default); 184 | } else { 185 | reject('devServer config not found'); 186 | } 187 | }); 188 | } 189 | }); 190 | }); 191 | } 192 | 193 | function packProject(options) { 194 | return new Promise((resolve, reject) => { 195 | console.log( 196 | `${delimiter}: Running webpack...` 197 | ); 198 | getWebpackConfig().then( 199 | (config) => build(config, options).then(resolve, reject), reject 200 | ); 201 | }); 202 | } 203 | 204 | function projectLicenses(options) { 205 | return new Promise((resolve, reject) => { 206 | console.log( 207 | `${delimiter}: Evaluating licenses...` 208 | ); 209 | const packageJSON = path.resolve('package.json'); 210 | const packageJSONAsString = fs.readFileSync(packageJSON); 211 | const json = JSON.parse(packageJSONAsString); 212 | if (json.dependencies) { 213 | json.bundledDependencies = Object.keys(json.dependencies); 214 | fs.writeFileSync(packageJSON, JSON.stringify(json, null, 2)); 215 | } 216 | 217 | try { 218 | cp.exec('npm pack', (packErr, stdout, stderr) => { 219 | console.log(stdout); 220 | console.error(stderr); 221 | 222 | if (packErr) { 223 | throw packErr; 224 | } 225 | const licenseMap = { 226 | name: json.name, 227 | version: json.version, 228 | dependencies: { licenseNotFound: [] } 229 | }; 230 | 231 | const tarballName = `${json.name}-${json.version}.tgz`; 232 | tarball.extractTarball(tarballName, './tmp', (err) => { 233 | if (err) { 234 | throw err; 235 | } 236 | 237 | fs.renameSync( 238 | path.resolve(tarballName), 239 | path.resolve(`${json.name}-${json.version}-src-with-dependecies.tgz`) 240 | ); 241 | 242 | const dependencies = fs.readdirSync('./tmp/package/node_modules'); 243 | 244 | dependencies.forEach((dependency) => { 245 | const dependencyPackageJSON = path.resolve( 246 | `node_modules/${dependency}/package.json` 247 | ); 248 | const contents = fs.readFileSync(dependencyPackageJSON); 249 | const instance = JSON.parse(contents); 250 | let license = instance.license; 251 | if (!license && instance.licenses) { 252 | license = instance.licenses[0]; 253 | } 254 | 255 | if (!license) { 256 | licenseMap.dependencies.licenseNotFound.push(dependency); 257 | } else if (license.type) { 258 | licenseMap.dependencies[dependency] = license.type; 259 | } else { 260 | licenseMap.dependencies[dependency] = license; 261 | } 262 | }); 263 | 264 | const dependencyLicense = path.resolve( 265 | `${json.name}-${json.version}-licenses.json` 266 | ); 267 | 268 | // write dependency license map 269 | fs.writeFileSync(dependencyLicense, JSON.stringify( 270 | licenseMap, null, 2) 271 | ); 272 | 273 | // revert original package.json 274 | fs.writeFileSync(packageJSON, JSON.stringify( 275 | JSON.parse(packageJSONAsString), null, 2) 276 | ); 277 | 278 | del.sync(['./tmp']); 279 | 280 | resolve(); 281 | }); 282 | }); 283 | } catch (e) { 284 | console.log(e); 285 | 286 | // revert original package.json 287 | fs.writeFileSync(packageJSON, JSON.stringify( 288 | JSON.parse(packageJSONAsString), null, 2) 289 | ); 290 | 291 | reject(e); 292 | } 293 | }); 294 | } 295 | 296 | function errorHandler(err = {}) { 297 | console.log( 298 | `${delimiter}: ${chalk.red('failed')}` 299 | ); 300 | const isArray = Array.isArray(err); 301 | if (isArray) { 302 | err.forEach(e => console.error(e.message ? e.message : e)); 303 | } else { 304 | console.error(err.message ? err.message : err); 305 | } 306 | } 307 | 308 | export default function (vorpal) { 309 | vorpal 310 | .command( 311 | 'pack', 312 | 'Builds a grommet application for development and/or production' 313 | ) 314 | .option('--skip-open', 'Skip browser opening on first compilation') 315 | .option('--licenses', 'Generate license information') 316 | .action((args, cb) => { 317 | const timeId = process.hrtime(); 318 | 319 | deleteDistributionFolder() 320 | .then(() => packProject(args.options)) 321 | .then(() => { 322 | if (args.options.licenses) { 323 | return projectLicenses(); 324 | } 325 | return Promise.resolve(); 326 | }) 327 | .then(() => { 328 | console.log( 329 | `${delimiter}: ${chalk.green('success')}` 330 | ); 331 | const t = process.hrtime(timeId); 332 | console.log(`${emoji.get('sparkles')} ${prettyHrtime(t)}`); 333 | }).catch((err) => { 334 | errorHandler(err); 335 | process.exit(1); 336 | }); 337 | }); 338 | }; 339 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------