Переглянути джерело

Merge pull request #15561 from grafana/tooling/npm-scripts

@grafana/ui publishing scripts
Dominik Prokop 6 роки тому
батько
коміт
38fc55e09b
41 змінених файлів з 787 додано та 120 видалено
  1. 6 0
      .gitignore
  2. 9 1
      package.json
  3. 4 0
      packages/grafana-ui/CHANGELOG.md
  4. 12 2
      packages/grafana-ui/README.md
  5. 7 0
      packages/grafana-ui/index.js
  6. 17 3
      packages/grafana-ui/package.json
  7. 54 0
      packages/grafana-ui/rollup.config.ts
  8. 2 2
      packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx
  9. 2 23
      packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx
  10. 1 1
      packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx
  11. 17 3
      packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx
  12. 2 1
      packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx
  13. 1 2
      packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx
  14. 9 0
      packages/grafana-ui/src/components/ColorPicker/warnAboutColorPickerPropsDeprecation.ts
  15. 2 2
      packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx
  16. 1 1
      packages/grafana-ui/src/components/FormField/FormField.tsx
  17. 4 4
      packages/grafana-ui/src/components/Gauge/Gauge.tsx
  18. 1 1
      packages/grafana-ui/src/components/Select/Select.tsx
  19. 2 2
      packages/grafana-ui/src/components/Switch/Switch.tsx
  20. 3 1
      packages/grafana-ui/src/components/ValueMappingsEditor/MappingRow.tsx
  21. 1 1
      packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.tsx
  22. 0 1
      packages/grafana-ui/src/index.ts
  23. 2 2
      packages/grafana-ui/src/themes/ThemeContext.tsx
  24. 15 0
      packages/grafana-ui/src/themes/getTheme.ts
  25. 3 13
      packages/grafana-ui/src/themes/index.ts
  26. 12 8
      packages/grafana-ui/src/utils/colors.ts
  27. 2 2
      packages/grafana-ui/src/utils/namedColorsPalette.ts
  28. 2 2
      packages/grafana-ui/src/utils/processTimeSeries.ts
  29. 1 1
      packages/grafana-ui/src/utils/storybook/withTheme.tsx
  30. 12 0
      packages/grafana-ui/tsconfig.build.json
  31. 4 2
      packages/grafana-ui/tsconfig.json
  32. 1 1
      public/app/core/utils/explore.ts
  33. 1 0
      scripts/cli/index.d.ts
  34. 23 12
      scripts/cli/index.ts
  35. 10 1
      scripts/cli/tasks/core.start.ts
  36. 103 0
      scripts/cli/tasks/grafanaui.build.ts
  37. 133 0
      scripts/cli/tasks/grafanaui.release.ts
  38. 14 0
      scripts/cli/utils/cwd.ts
  39. 6 0
      scripts/cli/utils/execTask.ts
  40. 7 0
      scripts/cli/utils/startSpinner.ts
  41. 279 25
      yarn.lock

+ 6 - 0
.gitignore

@@ -78,3 +78,9 @@ debug.test
 
 /scripts/build/release_publisher/release_publisher
 *.patch
+
+
+# Ignoring frontend packages specifics
+/packages/**/dist
+/packages/**/compiled
+/packages/**/.rpt2_cache

+ 9 - 1
package.json

@@ -22,6 +22,7 @@
     "@types/commander": "^2.12.2",
     "@types/d3": "^4.10.1",
     "@types/enzyme": "^3.1.13",
+    "@types/inquirer": "^0.0.43",
     "@types/jest": "^23.3.2",
     "@types/jquery": "^1.10.35",
     "@types/node": "^8.0.31",
@@ -47,6 +48,7 @@
     "enzyme-to-json": "^3.3.4",
     "es6-promise": "^3.0.2",
     "es6-shim": "^0.35.3",
+    "execa": "^1.0.0",
     "expect.js": "~0.2.0",
     "expose-loader": "^0.7.3",
     "file-loader": "^1.1.11",
@@ -70,6 +72,7 @@
     "html-webpack-harddisk-plugin": "^0.2.0",
     "html-webpack-plugin": "^3.2.0",
     "husky": "^1.3.1",
+    "inquirer": "^6.2.2",
     "jest": "^23.6.0",
     "jest-date-mock": "^1.0.6",
     "lint-staged": "^8.1.3",
@@ -83,6 +86,7 @@
     "node-sass": "^4.11.0",
     "npm": "^5.4.2",
     "optimize-css-assets-webpack-plugin": "^4.0.2",
+    "ora": "^3.1.0",
     "phantomjs-prebuilt": "^2.1.15",
     "postcss-browser-reporter": "^0.5.0",
     "postcss-loader": "^2.0.6",
@@ -92,8 +96,10 @@
     "react-test-renderer": "^16.5.0",
     "redux-mock-store": "^1.5.3",
     "regexp-replace-loader": "^1.0.1",
+    "rimraf": "^2.6.3",
     "sass-lint": "^1.10.2",
     "sass-loader": "^7.0.1",
+    "semver": "^5.6.0",
     "sinon": "1.17.6",
     "style-loader": "^0.21.0",
     "systemjs": "0.20.19",
@@ -129,7 +135,9 @@
     "api-tests": "jest --notify --watch --config=tests/api/jest.js",
     "storybook": "cd packages/grafana-ui && yarn storybook",
     "themes:generate": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/generateSassVariableFiles.ts",
-    "prettier:check": "prettier --list-different \"**/*.{ts,tsx,scss}\""
+    "prettier:check": "prettier --list-different \"**/*.{ts,tsx,scss}\"",
+    "gui:build": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts --build",
+    "gui:release": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts --release"
   },
   "husky": {
     "hooks": {

+ 4 - 0
packages/grafana-ui/CHANGELOG.md

@@ -0,0 +1,4 @@
+# 1.0.0-alpha.0 (2019-02-21)
+
+First public release
+

+ 12 - 2
packages/grafana-ui/README.md

@@ -1,3 +1,13 @@
-# Grafana (WIP) shared component library
+# Grafana UI components library
 
-Used by internal & external plugins.
+@grafana/ui is a collection of components used by [Grafana](https://github.com/grafana/grafana)
+
+Our goal is to deliver Grafana's common UI elements for plugins developers and contributors.
+
+See [package source](https://github.com/grafana/grafana/tree/master/packages/grafana-ui) for more details.
+
+## Installation
+
+`yarn add @grafana/ui`
+
+`npm install @grafana/ui`

+ 7 - 0
packages/grafana-ui/index.js

@@ -0,0 +1,7 @@
+'use strict'
+
+if (process.env.NODE_ENV === 'production') {
+  module.exports = require('./index.production.js');
+} else {
+  module.exports = require('./index.development.js');
+}

+ 17 - 3
packages/grafana-ui/package.json

@@ -1,12 +1,19 @@
 {
   "name": "@grafana/ui",
-  "version": "1.0.0",
-  "description": "",
+  "version": "0.0.0",
+  "description": "Grafana Components Library",
+  "keywords": [
+    "typescript",
+    "react",
+    "react-component"
+  ],
   "main": "src/index.ts",
   "scripts": {
     "tslint": "tslint -c tslint.json --project tsconfig.json",
     "typecheck": "tsc --noEmit",
-    "storybook": "start-storybook -p 9001 -c .storybook -s ../../public"
+    "storybook": "start-storybook -p 9001 -c .storybook -s ../../public",
+    "clean": "rimraf ./dist ./compiled",
+    "build": "rollup -c rollup.config.ts"
   },
   "author": "Grafana Labs",
   "license": "Apache-2.0",
@@ -53,6 +60,13 @@
     "react-docgen-typescript-loader": "^3.0.0",
     "react-docgen-typescript-webpack-plugin": "^1.1.0",
     "react-test-renderer": "^16.7.0",
+    "rollup": "^1.1.2",
+    "rollup-plugin-commonjs": "^9.2.0",
+    "rollup-plugin-node-resolve": "^4.0.0",
+    "rollup-plugin-sourcemaps": "^0.4.2",
+    "rollup-plugin-terser": "^4.0.4",
+    "rollup-plugin-typescript2": "^0.19.2",
+    "rollup-plugin-visualizer": "^0.9.2",
     "typescript": "^3.2.2"
   },
   "resolutions": {

+ 54 - 0
packages/grafana-ui/rollup.config.ts

@@ -0,0 +1,54 @@
+import resolve from 'rollup-plugin-node-resolve';
+import commonjs from 'rollup-plugin-commonjs';
+import sourceMaps from 'rollup-plugin-sourcemaps';
+import { terser } from 'rollup-plugin-terser';
+
+const pkg = require('./package.json');
+
+const libraryName = pkg.name;
+
+const buildCjsPackage = ({ env }) => {
+  return {
+    input: `compiled/index.js`,
+    output: [
+      {
+        file: `dist/index.${env}.js`,
+        name: libraryName,
+        format: 'cjs',
+        sourcemap: true,
+        exports: 'named',
+        globals: {
+          react: 'React',
+          'prop-types': 'PropTypes',
+        },
+      },
+    ],
+    external: ['react', 'react-dom'],
+    plugins: [
+      commonjs({
+        include: /node_modules/,
+        namedExports: {
+          '../../node_modules/lodash/lodash.js': [
+            'flatten',
+            'find',
+            'upperFirst',
+            'debounce',
+            'isNil',
+            'isNumber',
+            'flattenDeep',
+            'map',
+            'chunk',
+            'sortBy',
+            'uniqueId',
+            'zip',
+          ],
+          '../../node_modules/react-color/lib/components/common': ['Saturation', 'Hue', 'Alpha'],
+        },
+      }),
+      resolve(),
+      sourceMaps(),
+      env === 'production' && terser(),
+    ],
+  };
+};
+export default [buildCjsPackage({ env: 'development' }), buildCjsPackage({ env: 'production' })];

+ 2 - 2
packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx

@@ -1,7 +1,7 @@
 import React from 'react';
-import { ColorPickerProps } from './ColorPicker';
+import { ColorPickerProps } from './ColorPickerPopover';
 import tinycolor from 'tinycolor2';
-import { debounce } from 'lodash';
+import debounce from 'lodash/debounce';
 
 interface ColorInputState {
   previousColor: string;

+ 2 - 23
packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx

@@ -1,32 +1,11 @@
 import React, { Component, createRef } from 'react';
 import { PopperController } from '../Tooltip/PopperController';
 import { Popper } from '../Tooltip/Popper';
-import { ColorPickerPopover } from './ColorPickerPopover';
-import { Themeable } from '../../types';
+import { ColorPickerPopover, ColorPickerProps, ColorPickerChangeHandler } from './ColorPickerPopover';
 import { getColorFromHexRgbOrName } from '../../utils/namedColorsPalette';
 import { SeriesColorPickerPopover } from './SeriesColorPickerPopover';
-import propDeprecationWarning from '../../utils/propDeprecationWarning';
-import { withTheme } from '../../themes/ThemeContext';
-type ColorPickerChangeHandler = (color: string) => void;
-
-export interface ColorPickerProps extends Themeable {
-  color: string;
-  onChange: ColorPickerChangeHandler;
-
-  /**
-   * @deprecated Use onChange instead
-   */
-  onColorChange?: ColorPickerChangeHandler;
-  enableNamedColors?: boolean;
-  children?: JSX.Element;
-}
 
-export const warnAboutColorPickerPropsDeprecation = (componentName: string, props: ColorPickerProps) => {
-  const { onColorChange } = props;
-  if (onColorChange) {
-    propDeprecationWarning(componentName, 'onColorChange', 'onChange');
-  }
-};
+import { withTheme } from '../../themes/ThemeContext';
 
 export const colorPickerFactory = <T extends ColorPickerProps>(
   popover: React.ComponentType<T>,

+ 1 - 1
packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx

@@ -3,7 +3,7 @@ import { mount, ReactWrapper } from 'enzyme';
 import { ColorPickerPopover } from './ColorPickerPopover';
 import { getColorDefinitionByName, getNamedColorPalette } from '../../utils/namedColorsPalette';
 import { ColorSwatch } from './NamedColorsGroup';
-import { flatten } from 'lodash';
+import flatten from 'lodash/flatten';
 import { GrafanaThemeType } from '../../types';
 import { getTheme } from '../../themes';
 

+ 17 - 3
packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx

@@ -1,23 +1,37 @@
 import React from 'react';
 import { NamedColorsPalette } from './NamedColorsPalette';
 import { getColorName, getColorFromHexRgbOrName } from '../../utils/namedColorsPalette';
-import { ColorPickerProps, warnAboutColorPickerPropsDeprecation } from './ColorPicker';
 import { PopperContentProps } from '../Tooltip/PopperController';
 import SpectrumPalette from './SpectrumPalette';
-import { GrafanaThemeType } from '../../types/theme';
+import { GrafanaThemeType, Themeable } from '../../types/theme';
+import { warnAboutColorPickerPropsDeprecation } from './warnAboutColorPickerPropsDeprecation';
 
+export type ColorPickerChangeHandler = (color: string) => void;
+
+export interface ColorPickerProps extends Themeable {
+  color: string;
+  onChange: ColorPickerChangeHandler;
+
+  /**
+   * @deprecated Use onChange instead
+   */
+  onColorChange?: ColorPickerChangeHandler;
+  enableNamedColors?: boolean;
+  children?: JSX.Element;
+}
 export interface Props<T> extends ColorPickerProps, PopperContentProps {
   customPickers?: T;
 }
 
 type PickerType = 'palette' | 'spectrum';
 
-interface CustomPickersDescriptor {
+export interface CustomPickersDescriptor {
   [key: string]: {
     tabComponent: React.ComponentType<ColorPickerProps>;
     name: string;
   };
 }
+
 interface State<T> {
   activePicker: PickerType | keyof T;
 }

+ 2 - 1
packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx

@@ -2,7 +2,8 @@ import React, { FunctionComponent } from 'react';
 import { Themeable } from '../../types';
 import { ColorDefinition, getColorForTheme } from '../../utils/namedColorsPalette';
 import { Color } from 'csstype';
-import { find, upperFirst } from 'lodash';
+import upperFirst from 'lodash/upperFirst';
+import find from 'lodash/find';
 import { selectThemeVariant } from '../../themes/selectThemeVariant';
 
 type ColorChangeHandler = (color: ColorDefinition) => void;

+ 1 - 2
packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx

@@ -1,7 +1,6 @@
 import React, { FunctionComponent } from 'react';
 
-import { ColorPickerPopover } from './ColorPickerPopover';
-import { ColorPickerProps } from './ColorPicker';
+import { ColorPickerPopover, ColorPickerProps } from './ColorPickerPopover';
 import { PopperContentProps } from '../Tooltip/PopperController';
 import { Switch } from '../Switch/Switch';
 import { withTheme } from '../../themes/ThemeContext';

+ 9 - 0
packages/grafana-ui/src/components/ColorPicker/warnAboutColorPickerPropsDeprecation.ts

@@ -0,0 +1,9 @@
+import propDeprecationWarning from '../../utils/propDeprecationWarning';
+import { ColorPickerProps } from './ColorPickerPopover';
+
+export const warnAboutColorPickerPropsDeprecation = (componentName: string, props: ColorPickerProps) => {
+  const { onColorChange } = props;
+  if (onColorChange) {
+    propDeprecationWarning(componentName, 'onColorChange', 'onChange');
+  }
+};

+ 2 - 2
packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx

@@ -1,5 +1,5 @@
 import React, { PureComponent } from 'react';
-import _ from 'lodash';
+import isNil from 'lodash/isNil';
 import classNames from 'classnames';
 import Scrollbars from 'react-custom-scrollbars';
 
@@ -41,7 +41,7 @@ export class CustomScrollbar extends PureComponent<Props> {
   updateScroll() {
     const ref = this.ref.current;
 
-    if (ref && !_.isNil(this.props.scrollTop)) {
+    if (ref && !isNil(this.props.scrollTop)) {
       if (this.props.scrollTop > 10000) {
         ref.scrollToBottom();
       } else {

+ 1 - 1
packages/grafana-ui/src/components/FormField/FormField.tsx

@@ -1,5 +1,5 @@
 import React, { InputHTMLAttributes, FunctionComponent } from 'react';
-import { FormLabel } from '..';
+import { FormLabel } from '../FormLabel/FormLabel';
 
 export interface Props extends InputHTMLAttributes<HTMLInputElement> {
   label: string;

+ 4 - 4
packages/grafana-ui/src/components/Gauge/Gauge.tsx

@@ -1,10 +1,10 @@
 import React, { PureComponent } from 'react';
 import $ from 'jquery';
-
-import { ValueMapping, Threshold, BasicGaugeColor, GrafanaThemeType } from '../../types';
 import { getMappedValue } from '../../utils/valueMappings';
-import { getColorFromHexRgbOrName, getValueFormat } from '../../utils';
-import { Themeable } from '../../index';
+import { getColorFromHexRgbOrName } from '../../utils/namedColorsPalette';
+import { Themeable, GrafanaThemeType } from '../../types/theme';
+import { ValueMapping, Threshold, BasicGaugeColor } from '../../types/panel';
+import { getValueFormat } from '../../utils/valueFormats/valueFormats';
 
 type TimeSeriesValue = string | number | null;
 

+ 1 - 1
packages/grafana-ui/src/components/Select/Select.tsx

@@ -16,7 +16,7 @@ import SelectOptionGroup from './SelectOptionGroup';
 import IndicatorsContainer from './IndicatorsContainer';
 import NoOptionsMessage from './NoOptionsMessage';
 import resetSelectStyles from './resetSelectStyles';
-import { CustomScrollbar } from '..';
+import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
 
 export interface SelectOptionItem {
   label?: string;

+ 2 - 2
packages/grafana-ui/src/components/Switch/Switch.tsx

@@ -1,5 +1,5 @@
 import React, { PureComponent } from 'react';
-import _ from 'lodash';
+import uniqueId from 'lodash/uniqueId';
 
 export interface Props {
   label: string;
@@ -17,7 +17,7 @@ export interface State {
 
 export class Switch extends PureComponent<Props, State> {
   state = {
-    id: _.uniqueId('check-'),
+    id: uniqueId(),
   };
 
   internalOnChange = (event: React.FormEvent<HTMLInputElement>) => {

+ 3 - 1
packages/grafana-ui/src/components/ValueMappingsEditor/MappingRow.tsx

@@ -1,7 +1,9 @@
 import React, { ChangeEvent, PureComponent } from 'react';
 
 import { MappingType, ValueMapping } from '../../types';
-import { FormField, FormLabel, Select } from '..';
+import { Select } from '../Select/Select';
+import { FormField } from '../FormField/FormField';
+import { FormLabel } from '../FormLabel/FormLabel';
 
 export interface Props {
   valueMapping: ValueMapping;

+ 1 - 1
packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.tsx

@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
 
 import MappingRow from './MappingRow';
 import { MappingType, ValueMapping } from '../../types';
-import { PanelOptionsGroup } from '..';
+import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup';
 
 export interface Props {
   valueMappings: ValueMapping[];

+ 0 - 1
packages/grafana-ui/src/index.ts

@@ -2,4 +2,3 @@ export * from './components';
 export * from './types';
 export * from './utils';
 export * from './themes';
-export * from './themes/ThemeContext';

+ 2 - 2
packages/grafana-ui/src/themes/ThemeContext.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
-import { GrafanaThemeType, Themeable } from '../types';
-import { getTheme } from './index';
+import { getTheme } from './getTheme';
+import { GrafanaThemeType, Themeable } from '../types/theme';
 
 type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
 type Subtract<T, K> = Omit<T, keyof K>;

+ 15 - 0
packages/grafana-ui/src/themes/getTheme.ts

@@ -0,0 +1,15 @@
+import darkTheme from './dark';
+import lightTheme from './light';
+import { GrafanaTheme } from '../types/theme';
+
+let themeMock: ((name?: string) => GrafanaTheme) | null;
+
+export const getTheme = (name?: string) =>
+  (themeMock && themeMock(name)) || (name === 'light' ? lightTheme : darkTheme);
+
+export const mockTheme = (mock: (name?: string) => GrafanaTheme) => {
+  themeMock = mock;
+  return () => {
+    themeMock = null;
+  };
+};

+ 3 - 13
packages/grafana-ui/src/themes/index.ts

@@ -1,14 +1,4 @@
-import darkTheme from './dark';
-import lightTheme from './light';
-import { GrafanaTheme } from '../types/theme';
+import { ThemeContext, withTheme } from './ThemeContext';
+import { getTheme, mockTheme } from './getTheme';
 
-let themeMock: ((name?: string) => GrafanaTheme) | null;
-
-export let getTheme = (name?: string) => (themeMock && themeMock(name)) || (name === 'light' ? lightTheme : darkTheme);
-
-export const mockTheme = (mock: (name?: string) => GrafanaTheme) => {
-  themeMock = mock;
-  return () => {
-    themeMock = null;
-  };
-};
+export { ThemeContext, withTheme, mockTheme, getTheme };

+ 12 - 8
packages/grafana-ui/src/utils/colors.ts

@@ -1,4 +1,8 @@
-import _ from 'lodash';
+import map from 'lodash/map';
+import sortBy from 'lodash/sortBy';
+import flattenDeep from 'lodash/flattenDeep';
+import chunk from 'lodash/chunk';
+import zip from 'lodash/zip';
 import tinycolor from 'tinycolor2';
 
 export const PALETTE_ROWS = 4;
@@ -69,16 +73,16 @@ export const colors = [
 ];
 
 function sortColorsByHue(hexColors: string[]) {
-  const hslColors = _.map(hexColors, hexToHsl);
+  const hslColors = map(hexColors, hexToHsl);
 
-  const sortedHSLColors = _.sortBy(hslColors, ['h']);
-  const chunkedHSLColors = _.chunk(sortedHSLColors, PALETTE_ROWS);
-  const sortedChunkedHSLColors = _.map(chunkedHSLColors, chunk => {
-    return _.sortBy(chunk, 'l');
+  const sortedHSLColors = sortBy(hslColors, ['h']);
+  const chunkedHSLColors = chunk(sortedHSLColors, PALETTE_ROWS);
+  const sortedChunkedHSLColors = map(chunkedHSLColors, chunk => {
+    return sortBy(chunk, 'l');
   });
-  const flattenedZippedSortedChunkedHSLColors = _.flattenDeep(_.zip(...sortedChunkedHSLColors));
+  const flattenedZippedSortedChunkedHSLColors = flattenDeep(zip(...sortedChunkedHSLColors));
 
-  return _.map(flattenedZippedSortedChunkedHSLColors, hslToHex);
+  return map(flattenedZippedSortedChunkedHSLColors, hslToHex);
 }
 
 function hexToHsl(color: string) {

+ 2 - 2
packages/grafana-ui/src/utils/namedColorsPalette.ts

@@ -1,5 +1,5 @@
-import { flatten } from 'lodash';
-import { GrafanaThemeType } from '../types';
+import flatten from 'lodash/flatten';
+import { GrafanaThemeType } from '../types/theme';
 import tinycolor from 'tinycolor2';
 
 type Hue = 'green' | 'yellow' | 'red' | 'blue' | 'orange' | 'purple';

+ 2 - 2
packages/grafana-ui/src/utils/processTimeSeries.ts

@@ -1,5 +1,5 @@
 // Libraries
-import _ from 'lodash';
+import isNumber from 'lodash/isNumber';
 
 import { colors } from './colors';
 
@@ -75,7 +75,7 @@ export function processTimeSeries({ timeSeries, nullValueMode }: Options): TimeS
       }
 
       if (currentValue !== null) {
-        if (_.isNumber(currentValue)) {
+        if (isNumber(currentValue)) {
           total += currentValue;
           allIsNull = false;
           nonNulls++;

+ 1 - 1
packages/grafana-ui/src/utils/storybook/withTheme.tsx

@@ -2,7 +2,7 @@ import React from 'react';
 import { RenderFunction } from '@storybook/react';
 import { ThemeContext } from '../../themes/ThemeContext';
 import { select } from '@storybook/addon-knobs';
-import { getTheme } from '../../themes';
+import { getTheme } from '../../themes/index';
 import { GrafanaThemeType } from '../../types';
 
 const ThemableStory: React.FunctionComponent<{}> = ({ children }) => {

+ 12 - 0
packages/grafana-ui/tsconfig.build.json

@@ -0,0 +1,12 @@
+{
+  "extends": "./tsconfig.json",
+  "exclude": [
+    "dist",
+    "node_modules",
+    "src/utils/storybook",
+    "**/*.test.ts",
+    "**/*.test.tsx",
+    "**/*.story.tsx",
+    "**/*.tmpl.ts"
+  ]
+}

+ 4 - 2
packages/grafana-ui/tsconfig.json

@@ -5,13 +5,15 @@
   "compilerOptions": {
     "rootDirs": [".", "stories"],
     "module": "esnext",
-    "outDir": "dist",
+    "outDir": "compiled",
     "declaration": true,
+    "declarationDir": "dist",
     "strict": true,
     "alwaysStrict": true,
     "noImplicitAny": true,
     "strictNullChecks": true,
     "typeRoots": ["./node_modules/@types", "types"],
-    "skipLibCheck": true // Temp workaround for Duplicate identifier tsc errors
+    "skipLibCheck": true, // Temp workaround for Duplicate identifier tsc errors,
+    "removeComments": false
   }
 }

+ 1 - 1
public/app/core/utils/explore.ts

@@ -11,7 +11,7 @@ import { colors } from '@grafana/ui';
 import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
 
 // Types
-import { RawTimeRange, IntervalValues, DataQuery, DataSourceApi } from '@grafana/ui/src/types';
+import { RawTimeRange, IntervalValues, DataQuery, DataSourceApi } from '@grafana/ui';
 import TimeSeries from 'app/core/time_series2';
 import {
   ExploreUrlState,

+ 1 - 0
scripts/cli/index.d.ts

@@ -0,0 +1 @@
+export type Task<T> = (options: T) => Promise<void>;

+ 23 - 12
scripts/cli/index.ts

@@ -1,22 +1,33 @@
 import program from 'commander';
-import { startTask } from './start';
 import chalk from 'chalk';
+import { execTask } from './utils/execTask';
 
+export type Task<T> = (options: T) => Promise<void>;
+
+// TODO: Refactor to commander commands
+// This will enable us to have command scoped options and limit the ifs below
 program
   .option('-h, --hot', 'Runs front-end with hot reload enabled')
   .option('-t, --theme', 'Watches for theme changes and regenerates variables.scss files')
   .option('-d, --depreciate <scripts>', 'Inform about npm script deprecation', v => v.split(','))
+  .option('-b, --build', 'Created @grafana/ui build')
+  .option('-r, --release', 'Releases @grafana/ui to npm')
   .parse(process.argv);
 
-if (program.depreciate && program.depreciate.length === 2) {
-  console.log(
-    chalk.yellow.bold(
-      `[NPM script depreciation] ${program.depreciate[0]} is deprecated! Use ${program.depreciate[1]} instead!`
-    )
-  );
+if (program.build) {
+  execTask('grafanaui.build');
+} else if (program.release) {
+  execTask('grafanaui.release');
+} else {
+  if (program.depreciate && program.depreciate.length === 2) {
+    console.log(
+      chalk.yellow.bold(
+        `[NPM script depreciation] ${program.depreciate[0]} is deprecated! Use ${program.depreciate[1]} instead!`
+      )
+    );
+  }
+  execTask('core.start', {
+    watchThemes: !!program.theme,
+    hot: !!program.hot,
+  });
 }
-
-startTask({
-  watchThemes: !!program.theme,
-  hot: !!program.hot,
-});

+ 10 - 1
scripts/cli/start.ts → scripts/cli/tasks/core.start.ts

@@ -1,7 +1,14 @@
 import concurrently from 'concurrently';
+import { Task } from '..';
 
-export const startTask = async ({ watchThemes, hot }: { watchThemes: boolean; hot: boolean }) => {
+interface StartTaskOptions {
+  watchThemes: boolean;
+  hot: boolean;
+}
+
+const startTask: Task<StartTaskOptions> = async ({ watchThemes, hot }) => {
   const jobs = [];
+
   if (watchThemes) {
     jobs.push({
       command: 'nodemon -e ts -w ./packages/grafana-ui/src/themes -x yarn run themes:generate',
@@ -30,3 +37,5 @@ export const startTask = async ({ watchThemes, hot }: { watchThemes: boolean; ho
     process.exit(1);
   }
 };
+
+export default startTask;

+ 103 - 0
scripts/cli/tasks/grafanaui.build.ts

@@ -0,0 +1,103 @@
+import execa from 'execa';
+import fs from 'fs';
+import { Task } from '..';
+import { changeCwdToGrafanaUi, restoreCwd } from '../utils/cwd';
+import chalk from 'chalk';
+import { startSpinner } from '../utils/startSpinner';
+
+let distDir, cwd;
+
+const clean = async () => {
+  const spinner = startSpinner('Cleaning');
+  try {
+    await execa('npm', ['run', 'clean']);
+    spinner.succeed();
+  } catch (e) {
+    spinner.fail();
+    throw e;
+  }
+};
+
+const compile = async () => {
+  const spinner = startSpinner('Compiling sources');
+  try {
+    await execa('tsc', ['-p', './tsconfig.build.json']);
+    spinner.succeed();
+  } catch (e) {
+    console.log(e);
+    spinner.fail();
+  }
+};
+
+const rollup = async () => {
+  const spinner = startSpinner('Bundling');
+
+  try {
+    await execa('npm', ['run', 'build']);
+    spinner.succeed();
+  } catch (e) {
+    spinner.fail();
+  }
+};
+
+export const savePackage = async (path, pkg) => {
+  const spinner = startSpinner('Updating package.json');
+
+  return new Promise((resolve, reject) => {
+    fs.writeFile(path, JSON.stringify(pkg, null, 2), err => {
+      if (err) {
+        spinner.fail();
+        console.error(err);
+        reject(err);
+        return;
+      }
+      spinner.succeed();
+      resolve();
+    });
+  });
+};
+
+const preparePackage = async pkg => {
+  pkg.main = 'index.js';
+  pkg.types = 'index.d.ts';
+  await savePackage(`${cwd}/dist/package.json`, pkg);
+};
+
+const moveFiles = async () => {
+  const files = ['README.md', 'CHANGELOG.md', 'index.js'];
+  const spinner = startSpinner(`Moving ${files.join(', ')} files`);
+
+  const promises = files.map(file => {
+    return fs.copyFile(`${cwd}/${file}`, `${distDir}/${file}`, err => {
+      if (err) {
+        console.error(err);
+        return;
+      }
+    });
+  });
+
+  try {
+    await Promise.all(promises);
+    spinner.succeed();
+  } catch (e) {
+    spinner.fail();
+  }
+};
+
+const buildTask: Task<void> = async () => {
+  cwd = changeCwdToGrafanaUi();
+  distDir = `${cwd}/dist`;
+  const pkg = require(`${cwd}/package.json`);
+
+  console.log(chalk.yellow(`Building ${pkg.name} @ ${pkg.version}`));
+
+  await clean();
+  await compile();
+  await rollup();
+  await preparePackage(pkg);
+  await moveFiles();
+
+  restoreCwd();
+};
+
+export default buildTask;

+ 133 - 0
scripts/cli/tasks/grafanaui.release.ts

@@ -0,0 +1,133 @@
+import execa from 'execa';
+import { Task } from '..';
+import { execTask } from '../utils/execTask';
+import { changeCwdToGrafanaUiDist, changeCwdToGrafanaUi } from '../utils/cwd';
+import semver from 'semver';
+import inquirer from 'inquirer';
+import chalk from 'chalk';
+import { startSpinner } from '../utils/startSpinner';
+import { savePackage } from './grafanaui.build';
+
+type VersionBumpType = 'patch' | 'minor' | 'major';
+
+const promptBumpType = async () => {
+  return inquirer.prompt<{ type: VersionBumpType }>([
+    {
+      type: 'list',
+      message: 'Select version bump',
+      name: 'type',
+      choices: ['patch', 'minor', 'major'],
+      validate: answer => {
+        if (answer.length < 1) {
+          return 'You must choose something';
+        }
+
+        return true;
+      },
+    },
+  ]);
+};
+
+const promptPrereleaseId = async () => {
+  return inquirer.prompt<{ id: string }>([
+    {
+      type: 'list',
+      message: 'Is this a prerelease?',
+      name: 'id',
+      choices: ['no', 'alpha', 'beta'],
+      validate: answer => {
+        if (answer.length < 1) {
+          return 'You must choose something';
+        }
+
+        return true;
+      },
+    },
+  ]);
+};
+
+const promptConfirm = async (message?: string) => {
+  return inquirer.prompt<{ confirmed: boolean }>([
+    {
+      type: 'confirm',
+      message: message || 'Is that correct?',
+      name: 'confirmed',
+      default: false,
+    },
+  ]);
+};
+
+const bumpVersion = async (version: string) => {
+  const spinner = startSpinner(`Saving version ${version} to package.json`);
+  changeCwdToGrafanaUi();
+
+  try {
+    await execa('npm', ['version', version]);
+    spinner.succeed();
+  } catch (e) {
+    console.log(e);
+    spinner.fail();
+  }
+
+  changeCwdToGrafanaUiDist();
+  const pkg = require(`${process.cwd()}/package.json`);
+  pkg.version = version;
+  await savePackage(`${process.cwd()}/package.json`, pkg);
+};
+
+const publishPackage = async (name: string, version: string) => {
+  changeCwdToGrafanaUiDist();
+  console.log(chalk.yellowBright.bold(`\nReview dist package.json before proceeding!\n`));
+  const { confirmed } = await promptConfirm('Are you ready to publish to npm?');
+
+  if (!confirmed) {
+    process.exit();
+  }
+
+  const spinner = startSpinner(`Publishing ${name} @ ${version} to npm registry...`);
+
+  try {
+    await execa('npm', ['publish', '--access', 'public']);
+    spinner.succeed();
+  } catch (e) {
+    console.log(e);
+    spinner.fail();
+    process.exit(1);
+  }
+};
+
+const releaseTask: Task<void> = async () => {
+  await execTask('grafanaui.build');
+  let releaseConfirmed = false;
+  let nextVersion;
+  changeCwdToGrafanaUiDist();
+
+  const pkg = require(`${process.cwd()}/package.json`);
+
+  console.log(`Current version: ${pkg.version}`);
+
+  do {
+    const { type } = await promptBumpType();
+    const { id } = await promptPrereleaseId();
+
+    if (id !== 'no') {
+      nextVersion = semver.inc(pkg.version, `pre${type}`, id);
+    } else {
+      nextVersion = semver.inc(pkg.version, type);
+    }
+
+    console.log(chalk.yellowBright.bold(`You are going to release a new version of ${pkg.name}`));
+    console.log(chalk.green(`Version bump: ${pkg.version} ->`), chalk.bold.yellowBright(`${nextVersion}`));
+    const { confirmed } = await promptConfirm();
+
+    releaseConfirmed = confirmed;
+  } while (!releaseConfirmed);
+
+  await bumpVersion(nextVersion);
+  await publishPackage(pkg.name, nextVersion);
+
+  console.log(chalk.green(`\nVersion ${nextVersion} of ${pkg.name} succesfully released!`));
+  console.log(chalk.yellow(`\nUpdated @grafana/ui/package.json with version bump created - COMMIT THIS FILE!`));
+};
+
+export default releaseTask;

+ 14 - 0
scripts/cli/utils/cwd.ts

@@ -0,0 +1,14 @@
+const cwd = process.cwd();
+
+export const changeCwdToGrafanaUi = () => {
+  process.chdir(`${cwd}/packages/grafana-ui`);
+  return process.cwd();
+};
+
+export const changeCwdToGrafanaUiDist = () => {
+  process.chdir(`${cwd}/packages/grafana-ui/dist`);
+};
+
+export const restoreCwd = () => {
+  process.chdir(cwd);
+};

+ 6 - 0
scripts/cli/utils/execTask.ts

@@ -0,0 +1,6 @@
+import { Task } from '..';
+
+export const execTask = async <T>(taskName, options?: T) => {
+  const task = await import(`${__dirname}/../tasks/${taskName}.ts`);
+  return task.default(options) as Task<T>;
+};

+ 7 - 0
scripts/cli/utils/startSpinner.ts

@@ -0,0 +1,7 @@
+import ora from 'ora';
+
+export const startSpinner = (label: string) => {
+  const spinner = new ora(label);
+  spinner.start();
+  return spinner;
+};

+ 279 - 25
yarn.lock

@@ -1740,11 +1740,24 @@
     "@types/cheerio" "*"
     "@types/react" "*"
 
+"@types/estree@0.0.39":
+  version "0.0.39"
+  resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
+  integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
+
 "@types/geojson@*":
   version "7946.0.5"
   resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.5.tgz#9aea839ea5af4b1bc079f1d9fa977d48665e02b0"
   integrity sha512-rLlMXpd3rdlrp0+xsrda/hFfOpIxgqFcRpk005UKbHtcdFK+QXAjhBAPnvO58qF4O1LdDXrcaiJxMgstCIlcaw==
 
+"@types/inquirer@^0.0.43":
+  version "0.0.43"
+  resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-0.0.43.tgz#1eb0bbb4648e6cc568bd396c1e989f620ad01273"
+  integrity sha512-xgyfKZVMFqE8aIKy1xfFVsX2MxyXUNgjgmbF6dRbR3sL+ZM5K4ka/9L4mmTwX8eTeVYtduyXu0gUVwVJa1HbNw==
+  dependencies:
+    "@types/rx" "*"
+    "@types/through" "*"
+
 "@types/jest@^23.3.2":
   version "23.3.14"
   resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.14.tgz#37daaf78069e7948520474c87b80092ea912520a"
@@ -1756,9 +1769,9 @@
   integrity sha512-SVtqEcudm7yjkTwoRA1gC6CNMhGDdMx4Pg8BPdiqI7bXXdCn1BPmtxgeWYQOgDxrq53/5YTlhq5ULxBEAlWIBg==
 
 "@types/lodash@^4.14.119":
-  version "4.14.120"
-  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.120.tgz#cf265d06f6c7a710db087ed07523ab8c1a24047b"
-  integrity sha512-jQ21kQ120mo+IrDs1nFNVm/AsdFxIx2+vZ347DbogHJPd/JzKNMOqU6HCYin1W6v8l5R9XSO2/e9cxmn7HAnVw==
+  version "4.14.119"
+  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.119.tgz#be847e5f4bc3e35e46d041c394ead8b603ad8b39"
+  integrity sha512-Z3TNyBL8Vd/M9D9Ms2S3LmFq2sSMzahodD6rCS9V2N44HUMINb75jNkSuwAx7eo2ufqTdfOdtGQpNbieUjPQmw==
 
 "@types/node@*":
   version "11.9.0"
@@ -1859,6 +1872,107 @@
   dependencies:
     reselect "*"
 
+"@types/rx-core-binding@*":
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/@types/rx-core-binding/-/rx-core-binding-4.0.4.tgz#d969d32f15a62b89e2862c17b3ee78fe329818d3"
+  integrity sha512-5pkfxnC4w810LqBPUwP5bg7SFR/USwhMSaAeZQQbEHeBp57pjKXRlXmqpMrLJB4y1oglR/c2502853uN0I+DAQ==
+  dependencies:
+    "@types/rx-core" "*"
+
+"@types/rx-core@*":
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/@types/rx-core/-/rx-core-4.0.3.tgz#0b3354b1238cedbe2b74f6326f139dbc7a591d60"
+  integrity sha1-CzNUsSOM7b4rdPYybxOdvHpZHWA=
+
+"@types/rx-lite-aggregates@*":
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/@types/rx-lite-aggregates/-/rx-lite-aggregates-4.0.3.tgz#6efb2b7f3d5f07183a1cb2bd4b1371d7073384c2"
+  integrity sha512-MAGDAHy8cRatm94FDduhJF+iNS5//jrZ/PIfm+QYw9OCeDgbymFHChM8YVIvN2zArwsRftKgE33QfRWvQk4DPg==
+  dependencies:
+    "@types/rx-lite" "*"
+
+"@types/rx-lite-async@*":
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/@types/rx-lite-async/-/rx-lite-async-4.0.2.tgz#27fbf0caeff029f41e2d2aae638b05e91ceb600c"
+  integrity sha512-vTEv5o8l6702ZwfAM5aOeVDfUwBSDOs+ARoGmWAKQ6LOInQ8J4/zjM7ov12fuTpktUKdMQjkeCp07Vd73mPkxw==
+  dependencies:
+    "@types/rx-lite" "*"
+
+"@types/rx-lite-backpressure@*":
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/@types/rx-lite-backpressure/-/rx-lite-backpressure-4.0.3.tgz#05abb19bdf87cc740196c355e5d0b37bb50b5d56"
+  integrity sha512-Y6aIeQCtNban5XSAF4B8dffhIKu6aAy/TXFlScHzSxh6ivfQBQw6UjxyEJxIOt3IT49YkS+siuayM2H/Q0cmgA==
+  dependencies:
+    "@types/rx-lite" "*"
+
+"@types/rx-lite-coincidence@*":
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/@types/rx-lite-coincidence/-/rx-lite-coincidence-4.0.3.tgz#80bd69acc4054a15cdc1638e2dc8843498cd85c0"
+  integrity sha512-1VNJqzE9gALUyMGypDXZZXzR0Tt7LC9DdAZQ3Ou/Q0MubNU35agVUNXKGHKpNTba+fr8GdIdkC26bRDqtCQBeQ==
+  dependencies:
+    "@types/rx-lite" "*"
+
+"@types/rx-lite-experimental@*":
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/@types/rx-lite-experimental/-/rx-lite-experimental-4.0.1.tgz#c532f5cbdf3f2c15da16ded8930d1b2984023cbd"
+  integrity sha1-xTL1y98/LBXaFt7Ykw0bKYQCPL0=
+  dependencies:
+    "@types/rx-lite" "*"
+
+"@types/rx-lite-joinpatterns@*":
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/@types/rx-lite-joinpatterns/-/rx-lite-joinpatterns-4.0.1.tgz#f70fe370518a8432f29158cc92ffb56b4e4afc3e"
+  integrity sha1-9w/jcFGKhDLykVjMkv+1a05K/D4=
+  dependencies:
+    "@types/rx-lite" "*"
+
+"@types/rx-lite-testing@*":
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/@types/rx-lite-testing/-/rx-lite-testing-4.0.1.tgz#21b19d11f4dfd6ffef5a9d1648e9c8879bfe21e9"
+  integrity sha1-IbGdEfTf1v/vWp0WSOnIh5v+Iek=
+  dependencies:
+    "@types/rx-lite-virtualtime" "*"
+
+"@types/rx-lite-time@*":
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/@types/rx-lite-time/-/rx-lite-time-4.0.3.tgz#0eda65474570237598f3448b845d2696f2dbb1c4"
+  integrity sha512-ukO5sPKDRwCGWRZRqPlaAU0SKVxmWwSjiOrLhoQDoWxZWg6vyB9XLEZViKOzIO6LnTIQBlk4UylYV0rnhJLxQw==
+  dependencies:
+    "@types/rx-lite" "*"
+
+"@types/rx-lite-virtualtime@*":
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/@types/rx-lite-virtualtime/-/rx-lite-virtualtime-4.0.3.tgz#4b30cacd0fe2e53af29f04f7438584c7d3959537"
+  integrity sha512-3uC6sGmjpOKatZSVHI2xB1+dedgml669ZRvqxy+WqmGJDVusOdyxcKfyzjW0P3/GrCiN4nmRkLVMhPwHCc5QLg==
+  dependencies:
+    "@types/rx-lite" "*"
+
+"@types/rx-lite@*":
+  version "4.0.6"
+  resolved "https://registry.yarnpkg.com/@types/rx-lite/-/rx-lite-4.0.6.tgz#3c02921c4244074234f26b772241bcc20c18c253"
+  integrity sha512-oYiDrFIcor9zDm0VDUca1UbROiMYBxMLMaM6qzz4ADAfOmA9r1dYEcAFH+2fsPI5BCCjPvV9pWC3X3flbrvs7w==
+  dependencies:
+    "@types/rx-core" "*"
+    "@types/rx-core-binding" "*"
+
+"@types/rx@*":
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/@types/rx/-/rx-4.1.1.tgz#598fc94a56baed975f194574e0f572fd8e627a48"
+  integrity sha1-WY/JSla67ZdfGUV04PVy/Y5iekg=
+  dependencies:
+    "@types/rx-core" "*"
+    "@types/rx-core-binding" "*"
+    "@types/rx-lite" "*"
+    "@types/rx-lite-aggregates" "*"
+    "@types/rx-lite-async" "*"
+    "@types/rx-lite-backpressure" "*"
+    "@types/rx-lite-coincidence" "*"
+    "@types/rx-lite-experimental" "*"
+    "@types/rx-lite-joinpatterns" "*"
+    "@types/rx-lite-testing" "*"
+    "@types/rx-lite-time" "*"
+    "@types/rx-lite-virtualtime" "*"
+
 "@types/storybook__addon-actions@^3.4.1":
   version "3.4.1"
   resolved "https://registry.yarnpkg.com/@types/storybook__addon-actions/-/storybook__addon-actions-3.4.1.tgz#8f90d76b023b58ee794170f2fe774a3fddda2c1d"
@@ -1905,6 +2019,13 @@
   resolved "https://registry.yarnpkg.com/@types/tether/-/tether-1.4.4.tgz#0fde1ccbd2f1fad74f8f465fe6227ff3b7bff634"
   integrity sha512-6qhsFJVMuMqaQRVyQVi3zUBLfKYyryktL0ZP0Z3zegzeQ7WKm0PZNCdl3JsaitJbzqaoQ9qsFKMfaj5MiMfcSQ==
 
+"@types/through@*":
+  version "0.0.29"
+  resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.29.tgz#72943aac922e179339c651fa34a4428a4d722f93"
+  integrity sha512-9a7C5VHh+1BKblaYiq+7Tfc+EOmjMdZaD1MYtkQjSoxgB69tBjW98ry6SKsi4zEIWztLOMRuL87A3bdT/Fc/4w==
+  dependencies:
+    "@types/node" "*"
+
 "@types/tinycolor2@^1.4.1":
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.1.tgz#2f5670c9d1d6e558897a810ed284b44918fc1253"
@@ -4381,6 +4502,11 @@ builtin-modules@^1.0.0, builtin-modules@^1.1.1:
   resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
   integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
 
+builtin-modules@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.0.0.tgz#1e587d44b006620d90286cc7a9238bbc6129cab1"
+  integrity sha512-hMIeU4K2ilbXV6Uv93ZZ0Avg/M91RaKXucQ+4me2Do1txxBDyDZWCBa5bJSLqoNTRpXTLwEzIk1KmloenDDjhg==
+
 builtin-status-codes@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
@@ -4848,6 +4974,11 @@ cli-cursor@^2.0.0, cli-cursor@^2.1.0:
   dependencies:
     restore-cursor "^2.0.0"
 
+cli-spinners@^1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a"
+  integrity sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==
+
 cli-table2@~0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/cli-table2/-/cli-table2-0.2.0.tgz#2d1ef7f218a0e786e214540562d4bd177fe32d97"
@@ -7097,6 +7228,11 @@ estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
   resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
   integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=
 
+estree-walker@^0.5.2:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.5.2.tgz#d3850be7529c9580d815600b53126515e146dd39"
+  integrity sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig==
+
 esutils@^2.0.0, esutils@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
@@ -7795,6 +7931,15 @@ fs-constants@^1.0.0:
   resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
   integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
 
+fs-extra@7.0.1, fs-extra@^7.0.1:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
+  integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
+  dependencies:
+    graceful-fs "^4.1.2"
+    jsonfile "^4.0.0"
+    universalify "^0.1.0"
+
 fs-extra@^0.30.0:
   version "0.30.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0"
@@ -7824,15 +7969,6 @@ fs-extra@^3.0.1:
     jsonfile "^3.0.0"
     universalify "^0.1.0"
 
-fs-extra@^7.0.1:
-  version "7.0.1"
-  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
-  integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
-  dependencies:
-    graceful-fs "^4.1.2"
-    jsonfile "^4.0.0"
-    universalify "^0.1.0"
-
 fs-minipass@^1.2.5:
   version "1.2.5"
   resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d"
@@ -9211,7 +9347,7 @@ inquirer@^5.2.0:
     strip-ansi "^4.0.0"
     through "^2.3.6"
 
-inquirer@^6.0.0, inquirer@^6.2.0:
+inquirer@^6.0.0, inquirer@^6.2.0, inquirer@^6.2.2:
   version "6.2.2"
   resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.2.tgz#46941176f65c9eb20804627149b743a218f25406"
   integrity sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==
@@ -9523,6 +9659,11 @@ is-lower-case@^1.1.0:
   dependencies:
     lower-case "^1.1.0"
 
+is-module@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
+  integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=
+
 is-my-ip-valid@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824"
@@ -10189,6 +10330,14 @@ jest-worker@^23.2.0:
   dependencies:
     merge-stream "^1.0.1"
 
+jest-worker@^24.0.0:
+  version "24.0.0"
+  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.0.0.tgz#3d3483b077bf04f412f47654a27bba7e947f8b6d"
+  integrity sha512-s64/OThpfQvoCeHG963MiEZOAAxu8kHsaL/rCMF7lpdzo7vgF0CtPml9hfguOMgykgH/eOm4jFP4ibfHLruytg==
+  dependencies:
+    merge-stream "^1.0.1"
+    supports-color "^6.1.0"
+
 jest@^23.6.0:
   version "23.6.0"
   resolved "https://registry.yarnpkg.com/jest/-/jest-23.6.0.tgz#ad5835e923ebf6e19e7a1d7529a432edfee7813d"
@@ -11050,6 +11199,13 @@ lru-cache@^5.1.1:
   dependencies:
     yallist "^3.0.2"
 
+magic-string@^0.25.1:
+  version "0.25.2"
+  resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.2.tgz#139c3a729515ec55e96e69e82a11fe890a293ad9"
+  integrity sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==
+  dependencies:
+    sourcemap-codec "^1.4.4"
+
 make-dir@^1.0.0, make-dir@^1.1.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
@@ -12525,7 +12681,7 @@ opener@~1.4.3:
   resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
   integrity sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=
 
-opn@5.4.0, opn@^5.1.0, opn@^5.4.0:
+opn@5.4.0, opn@^5.1.0, opn@^5.3.0, opn@^5.4.0:
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/opn/-/opn-5.4.0.tgz#cb545e7aab78562beb11aa3bfabc7042e1761035"
   integrity sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==
@@ -12560,6 +12716,18 @@ optionator@^0.8.1:
     type-check "~0.3.2"
     wordwrap "~1.0.0"
 
+ora@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/ora/-/ora-3.1.0.tgz#dbedd8c03b5d017fb67083e87ee52f5ec89823ed"
+  integrity sha512-vRBPaNCclUi8pUxRF/G8+5qEQkc6EgzKK1G2ZNJUIGu088Un5qIxFXeDgymvPRM9nmrcUOGzQgS1Vmtz+NtlMw==
+  dependencies:
+    chalk "^2.4.2"
+    cli-cursor "^2.1.0"
+    cli-spinners "^1.3.1"
+    log-symbols "^2.2.0"
+    strip-ansi "^5.0.0"
+    wcwidth "^1.0.1"
+
 ordered-ast-traverse@~1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/ordered-ast-traverse/-/ordered-ast-traverse-1.1.1.tgz#6843a170bc0eee8b520cc8ddc1ddd3aa30fa057c"
@@ -13587,12 +13755,7 @@ preserve@^0.2.0:
   resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
   integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=
 
-prettier@1.16.4:
-  version "1.16.4"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717"
-  integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==
-
-prettier@^1.12.1:
+prettier@1.16.4, prettier@^1.12.1:
   version "1.16.4"
   resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717"
   integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==
@@ -15022,6 +15185,13 @@ resolve@1.1.7, resolve@~1.1.0:
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
   integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
 
+resolve@1.8.1:
+  version "1.8.1"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
+  integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==
+  dependencies:
+    path-parse "^1.0.5"
+
 resolve@1.x, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1:
   version "1.10.0"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba"
@@ -15084,7 +15254,7 @@ right-align@^0.1.1:
   dependencies:
     align-text "^0.1.1"
 
-rimraf@2, rimraf@^2.2.8, rimraf@^2.4.4, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@~2.6.2:
+rimraf@2, rimraf@^2.2.8, rimraf@^2.4.4, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@~2.6.2:
   version "2.6.3"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
   integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
@@ -15104,6 +15274,80 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
     hash-base "^3.0.0"
     inherits "^2.0.1"
 
+rollup-plugin-commonjs@^9.2.0:
+  version "9.2.0"
+  resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.2.0.tgz#4604e25069e0c78a09e08faa95dc32dec27f7c89"
+  integrity sha512-0RM5U4Vd6iHjL6rLvr3lKBwnPsaVml+qxOGaaNUWN1lSq6S33KhITOfHmvxV3z2vy9Mk4t0g4rNlVaJJsNQPWA==
+  dependencies:
+    estree-walker "^0.5.2"
+    magic-string "^0.25.1"
+    resolve "^1.8.1"
+    rollup-pluginutils "^2.3.3"
+
+rollup-plugin-node-resolve@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-4.0.0.tgz#9bc6b8205e9936cc0e26bba2415f1ecf1e64d9b2"
+  integrity sha512-7Ni+/M5RPSUBfUaP9alwYQiIKnKeXCOHiqBpKUl9kwp3jX5ZJtgXAait1cne6pGEVUUztPD6skIKH9Kq9sNtfw==
+  dependencies:
+    builtin-modules "^3.0.0"
+    is-module "^1.0.0"
+    resolve "^1.8.1"
+
+rollup-plugin-sourcemaps@^0.4.2:
+  version "0.4.2"
+  resolved "https://registry.yarnpkg.com/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.4.2.tgz#62125aa94087aadf7b83ef4dfaf629b473135e87"
+  integrity sha1-YhJaqUCHqt97g+9N+vYptHMTXoc=
+  dependencies:
+    rollup-pluginutils "^2.0.1"
+    source-map-resolve "^0.5.0"
+
+rollup-plugin-terser@^4.0.4:
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-4.0.4.tgz#6f661ef284fa7c27963d242601691dc3d23f994e"
+  integrity sha512-wPANT5XKVJJ8RDUN0+wIr7UPd0lIXBo4UdJ59VmlPCtlFsE20AM+14pe+tk7YunCsWEiuzkDBY3QIkSCjtrPXg==
+  dependencies:
+    "@babel/code-frame" "^7.0.0"
+    jest-worker "^24.0.0"
+    serialize-javascript "^1.6.1"
+    terser "^3.14.1"
+
+rollup-plugin-typescript2@^0.19.2:
+  version "0.19.2"
+  resolved "https://registry.yarnpkg.com/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.19.2.tgz#87d9c799cd6e02efbedbba25af12753a1e92b6c2"
+  integrity sha512-DRG7SaYX0QzBIz6rII5nm1UkiceS95r8mJjujugybyIueNF3auvzGTHMK62O7As/0q5RHjXsOguWOUv+KJKLFA==
+  dependencies:
+    fs-extra "7.0.1"
+    resolve "1.8.1"
+    rollup-pluginutils "2.3.3"
+    tslib "1.9.3"
+
+rollup-plugin-visualizer@^0.9.2:
+  version "0.9.2"
+  resolved "https://registry.yarnpkg.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-0.9.2.tgz#bbc8e8e67d5aa3e6c188c5ca0fcfa57234fb9f92"
+  integrity sha512-EHXHLp9Q8v5QdRTSjgio4Alr2MKxCJroLhJunmcH+pWAM5869nI5mdWjk2jp64rjxzEahrMYmfF/G5sbTHIhKw==
+  dependencies:
+    mkdirp "^0.5.1"
+    opn "^5.3.0"
+    source-map "^0.7.3"
+    typeface-oswald "0.0.54"
+
+rollup-pluginutils@2.3.3, rollup-pluginutils@^2.0.1, rollup-pluginutils@^2.3.3:
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.3.3.tgz#3aad9b1eb3e7fe8262820818840bf091e5ae6794"
+  integrity sha512-2XZwja7b6P5q4RZ5FhyX1+f46xi1Z3qBKigLRZ6VTZjwbN0K1IFGMlwm06Uu0Emcre2Z63l77nq/pzn+KxIEoA==
+  dependencies:
+    estree-walker "^0.5.2"
+    micromatch "^2.3.11"
+
+rollup@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.1.2.tgz#8d094b85683b810d0c05a16bd7618cf70d48eba7"
+  integrity sha512-OkdMxqMl8pWoQc5D8y1cIinYQPPLV8ZkfLgCzL6SytXeNA2P7UHynEQXI9tYxuAjAMsSyvRaWnyJDLHMxq0XAg==
+  dependencies:
+    "@types/estree" "0.0.39"
+    "@types/node" "*"
+    acorn "^6.0.5"
+
 rst-selector-parser@^2.2.3:
   version "2.2.3"
   resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91"
@@ -15377,7 +15621,7 @@ sentence-case@^2.1.0:
     no-case "^2.2.0"
     upper-case-first "^1.1.2"
 
-serialize-javascript@^1.4.0:
+serialize-javascript@^1.4.0, serialize-javascript@^1.6.1:
   version "1.6.1"
   resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.6.1.tgz#4d1f697ec49429a847ca6f442a2a755126c4d879"
   integrity sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==
@@ -15908,6 +16152,11 @@ source-map@^0.7.2, source-map@^0.7.3:
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
   integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
 
+sourcemap-codec@^1.4.4:
+  version "1.4.4"
+  resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz#c63ea927c029dd6bd9a2b7fa03b3fec02ad56e9f"
+  integrity sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg==
+
 space-separated-tokens@^1.0.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.2.tgz#e95ab9d19ae841e200808cd96bc7bd0adbbb3412"
@@ -16562,7 +16811,7 @@ terser-webpack-plugin@^1.1.0:
     webpack-sources "^1.1.0"
     worker-farm "^1.5.2"
 
-terser@^3.16.1:
+terser@^3.14.1, terser@^3.16.1:
   version "3.16.1"
   resolved "https://registry.yarnpkg.com/terser/-/terser-3.16.1.tgz#5b0dd4fa1ffd0b0b43c2493b2c364fd179160493"
   integrity sha512-JDJjgleBROeek2iBcSNzOHLKsB/MdDf+E/BOAJ0Tk9r7p9/fVobfv7LMJ/g/k3v9SXdmjZnIlFd5nfn/Rt0Xow==
@@ -16887,7 +17136,7 @@ ts-node@^8.0.2:
     source-map-support "^0.5.6"
     yn "^3.0.0"
 
-tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
+tslib@1.9.3, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
   version "1.9.3"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
   integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
@@ -16987,6 +17236,11 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
+typeface-oswald@0.0.54:
+  version "0.0.54"
+  resolved "https://registry.yarnpkg.com/typeface-oswald/-/typeface-oswald-0.0.54.tgz#1e253011622cdd50f580c04e7d625e7f449763d7"
+  integrity sha512-U1WMNp4qfy4/3khIfHMVAIKnNu941MXUfs3+H9R8PFgnoz42Hh9pboSFztWr86zut0eXC8byalmVhfkiKON/8Q==
+
 typescript@^3.0.3, typescript@^3.2.2:
   version "3.3.3"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.3.tgz#f1657fc7daa27e1a8930758ace9ae8da31403221"
@@ -17539,7 +17793,7 @@ wbuf@^1.1.0, wbuf@^1.7.3:
   dependencies:
     minimalistic-assert "^1.0.0"
 
-wcwidth@^1.0.0:
+wcwidth@^1.0.0, wcwidth@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
   integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=