├── .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 |
--------------------------------------------------------------------------------
/templates/basic/public/img/mobile-app-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/templates/docs/public/img/mobile-app-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 | 
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 | {
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 |
39 | <%= appTitle %>
40 |
41 | }
43 | onClick={this._onClose}
44 | plain={true}
45 | a11yTitle='Close Menu'
46 | />
47 |
48 |
51 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------