diff --git a/app/server/README.md b/app/server/README.md new file mode 100644 index 000000000000..37bc6240d2e3 --- /dev/null +++ b/app/server/README.md @@ -0,0 +1,25 @@ +# Storybook for Server + +--- + +Storybook for Server is a UI development environment for your plain HTML snippets rendered by your server backend. +With it, you can visualize different states of your UI components and develop them interactively. + +![Storybook Screenshot](https://github.com/storybookjs/storybook/blob/master/media/storybook-intro.gif) + +Storybook runs outside of your app. +So you can develop UI components in isolation without worrying about app specific dependencies and requirements. + +## Getting Started + +```sh +cd my-app +npx -p @storybook/cli sb init -t server +``` + +For more information visit: [storybook.js.org](https://storybook.js.org) + +--- + +Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. diff --git a/app/server/bin/build.js b/app/server/bin/build.js new file mode 100755 index 000000000000..26142ec0af29 --- /dev/null +++ b/app/server/bin/build.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node + +process.env.NODE_ENV = process.env.NODE_ENV || 'production'; +require('../dist/server/build'); diff --git a/app/server/bin/index.js b/app/server/bin/index.js new file mode 100755 index 000000000000..2e96258ce63d --- /dev/null +++ b/app/server/bin/index.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require('../dist/server'); diff --git a/app/server/package.json b/app/server/package.json new file mode 100644 index 000000000000..9d6ea1d5f6a9 --- /dev/null +++ b/app/server/package.json @@ -0,0 +1,59 @@ +{ + "name": "@storybook/server", + "version": "6.0.0-alpha.4", + "description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.", + "keywords": [ + "storybook" + ], + "homepage": "https://github.com/storybookjs/storybook/tree/master/app/server", + "bugs": { + "url": "https://github.com/storybookjs/storybook/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/storybookjs/storybook.git", + "directory": "app/html" + }, + "license": "MIT", + "files": [ + "bin/**/*", + "dist/**/*", + "README.md", + "*.js", + "*.d.ts" + ], + "main": "dist/client/index.js", + "types": "dist/client/index.d.ts", + "bin": { + "build-storybook": "./bin/build.js", + "start-storybook": "./bin/index.js", + "storybook-server": "./bin/index.js" + }, + "scripts": { + "prepare": "node ../../scripts/prepare.js" + }, + "dependencies": { + "@storybook/addons": "6.0.0-alpha.4", + "@storybook/core": "6.0.0-alpha.4", + "@storybook/node-logger": "^5.2.8", + "@types/webpack-env": "^1.13.9", + "core-js": "^3.0.1", + "global": "^4.3.2", + "regenerator-runtime": "^0.13.3", + "safe-identifier": "^0.3.1", + "ts-dedent": "^1.1.0" + }, + "devDependencies": { + "fs-extra": "^8.0.1" + }, + "peerDependencies": { + "babel-loader": "^7.0.0 || ^8.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "publishConfig": { + "access": "public" + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" +} diff --git a/app/server/src/client/index.ts b/app/server/src/client/index.ts new file mode 100644 index 000000000000..8034a9d6433d --- /dev/null +++ b/app/server/src/client/index.ts @@ -0,0 +1,14 @@ +export { + storiesOf, + setAddon, + addDecorator, + addParameters, + configure, + getStorybook, + forceReRender, + raw, +} from './preview'; + +if (module && module.hot && module.hot.decline) { + module.hot.decline(); +} diff --git a/app/server/src/client/preview/globals.ts b/app/server/src/client/preview/globals.ts new file mode 100644 index 000000000000..90dca4aa3750 --- /dev/null +++ b/app/server/src/client/preview/globals.ts @@ -0,0 +1,3 @@ +import { window } from 'global'; + +window.STORYBOOK_ENV = 'SERVER'; diff --git a/app/server/src/client/preview/index.ts b/app/server/src/client/preview/index.ts new file mode 100644 index 000000000000..bccce6b8ac26 --- /dev/null +++ b/app/server/src/client/preview/index.ts @@ -0,0 +1,43 @@ +import { start } from '@storybook/core/client'; +import { ClientStoryApi, Loadable } from '@storybook/addons'; + +import './globals'; +import { renderMain as render, setFetchStoryHtml } from './render'; +import { StoryFnServerReturnType, IStorybookSection, ConfigureOptionsArgs } from './types'; + +const framework = 'server'; + +interface ClientApi extends ClientStoryApi { + setAddon(addon: any): void; + configure(loader: Loadable, module: NodeModule, options?: ConfigureOptionsArgs): void; + getStorybook(): IStorybookSection[]; + clearDecorators(): void; + forceReRender(): void; + raw: () => any; // todo add type +} + +const api = start(render); + +export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { + return (api.clientApi.storiesOf(kind, m) as ReturnType).addParameters({ + framework, + }); +}; + +const setRenderFetchAndConfigure: ClientApi['configure'] = (loader, module, options) => { + if (options && options.fetchStoryHtml) { + setFetchStoryHtml(options.fetchStoryHtml); + } + api.configure(loader, module, framework); +}; + +export const configure: ClientApi['configure'] = setRenderFetchAndConfigure; +export const { + addDecorator, + addParameters, + clearDecorators, + setAddon, + forceReRender, + getStorybook, + raw, +} = api.clientApi; diff --git a/app/server/src/client/preview/render.ts b/app/server/src/client/preview/render.ts new file mode 100644 index 000000000000..9bb8a94dfe9a --- /dev/null +++ b/app/server/src/client/preview/render.ts @@ -0,0 +1,61 @@ +import { document, fetch, Node } from 'global'; +import dedent from 'ts-dedent'; +import { RenderMainArgs, FetchStoryHtmlType } from './types'; + +const rootElement = document.getElementById('root'); + +let fetchStoryHtml: FetchStoryHtmlType = async (url, path, params) => { + const fetchUrl = new URL(`${url}/${path}`); + fetchUrl.search = new URLSearchParams(params).toString(); + + const response = await fetch(fetchUrl); + return response.text(); +}; + +export async function renderMain({ + storyFn, + id, + selectedKind, + selectedStory, + showMain, + showError, + forceRender, + parameters, +}: RenderMainArgs) { + const storyParams = storyFn(); + + const { + server: { url, id: storyId, params }, + } = parameters; + + const fetchId = storyId || id; + const fetchParams = { ...params, ...storyParams }; + const element = await fetchStoryHtml(url, fetchId, fetchParams); + + showMain(); + if (typeof element === 'string') { + rootElement.innerHTML = element; + } else if (element instanceof Node) { + // Don't re-mount the element if it didn't change and neither did the story + if (rootElement.firstChild === element && forceRender === true) { + return; + } + + rootElement.innerHTML = ''; + rootElement.appendChild(element); + } else { + showError({ + title: `Expecting an HTML snippet or DOM node from the story: "${selectedStory}" of "${selectedKind}".`, + description: dedent` + Did you forget to return the HTML snippet from the story? + Use "() => " or when defining the story. + `, + }); + } +} + +export const setFetchStoryHtml: any = (fetchHtml: FetchStoryHtmlType) => { + if (fetchHtml !== undefined) { + fetchStoryHtml = fetchHtml; + } +}; diff --git a/app/server/src/client/preview/types.ts b/app/server/src/client/preview/types.ts new file mode 100644 index 000000000000..c05f54ca5f47 --- /dev/null +++ b/app/server/src/client/preview/types.ts @@ -0,0 +1,35 @@ +import { StoryFn } from '@storybook/addons'; + +export type StoryFnServerReturnType = any; + +export type FetchStoryHtmlType = (url: string, id: string, params: any) => Promise; + +export interface IStorybookStory { + name: string; + render: () => any; +} + +export interface IStorybookSection { + kind: string; + stories: IStorybookStory[]; +} + +export interface ShowErrorArgs { + title: string; + description: string; +} + +export interface ConfigureOptionsArgs { + fetchStoryHtml: FetchStoryHtmlType; +} + +export interface RenderMainArgs { + storyFn: () => StoryFn; + id: string; + selectedKind: string; + selectedStory: string; + showMain: () => void; + showError: (args: ShowErrorArgs) => void; + forceRender: boolean; + parameters: any; +} diff --git a/app/server/src/lib/compiler/__testfixtures__/a11y.json b/app/server/src/lib/compiler/__testfixtures__/a11y.json new file mode 100644 index 000000000000..cbbc9279e2b8 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/a11y.json @@ -0,0 +1,15 @@ +{ + "title": "Addons/a11y", + "addons": ["a11y"], + "parameters": { + "options": { "selectedPanel": "storybook/a11y/panel" } + }, + "stories": [ + { + "name": "Label", + "parameters": { + "server": { "id": "addons/a11y/label" } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/a11y.snapshot b/app/server/src/lib/compiler/__testfixtures__/a11y.snapshot new file mode 100644 index 000000000000..32059d5a8fa8 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/a11y.snapshot @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler a11y.json 1`] = ` +"import { withA11y } from '@storybook/addon-a11y'; + +export default { + title: 'Addons/a11y', + decorators: [ + withA11y + ], + parameters: { + options: { + selectedPanel: 'storybook/a11y/panel' + } + } +}; + +export const Label = () => {}; +Label.story = { + name: 'Label', + parameters: { + server: { + id: 'addons/a11y/label' + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/actions.json b/app/server/src/lib/compiler/__testfixtures__/actions.json new file mode 100644 index 000000000000..6adb4d8c591b --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/actions.json @@ -0,0 +1,16 @@ +{ + "title": "Addons/Actions", + "addons": ["actions"], + "parameters": { + "options": { "selectedPanel": "storybook/actions/panel" } + }, + "stories": [ + { + "name": "Multiple actions + config", + "parameters": { + "server": { "id": "addons/actions/story3" } + }, + "actions": ["click", "contextmenu", { "clearOnStoryChange": false }] + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/actions.snapshot b/app/server/src/lib/compiler/__testfixtures__/actions.snapshot new file mode 100644 index 000000000000..b6a1c8f83f95 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/actions.snapshot @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler actions.json 1`] = ` +"import { withActions } from '@storybook/addon-actions'; + +export default { + title: 'Addons/Actions', + parameters: { + options: { + selectedPanel: 'storybook/actions/panel' + } + } +}; + +export const Multiple_actions_config = () => {}; +Multiple_actions_config.story = { + decorators: [ + withActions( + 'click', + 'contextmenu', + { + clearOnStoryChange: false + } + ) + ], + name: 'Multiple actions + config', + parameters: { + server: { + id: 'addons/actions/story3' + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/backgrounds.json b/app/server/src/lib/compiler/__testfixtures__/backgrounds.json new file mode 100644 index 000000000000..4c91c4f4c51c --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/backgrounds.json @@ -0,0 +1,17 @@ +{ + "title": "Addons/Backgrounds", + "parameters": { + "backgrounds": [ + { "name": "light", "value": "#eeeeee" }, + { "name": "dark", "value": "#222222", "default": true } + ] + }, + "stories": [ + { + "name": "Story 1", + "parameters": { + "server": { "id": "addons/backgrounds/story1" } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/backgrounds.snapshot b/app/server/src/lib/compiler/__testfixtures__/backgrounds.snapshot new file mode 100644 index 000000000000..c1408fd5dbc2 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/backgrounds.snapshot @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler backgrounds.json 1`] = ` +" +export default { + title: 'Addons/Backgrounds', + parameters: { + backgrounds: [ + { + name: 'light', + value: '#eeeeee' + }, + { + name: 'dark', + value: '#222222', + default: true + } + ] + } +}; + +export const Story_1 = () => {}; +Story_1.story = { + name: 'Story 1', + parameters: { + server: { + id: 'addons/backgrounds/story1' + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.json b/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.json new file mode 100644 index 000000000000..e97dd4abfe87 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.json @@ -0,0 +1,40 @@ +{ + "title": "Kitchen Sink", + "addons": ["a11y", "knobs", "actions", "links"], + "parameters": { + "backgrounds": [ + { "name": "light", "value": "#eeeeee" }, + { "name": "dark", "value": "#222222", "default": true } + ], + "options": { "selectedPanel": "storybook/a11y/panel" }, + "server": { + "params": { "color": "red" } + } + }, + "stories": [ + { + "name": "Heading", + "parameters": { + "notes": "My notes on some bold text", + "server": { + "id": "demo/heading", + "params": { + "color": "orange" + } + } + }, + "knobs": [ + { "type": "text", "name": "Name", "value": "John Doe", "param": "name"}, + { "type": "number", "name": "Age", "value": 44, "param": "age"} + ], + "actions": ["click", "contextmenu", { "clearOnStoryChange": false }] + }, + { + "name": "Button", + "parameters": { + "notes": "My notes on a button", + "server": { "id": "demo/button" } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.snapshot b/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.snapshot new file mode 100644 index 000000000000..5049179d7a75 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.snapshot @@ -0,0 +1,78 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler kitchen_sink.json 1`] = ` +"import { withA11y } from '@storybook/addon-a11y'; +import { withLinks } from '@storybook/addon-links'; +import { array, boolean, color, date, number, object, select, text, withKnobs } from '@storybook/addon-knobs'; +import { withActions } from '@storybook/addon-actions'; + +export default { + title: 'Kitchen Sink', + decorators: [ + withA11y, + withLinks, + withKnobs + ], + parameters: { + backgrounds: [ + { + name: 'light', + value: '#eeeeee' + }, + { + name: 'dark', + value: '#222222', + default: true + } + ], + options: { + selectedPanel: 'storybook/a11y/panel' + }, + server: { + params: { + color: 'red' + } + } + } +}; + +export const Heading = () => { + return { + name: text('Name', 'John Doe'), + age: number('Age', 44, {}), + }; +}; +Heading.story = { + decorators: [ + withActions( + 'click', + 'contextmenu', + { + clearOnStoryChange: false + } + ) + ], + name: 'Heading', + parameters: { + notes: 'My notes on some bold text', + server: { + id: 'demo/heading', + params: { + color: 'orange' + } + } + } +}; + +export const Button = () => {}; +Button.story = { + name: 'Button', + parameters: { + notes: 'My notes on a button', + server: { + id: 'demo/button' + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/knobs.json b/app/server/src/lib/compiler/__testfixtures__/knobs.json new file mode 100644 index 000000000000..b62edc82bdcd --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/knobs.json @@ -0,0 +1,19 @@ +{ + "title": "Addons/Knobs", + "addons": ["knobs"], + "parameters": { + "options": { "selectedPanel": "storybook/knobs/panel" } + }, + "stories": [ + { + "name": "Simple", + "parameters": { + "server": { "id": "addons/knobs/simple" } + }, + "knobs": [ + { "type": "text", "name": "Name", "value": "John Doe", "param": "name"}, + { "type": "number", "name": "Age", "value": 44, "param": "age"} + ] + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/knobs.snapshot b/app/server/src/lib/compiler/__testfixtures__/knobs.snapshot new file mode 100644 index 000000000000..0b385ccad0b9 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/knobs.snapshot @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler knobs.json 1`] = ` +"import { array, boolean, color, date, number, object, select, text, withKnobs } from '@storybook/addon-knobs'; + +export default { + title: 'Addons/Knobs', + decorators: [ + withKnobs + ], + parameters: { + options: { + selectedPanel: 'storybook/knobs/panel' + } + } +}; + +export const Simple = () => { + return { + name: text('Name', 'John Doe'), + age: number('Age', 44, {}), + }; +}; +Simple.story = { + name: 'Simple', + parameters: { + server: { + id: 'addons/knobs/simple' + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/links.json b/app/server/src/lib/compiler/__testfixtures__/links.json new file mode 100644 index 000000000000..4c1974ae1912 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/links.json @@ -0,0 +1,14 @@ +{ + "title": "Welcome", + "addons": ["links"], + "stories": [ + { + "name": "Welcome", + "parameters": { + "server": { + "id": "welcome/welcome" + } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/links.snapshot b/app/server/src/lib/compiler/__testfixtures__/links.snapshot new file mode 100644 index 000000000000..b4c137add594 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/links.snapshot @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler links.json 1`] = ` +"import { withLinks } from '@storybook/addon-links'; + +export default { + title: 'Welcome', + decorators: [ + withLinks + ], +}; + +export const Welcome = () => {}; +Welcome.story = { + name: 'Welcome', + parameters: { + server: { + id: 'welcome/welcome' + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/multiple_stories.json b/app/server/src/lib/compiler/__testfixtures__/multiple_stories.json new file mode 100644 index 000000000000..207332142d29 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/multiple_stories.json @@ -0,0 +1,23 @@ +{ + "title": "Demo", + "stories": [ + { + "name": "Heading", + "parameters": { + "server": { "id": "demo/heading" } + } + }, + { + "name": "Headings", + "parameters": { + "server": { "id": "demo/headings" } + } + }, + { + "name": "Button", + "parameters": { + "server": { "id": "demo/button" } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/multiple_stories.snapshot b/app/server/src/lib/compiler/__testfixtures__/multiple_stories.snapshot new file mode 100644 index 000000000000..4e1bc924e2fc --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/multiple_stories.snapshot @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler multiple_stories.json 1`] = ` +" +export default { + title: 'Demo', +}; + +export const Heading = () => {}; +Heading.story = { + name: 'Heading', + parameters: { + server: { + id: 'demo/heading' + } + } +}; + +export const Headings = () => {}; +Headings.story = { + name: 'Headings', + parameters: { + server: { + id: 'demo/headings' + } + } +}; + +export const Button = () => {}; +Button.story = { + name: 'Button', + parameters: { + server: { + id: 'demo/button' + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/notes.json b/app/server/src/lib/compiler/__testfixtures__/notes.json new file mode 100644 index 000000000000..9dc4e21c17d5 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/notes.json @@ -0,0 +1,12 @@ +{ + "title": "Addons/Notes", + "stories": [ + { + "name": "Simple note", + "parameters": { + "notes": "My notes on some bold text", + "server": { "id": "addons/notes/story1" } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/notes.snapshot b/app/server/src/lib/compiler/__testfixtures__/notes.snapshot new file mode 100644 index 000000000000..95760047ea4b --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/notes.snapshot @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler notes.json 1`] = ` +" +export default { + title: 'Addons/Notes', +}; + +export const Simple_note = () => {}; +Simple_note.story = { + name: 'Simple note', + parameters: { + notes: 'My notes on some bold text', + server: { + id: 'addons/notes/story1' + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/params.json b/app/server/src/lib/compiler/__testfixtures__/params.json new file mode 100644 index 000000000000..60f6b720cf42 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/params.json @@ -0,0 +1,19 @@ +{ + "title": "Params", + "parameters": { + "server": { + "params": { "color": "red" } + } + }, + "stories": [ + { + "name": "Story", + "parameters": { + "server": { + "id": "params/story", + "params": { "message": "Hello World" } + } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/params.snapshot b/app/server/src/lib/compiler/__testfixtures__/params.snapshot new file mode 100644 index 000000000000..c0517efed521 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/params.snapshot @@ -0,0 +1,29 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler params.json 1`] = ` +" +export default { + title: 'Params', + parameters: { + server: { + params: { + color: 'red' + } + } + } +}; + +export const Story = () => {}; +Story.story = { + name: 'Story', + parameters: { + server: { + id: 'params/story', + params: { + message: 'Hello World' + } + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/params_override.json b/app/server/src/lib/compiler/__testfixtures__/params_override.json new file mode 100644 index 000000000000..717be13a4ad3 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/params_override.json @@ -0,0 +1,19 @@ +{ + "title": "Params", + "parameters": { + "server": { + "params": { "color": "red" } + } + }, + "stories": [ + { + "name": "Override", + "parameters": { + "server": { + "id": "params/override", + "params": { "message": "Hello World", "color": "green" } + } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/params_override.snapshot b/app/server/src/lib/compiler/__testfixtures__/params_override.snapshot new file mode 100644 index 000000000000..897da9eaf040 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/params_override.snapshot @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler params_override.json 1`] = ` +" +export default { + title: 'Params', + parameters: { + server: { + params: { + color: 'red' + } + } + } +}; + +export const Override = () => {}; +Override.story = { + name: 'Override', + parameters: { + server: { + id: 'params/override', + params: { + message: 'Hello World', + color: 'green' + } + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/decorators/actions.ts b/app/server/src/lib/compiler/decorators/actions.ts new file mode 100644 index 000000000000..190f0f292aff --- /dev/null +++ b/app/server/src/lib/compiler/decorators/actions.ts @@ -0,0 +1,36 @@ +import { StorybookSection, StorybookStory } from '../types'; +import { importMeta } from './utils'; +import { stringifyObject } from '../stringifier'; + +type Action = string | any; + +function stringifyActionsDecorator(actions: Action[], importName: string): string[] { + if (!actions || actions.length === 0) return []; + + const actionArgs = stringifyObject(actions, 2, true); + return [`${importName}(\n ${actionArgs}\n )`]; +} + +function actionsStoryDecorator(story: StorybookStory, importName: string): StorybookStory { + const { name, storyFn, decorators = [], actions, ...options } = story; + + return { + name, + storyFn, + decorators: [...decorators, ...stringifyActionsDecorator(actions, importName)], + ...options, + }; +} + +export function actionsDecorator(section: StorybookSection): StorybookSection { + const { title, imports, decorators, stories, ...options } = section; + const { importName, moduleName } = importMeta('actions'); + + return { + title, + imports: { ...imports, ...{ [moduleName]: [importName] } }, + decorators, + stories: stories.map(story => actionsStoryDecorator(story, importName)), + ...options, + }; +} diff --git a/app/server/src/lib/compiler/decorators/index.ts b/app/server/src/lib/compiler/decorators/index.ts new file mode 100644 index 000000000000..2e6969dcb50c --- /dev/null +++ b/app/server/src/lib/compiler/decorators/index.ts @@ -0,0 +1,23 @@ +import { StorybookSection, Decorator } from '../types'; +import { decorateSimpleAddon } from './utils'; +import { knobsDecorator } from './knobs'; +import { actionsDecorator } from './actions'; + +function createSimpleDecorator(addon: string) { + return (section: StorybookSection): StorybookSection => decorateSimpleAddon(section, addon); +} + +const allDecorators: Record = { + a11y: createSimpleDecorator('a11y'), + links: createSimpleDecorator('links'), + knobs: knobsDecorator, + actions: actionsDecorator, +}; + +export function decorateSection(section: StorybookSection, addons: string[]): StorybookSection { + const decorators = Object.keys(allDecorators) + .filter(addon => addons.includes(addon)) + .map(addon => allDecorators[addon]); + + return decorators.reduce((sec, decorator) => decorator(sec), section); +} diff --git a/app/server/src/lib/compiler/decorators/knobs.ts b/app/server/src/lib/compiler/decorators/knobs.ts new file mode 100644 index 000000000000..69d6f87ee86b --- /dev/null +++ b/app/server/src/lib/compiler/decorators/knobs.ts @@ -0,0 +1,93 @@ +import dedent from 'ts-dedent'; +import { StorybookSection, StorybookStory } from '../types'; +import { decorateSimpleAddon, importMeta } from './utils'; +import { stringifyObject } from '../stringifier'; + +type KnobType = 'text' | 'number' | 'color' | 'array' | 'boolean' | 'object' | 'date' | 'select'; + +interface StoryKnob { + param: string; + type: KnobType; + name: string; + value: any; + [x: string]: any; +} + +function stringifyKnob(knob: StoryKnob) { + const { param, type, name, value, ...opts } = knob; + const knobParam = param || name; // Todo: can we do away with this? + const level = 2; + const stringifiedValue = stringifyObject(value, level); + // TODO: Add group + const knobFunction = (t => { + switch (t) { + case 'text': + return `text('${name}', ${stringifiedValue})`; + case 'number': + return `number('${name}', ${stringifiedValue}, ${stringifyObject(opts, level)})`; + case 'color': + return `color('${name}', ${stringifiedValue})`; + case 'array': + return `array('${name}', ${stringifiedValue}).join(',')`; + case 'boolean': + return `boolean('${name}', ${stringifiedValue})`; + case 'object': + return `object('${name}', ${stringifiedValue})`; + case 'date': + return `date('${name}', new Date(${stringifiedValue}))`; + case 'select': + return `select('${name}', ${stringifyObject(opts.options, level)}, ${stringifiedValue})`; + default: + return ''; + } + })(type); + + return `${knobParam}: ${knobFunction}`; +} + +function stringifyStoryFunction(knobs: StoryKnob[], storyFn: string) { + if (!knobs || knobs.length === 0) return storyFn; + + return dedent` + () => { + return { + ${knobs.map((knob: any) => `${stringifyKnob(knob)},`).join('\n ')} + }; + } + `; +} + +function knobsStoryDecorator(story: StorybookStory): StorybookStory { + const { name, storyFn, knobs, ...options } = story; + + return { + name, + storyFn: stringifyStoryFunction(knobs, storyFn), + ...options, + }; +} + +export function knobsDecorator(section: StorybookSection): StorybookSection { + const { title, imports, decorators, stories, ...options } = decorateSimpleAddon(section, 'knobs'); + const { importName, moduleName } = importMeta('knobs'); + + const knobImports = [ + importName, + 'array', + 'boolean', + 'color', + 'date', + 'text', + 'number', + 'object', + 'select', + ]; + + return { + title, + imports: { ...imports, ...{ [moduleName]: knobImports } }, + decorators, + stories: stories.map(story => knobsStoryDecorator(story)), + ...options, + }; +} diff --git a/app/server/src/lib/compiler/decorators/utils.ts b/app/server/src/lib/compiler/decorators/utils.ts new file mode 100644 index 000000000000..838df57a9d74 --- /dev/null +++ b/app/server/src/lib/compiler/decorators/utils.ts @@ -0,0 +1,21 @@ +import { StorybookSection } from '../types'; + +export function importMeta(addon: string) { + return { + importName: `with${addon.charAt(0).toUpperCase() + addon.slice(1)}`, + moduleName: `@storybook/addon-${addon}`, + }; +} + +export function decorateSimpleAddon(section: StorybookSection, addon: string) { + const { title, imports, decorators, stories, ...options } = section; + const { importName, moduleName } = importMeta(addon); + + return { + title, + imports: { ...imports, ...{ [moduleName]: [importName] } }, + decorators: [...decorators, importName], + stories, + ...options, + }; +} diff --git a/app/server/src/lib/compiler/index.ts b/app/server/src/lib/compiler/index.ts new file mode 100644 index 000000000000..d25f090d3502 --- /dev/null +++ b/app/server/src/lib/compiler/index.ts @@ -0,0 +1,38 @@ +import { + CompileCsfModuleArgs, + CompileStorybookSectionArgs, + CompileStorybookStoryArgs, + StorybookSection, + StorybookStory, +} from './types'; + +import { stringifySection } from './stringifier'; +import { decorateSection } from './decorators'; + +function createStory(storyArgs: CompileStorybookStoryArgs): StorybookStory { + const { name, ...options } = storyArgs; + + return { + name, + storyFn: '() => {}', + ...options, + }; +} + +function createSection(args: CompileStorybookSectionArgs): StorybookSection { + const { title, stories, ...options } = args; + return { + imports: {}, + decorators: [], + title, + stories: stories.map(storyArgs => createStory(storyArgs)), + ...options, + }; +} + +export function compileCsfModule(args: CompileCsfModuleArgs): string { + const { addons = [], ...compileSectionArgs } = args; + const storybookSection = createSection(compileSectionArgs); + const decoratedSection = decorateSection(storybookSection, addons); + return stringifySection(decoratedSection); +} diff --git a/app/server/src/lib/compiler/json-to-csf-compiler.test.ts b/app/server/src/lib/compiler/json-to-csf-compiler.test.ts new file mode 100644 index 000000000000..503cf198a995 --- /dev/null +++ b/app/server/src/lib/compiler/json-to-csf-compiler.test.ts @@ -0,0 +1,24 @@ +import 'jest-specific-snapshot'; +import path from 'path'; +import fs from 'fs-extra'; +import { compileCsfModule } from '.'; + +const inputRegExp = /\.json$/; + +async function generate(filePath: string) { + const content = await fs.readFile(filePath, 'utf8'); + return compileCsfModule(JSON.parse(content)); +} + +describe('json-to-csf-compiler', () => { + const transformFixturesDir = path.join(__dirname, '__testfixtures__'); + fs.readdirSync(transformFixturesDir) + .filter((fileName: string) => inputRegExp.test(fileName)) + .forEach((fixtureFile: string) => { + it(fixtureFile, async () => { + const inputPath = path.join(transformFixturesDir, fixtureFile); + const code = await generate(inputPath); + expect(code).toMatchSpecificSnapshot(inputPath.replace(inputRegExp, '.snapshot')); + }); + }); +}); diff --git a/app/server/src/lib/compiler/stringifier.ts b/app/server/src/lib/compiler/stringifier.ts new file mode 100644 index 000000000000..2926c3281caf --- /dev/null +++ b/app/server/src/lib/compiler/stringifier.ts @@ -0,0 +1,85 @@ +import dedent from 'ts-dedent'; +import { StorybookStory, StorybookSection } from './types'; + +const { identifier } = require('safe-identifier'); + +export function stringifyObject(object: any, level = 0, excludeOuterParams = false): string { + if (typeof object === 'string') { + return `'${object}'`; + } + const indent = ' '.repeat(level); + if (Array.isArray(object)) { + const arrayStrings: string[] = object.map((item: any) => stringifyObject(item, level + 1)); + const arrayString = arrayStrings.join(`,\n${indent} `); + if (excludeOuterParams) return arrayString; + return `[\n${indent} ${arrayString}\n${indent}]`; + } + if (typeof object === 'object') { + let objectString = ''; + if (Object.keys(object).length > 0) { + const objectStrings: string[] = Object.keys(object).map(key => { + const value: string = stringifyObject(object[key], level + 1); + return `\n${indent} ${key}: ${value}`; + }); + objectString = objectStrings.join(','); + } + if (excludeOuterParams) return objectString; + if (objectString.length === 0) return '{}'; + return `{${objectString}\n${indent}}`; + } + + return object; +} + +export function stringifyImports(imports: Record): string { + if (Object.keys(imports).length === 0) return ''; + return Object.entries(imports) + .map(([module, names]) => `import { ${names.sort().join(', ')} } from '${module}';\n`) + .join(''); +} + +export function stringifyDecorators(decorators: string[]): string { + return decorators && decorators.length > 0 + ? `\n decorators: [\n ${decorators.join(',\n ')}\n ],` + : ''; +} + +export function stringifyDefault(section: StorybookSection): string { + const { title, imports, decorators, stories, ...options } = section; + + const decoratorsString = stringifyDecorators(decorators); + + const optionsString = stringifyObject(options, 0, true); + + return dedent` + export default { + title: '${title}',${decoratorsString}${optionsString} + }; + + `; +} + +export function stringifyStory(story: StorybookStory): string { + const { name, storyFn, decorators, ...options } = story; + const storyId = identifier(name); + + const decoratorsString = stringifyDecorators(decorators); + const optionsString = stringifyObject({ name, ...options }, 0, true); + + let storyString = ''; + if (decoratorsString.length > 0 || optionsString.length > 0) { + storyString = `${storyId}.story = {${decoratorsString}${optionsString}\n};\n`; + } + return `export const ${storyId} = ${storyFn};\n${storyString}`; +} + +export function stringifySection(section: StorybookSection): string { + const sectionString = [ + stringifyImports(section.imports), + stringifyDefault(section), + ...section.stories.map(story => stringifyStory(story)), + ].join('\n'); + + // console.log('sectionString:\n', sectionString); + return sectionString; +} diff --git a/app/server/src/lib/compiler/types.ts b/app/server/src/lib/compiler/types.ts new file mode 100644 index 000000000000..0de8dbd14f2b --- /dev/null +++ b/app/server/src/lib/compiler/types.ts @@ -0,0 +1,31 @@ +export interface CompileStorybookStoryArgs { + name: string; + [x: string]: any; +} + +export interface CompileStorybookSectionArgs { + title: string; + stories: CompileStorybookStoryArgs[]; + [x: string]: any; +} + +export interface CompileCsfModuleArgs extends CompileStorybookSectionArgs { + addons?: string[]; +} + +export interface StorybookStory { + name: string; + storyFn: string; + decorators?: string[]; + [x: string]: any; +} + +export interface StorybookSection { + imports: Record; + decorators?: string[]; + title: string; + stories: StorybookStory[]; + [x: string]: any; +} + +export type Decorator = (section: StorybookSection) => StorybookSection; diff --git a/app/server/src/server/build.ts b/app/server/src/server/build.ts new file mode 100644 index 000000000000..d8abf06a4396 --- /dev/null +++ b/app/server/src/server/build.ts @@ -0,0 +1,4 @@ +import { buildStatic } from '@storybook/core/server'; +import options from './options'; + +buildStatic(options); diff --git a/app/server/src/server/framework-preset-server.ts b/app/server/src/server/framework-preset-server.ts new file mode 100644 index 000000000000..2604c82e8b0f --- /dev/null +++ b/app/server/src/server/framework-preset-server.ts @@ -0,0 +1,24 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Configuration } from 'webpack'; +import path from 'path'; + +export function webpack(config: Configuration) { + return { + ...config, + module: { + ...config.module, + rules: [ + ...config.module.rules, + { + type: 'javascript/auto', + test: /\.stories\.json$/, + use: [ + { + loader: path.resolve(__dirname, './loader.js'), + }, + ], + }, + ], + }, + }; +} diff --git a/app/server/src/server/index.ts b/app/server/src/server/index.ts new file mode 100644 index 000000000000..774d96025a84 --- /dev/null +++ b/app/server/src/server/index.ts @@ -0,0 +1,4 @@ +import { buildDev } from '@storybook/core/server'; +import options from './options'; + +buildDev(options); diff --git a/app/server/src/server/loader.ts b/app/server/src/server/loader.ts new file mode 100644 index 000000000000..67b8aab46e0c --- /dev/null +++ b/app/server/src/server/loader.ts @@ -0,0 +1,5 @@ +import { compileCsfModule } from '../lib/compiler'; + +export default (content: string) => { + return compileCsfModule(JSON.parse(content)); +}; diff --git a/app/server/src/server/options.ts b/app/server/src/server/options.ts new file mode 100644 index 000000000000..da5391d10823 --- /dev/null +++ b/app/server/src/server/options.ts @@ -0,0 +1,8 @@ +// tslint:disable-next-line: no-var-requires +const packageJson = require('../../package.json'); + +export default { + packageJson, + framework: 'server', + frameworkPresets: [require.resolve('./framework-preset-server.js')], +}; diff --git a/app/server/src/typings.d.ts b/app/server/src/typings.d.ts new file mode 100644 index 000000000000..ef324850aa08 --- /dev/null +++ b/app/server/src/typings.d.ts @@ -0,0 +1,6 @@ +declare module '@storybook/core/*'; +declare module 'global'; +declare module 'fs-extra'; + +// will be provided by the webpack define plugin +declare var NODE_ENV: string | undefined; diff --git a/app/server/standalone.js b/app/server/standalone.js new file mode 100644 index 000000000000..1b1febe0d3bb --- /dev/null +++ b/app/server/standalone.js @@ -0,0 +1,8 @@ +const build = require('@storybook/core/standalone'); +const frameworkOptions = require('./dist/server/options').default; + +async function buildStandalone(options) { + return build(options, frameworkOptions); +} + +module.exports = buildStandalone; diff --git a/app/server/tsconfig.json b/app/server/tsconfig.json new file mode 100644 index 000000000000..82ce44329cc6 --- /dev/null +++ b/app/server/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "types": ["webpack-env"] + }, + "include": ["src/**/*"], + "exclude": ["src/__tests__/**/*"] +} diff --git a/examples/server-kitchen-sink/.storybook/main.js b/examples/server-kitchen-sink/.storybook/main.js new file mode 100644 index 000000000000..263d95f9579c --- /dev/null +++ b/examples/server-kitchen-sink/.storybook/main.js @@ -0,0 +1,13 @@ +module.exports = { + stories: ['../stories/**/*.stories.json'], + addons: [ + '@storybook/addon-docs', + '@storybook/addon-a11y', + '@storybook/addon-actions', + '@storybook/addon-backgrounds', + '@storybook/addon-knobs', + '@storybook/addon-links', + '@storybook/addon-notes', + '@storybook/addon-options', + ], +}; diff --git a/examples/server-kitchen-sink/.storybook/preview.js b/examples/server-kitchen-sink/.storybook/preview.js new file mode 100644 index 000000000000..467139f265b9 --- /dev/null +++ b/examples/server-kitchen-sink/.storybook/preview.js @@ -0,0 +1,25 @@ +import { addParameters, addDecorator } from '@storybook/server'; +import { withA11y } from '@storybook/addon-a11y'; + +addDecorator(withA11y); + +const port = process.env.SERVER_PORT || 1337; + +addParameters({ + a11y: { + config: {}, + options: { + checks: { 'color-contrast': { options: { noScroll: true } } }, + restoreScroll: true, + }, + }, + options: { + showRoots: true, + }, + docs: { + iframeHeight: '200px', + }, + server: { + url: `http://localhost:${port}/storybook_preview`, + }, +}); diff --git a/examples/server-kitchen-sink/README.md b/examples/server-kitchen-sink/README.md new file mode 100644 index 000000000000..6679953ba470 --- /dev/null +++ b/examples/server-kitchen-sink/README.md @@ -0,0 +1,9 @@ +# Server Kitchen Sink + +This is a dmmo app to test a standalone server using integration with Storybook using `@storybook/server`. + +Run `yarn install` to sync Storybook module with the source. + +Run `yarn start` to start. + +This starts an ExpressJS server on port `1337` and Storybook on port `9006`. diff --git a/examples/server-kitchen-sink/package.json b/examples/server-kitchen-sink/package.json new file mode 100644 index 000000000000..3cc656cc0535 --- /dev/null +++ b/examples/server-kitchen-sink/package.json @@ -0,0 +1,34 @@ +{ + "name": "server-kitchen-sink", + "version": "6.0.0-alpha.4", + "private": true, + "description": "", + "keywords": [], + "license": "MIT", + "author": "", + "main": "index.js", + "scripts": { + "build-storybook": "build-storybook", + "server": "PORT=1337 nodemon server.js", + "start": "concurrently \"yarn server\" \"yarn storybook\"", + "storybook": "SERVER_PORT=1137 start-storybook -p 9006 --quiet" + }, + "devDependencies": { + "@storybook/addon-a11y": "6.0.0-alpha.4", + "@storybook/addon-actions": "6.0.0-alpha.4", + "@storybook/addon-backgrounds": "6.0.0-alpha.4", + "@storybook/addon-centered": "6.0.0-alpha.4", + "@storybook/addon-knobs": "6.0.0-alpha.4", + "@storybook/addon-links": "6.0.0-alpha.4", + "@storybook/addon-notes": "6.0.0-alpha.4", + "@storybook/node-logger": "6.0.0-alpha.4", + "@storybook/server": "6.0.0-alpha.4", + "concurrently": "^5.0.2", + "cors": "^2.8.5", + "express": "~4.16.4", + "morgan": "^1.9.1", + "nodemon": "^2.0.2", + "pug": "^2.0.4", + "safe-identifier": "^0.3.1" + } +} diff --git a/examples/server-kitchen-sink/server.js b/examples/server-kitchen-sink/server.js new file mode 100644 index 000000000000..ea25081f0915 --- /dev/null +++ b/examples/server-kitchen-sink/server.js @@ -0,0 +1,21 @@ +const express = require('express'); +const cors = require('cors'); +const morgan = require('morgan'); +const path = require('path'); +const { logger } = require('@storybook/node-logger'); + +const port = process.env.PORT || 8080; + +const app = express(); +app.use(cors()); +app.use(morgan('dev')); +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'pug'); + +app.get('/', (req, res) => res.send('Hello World!')); + +app.get(/storybook_preview\/(.*)/, (req, res) => { + res.render(req.params[0], req.query); +}); + +app.listen(port, () => logger.info(`Server listening on port ${port}!`)); diff --git a/examples/server-kitchen-sink/stories/addon-a11y.stories.json b/examples/server-kitchen-sink/stories/addon-a11y.stories.json new file mode 100644 index 000000000000..24687d848bcf --- /dev/null +++ b/examples/server-kitchen-sink/stories/addon-a11y.stories.json @@ -0,0 +1,34 @@ +{ + "title": "Addons/a11y", + "addons": ["a11y"], + "parameters": { + "options": { "selectedPanel": "storybook/a11y/panel" } + }, + "stories": [ + { + "name": "Default", + "parameters": { + "server": { "id": "addons/a11y/default" } + } + }, + { + "name": "Label", + "parameters": { + "server": { "id": "addons/a11y/label" } + } + }, + { + "name": "Disabled", + "parameters": { + "server": { "id": "addons/a11y/disabled" } + } + }, + { + "id": "Contrast", + "name": "Invalid contrast", + "parameters": { + "server": { "id": "addons/a11y/contrast" } + } + } + ] +} diff --git a/examples/server-kitchen-sink/stories/addon-actions.stories.json b/examples/server-kitchen-sink/stories/addon-actions.stories.json new file mode 100644 index 000000000000..0680dd89da6d --- /dev/null +++ b/examples/server-kitchen-sink/stories/addon-actions.stories.json @@ -0,0 +1,44 @@ + { + "title": "Addons/Actions", + "addons": ["actions"], + "parameters": { + "options": { "selectedPanel": "storybook/actions/panel" } + }, + "stories": [ + { + "name": "Hello World", + "parameters": { + "server": { "id": "addons/actions/story1" } + }, + "actions": ["click"] + }, + { + "name": "Multiple actions", + "parameters": { + "server": { "id": "addons/actions/story2" } + }, + "actions": ["click", "contextmenu"] + }, + { + "name": "Multiple actions + config", + "parameters": { + "server": { "id": "addons/actions/story3" } + }, + "actions": ["click", "contextmenu", { "clearOnStoryChange": false }] + }, + { + "name": "Multiple actions, object", + "parameters": { + "server": { "id": "addons/actions/story4" } + }, + "actions": [{ "click": "clicked", "contextmenu": "right clicked" }] + }, + { + "name": "Multiple actions, object + config", + "parameters": { + "server": { "id": "addons/actions/story6" } + }, + "actions": [{ "click": "clicked", "contextmenu": "right clicked" }, { "clearOnStoryChange": false }] + } + ] +} diff --git a/examples/server-kitchen-sink/stories/addon-backgrounds.stories.json b/examples/server-kitchen-sink/stories/addon-backgrounds.stories.json new file mode 100644 index 000000000000..dc77798cbb24 --- /dev/null +++ b/examples/server-kitchen-sink/stories/addon-backgrounds.stories.json @@ -0,0 +1,23 @@ +{ + "title": "Addons/Backgrounds", + "parameters": { + "backgrounds": [ + { "name": "light", "value": "#eeeeee" }, + { "name": "dark", "value": "#222222", "default": true } + ] + }, + "stories": [ + { + "name": "Story 1", + "parameters": { + "server": { "id": "addons/backgrounds/story1" } + } + }, + { + "name": "Story 2", + "parameters": { + "server": { "id": "addons/backgrounds/story2" } + } + } + ] +} diff --git a/examples/server-kitchen-sink/stories/addon-knobs.stories.json b/examples/server-kitchen-sink/stories/addon-knobs.stories.json new file mode 100644 index 000000000000..17a293eb5d1e --- /dev/null +++ b/examples/server-kitchen-sink/stories/addon-knobs.stories.json @@ -0,0 +1,64 @@ +{ + "title": "Addons/Knobs", + "addons": ["knobs"], + "parameters": { + "options": { "selectedPanel": "storybook/knobs/panel" } + }, + "stories": [ + { + "name": "Simple", + "parameters": { + "server": { "id": "addons/knobs/simple" } + }, + "knobs": [ + { "type": "text", "name": "Name", "value": "John Doe", "param": "name"}, + { "type": "number", "name": "Age", "value": 44, "param": "age"} + ] + }, + { + "name": "CSS transitions", + "parameters": { + "server": { "id": "addons/knobs/css" } + }, + "knobs": [ + { "type": "text", "name": "Name", "value": "John Doe", "param": "name"}, + { "type": "color", "name": "Text Color", "value": "orangered", "param": "textColor"} + ] + }, + { + "name": "All knobs", + "parameters": { + "server": { "id": "addons/knobs/all" } + }, + "knobs": [ + { "type": "text", "name": "Name", "value": "Jane", "param": "name"}, + { + "type": "number", + "name": "Stock", + "value": 20, + "param": "stock", + "range": true, + "min": 0, + "max": 30, + "step": 5 + }, + { + "type": "select", + "name": "Fruit", + "value": "apples", + "param": "fruit", + "options": { + "Apple": "apples", + "Banana": "bananas", + "Cherry": "cherries" + } + }, + { "type": "number", "name": "Price", "value": 2.25, "param": "price"}, + { "type": "color", "name": "Border", "value": "deeppink", "param": "colour"}, + { "type": "date", "name": "Today", "value": "Jan 20 2017 GMT+0", "param": "today"}, + { "type": "array", "name": "Items", "value": ["Laptop", "Book", "Whiskey"], "param": "items"}, + { "type": "boolean", "name": "Nice", "value": true, "param": "nice"} + ] + } + ] +} diff --git a/examples/server-kitchen-sink/stories/addon-notes.stories.json b/examples/server-kitchen-sink/stories/addon-notes.stories.json new file mode 100644 index 000000000000..9dc4e21c17d5 --- /dev/null +++ b/examples/server-kitchen-sink/stories/addon-notes.stories.json @@ -0,0 +1,12 @@ +{ + "title": "Addons/Notes", + "stories": [ + { + "name": "Simple note", + "parameters": { + "notes": "My notes on some bold text", + "server": { "id": "addons/notes/story1" } + } + } + ] +} diff --git a/examples/server-kitchen-sink/stories/demo.stories.json b/examples/server-kitchen-sink/stories/demo.stories.json new file mode 100644 index 000000000000..207332142d29 --- /dev/null +++ b/examples/server-kitchen-sink/stories/demo.stories.json @@ -0,0 +1,23 @@ +{ + "title": "Demo", + "stories": [ + { + "name": "Heading", + "parameters": { + "server": { "id": "demo/heading" } + } + }, + { + "name": "Headings", + "parameters": { + "server": { "id": "demo/headings" } + } + }, + { + "name": "Button", + "parameters": { + "server": { "id": "demo/button" } + } + } + ] +} diff --git a/examples/server-kitchen-sink/stories/kitchen_sink.stories.json b/examples/server-kitchen-sink/stories/kitchen_sink.stories.json new file mode 100644 index 000000000000..528bdec0a405 --- /dev/null +++ b/examples/server-kitchen-sink/stories/kitchen_sink.stories.json @@ -0,0 +1,33 @@ +{ + "title": "Kitchen Sink", + "addons": ["a11y", "knobs", "actions", "links"], + "parameters": { + "backgrounds": [ + { "name": "light", "value": "#eeeeee" }, + { "name": "dark", "value": "#222222", "default": true } + ], + "options": { "selectedPanel": "storybook/a11y/panel" }, + "server": { + "params": { "name": "Jane Doe" } + } + }, + "stories": [ + { + "name": "All the things", + "parameters": { + "notes": "My notes on some person", + "server": { + "id": "addons/knobs/simple", + "params": { + "name": "Jim Doe" + } + } + }, + "knobs": [ + { "type": "text", "name": "Name", "value": "John Doe", "param": "name"}, + { "type": "number", "name": "Age", "value": 44, "param": "age"} + ], + "actions": ["click", "contextmenu", { "clearOnStoryChange": false }] + } + ] +} diff --git a/examples/server-kitchen-sink/stories/params.stories.json b/examples/server-kitchen-sink/stories/params.stories.json new file mode 100644 index 000000000000..c714b0a562df --- /dev/null +++ b/examples/server-kitchen-sink/stories/params.stories.json @@ -0,0 +1,28 @@ +{ + "title": "Params", + "parameters": { + "server": { + "params": { "color": "red" } + } + }, + "stories": [ + { + "name": "Story", + "parameters": { + "server": { + "id": "params/story", + "params": { "message": "Hello World" } + } + } + }, + { + "name": "Override", + "parameters": { + "server": { + "id": "params/override", + "params": { "message": "Hello World", "color": "green" } + } + } + } + ] +} diff --git a/examples/server-kitchen-sink/stories/welcome.stories.json b/examples/server-kitchen-sink/stories/welcome.stories.json new file mode 100644 index 000000000000..4c1974ae1912 --- /dev/null +++ b/examples/server-kitchen-sink/stories/welcome.stories.json @@ -0,0 +1,14 @@ +{ + "title": "Welcome", + "addons": ["links"], + "stories": [ + { + "name": "Welcome", + "parameters": { + "server": { + "id": "welcome/welcome" + } + } + } + ] +} diff --git a/examples/server-kitchen-sink/views/addons/a11y/contrast.pug b/examples/server-kitchen-sink/views/addons/a11y/contrast.pug new file mode 100644 index 000000000000..4b217011d3a1 --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/a11y/contrast.pug @@ -0,0 +1 @@ +button(style='color: black; background-color: brown;') Testing the a11y addon \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/a11y/default.pug b/examples/server-kitchen-sink/views/addons/a11y/default.pug new file mode 100644 index 000000000000..0900630d219d --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/a11y/default.pug @@ -0,0 +1 @@ +button \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/a11y/disabled.pug b/examples/server-kitchen-sink/views/addons/a11y/disabled.pug new file mode 100644 index 000000000000..2b3652f5ecc7 --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/a11y/disabled.pug @@ -0,0 +1 @@ +button(disabled) Testing the a11y addon \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/a11y/label.pug b/examples/server-kitchen-sink/views/addons/a11y/label.pug new file mode 100644 index 000000000000..901f438d64a5 --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/a11y/label.pug @@ -0,0 +1 @@ +button Testing the a11y addon \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/actions/button.pug b/examples/server-kitchen-sink/views/addons/actions/button.pug new file mode 100644 index 000000000000..e612bbfb36f1 --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/actions/button.pug @@ -0,0 +1 @@ +button(type="button") Hello World \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/actions/story1.pug b/examples/server-kitchen-sink/views/addons/actions/story1.pug new file mode 100644 index 000000000000..7894bcc0743b --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/actions/story1.pug @@ -0,0 +1 @@ +include button.pug \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/actions/story2.pug b/examples/server-kitchen-sink/views/addons/actions/story2.pug new file mode 100644 index 000000000000..7894bcc0743b --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/actions/story2.pug @@ -0,0 +1 @@ +include button.pug \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/actions/story3.pug b/examples/server-kitchen-sink/views/addons/actions/story3.pug new file mode 100644 index 000000000000..7894bcc0743b --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/actions/story3.pug @@ -0,0 +1 @@ +include button.pug \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/actions/story4.pug b/examples/server-kitchen-sink/views/addons/actions/story4.pug new file mode 100644 index 000000000000..7894bcc0743b --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/actions/story4.pug @@ -0,0 +1 @@ +include button.pug \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/actions/story5.pug b/examples/server-kitchen-sink/views/addons/actions/story5.pug new file mode 100644 index 000000000000..557c20da0e89 --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/actions/story5.pug @@ -0,0 +1,3 @@ +div + | Clicks on this button will be logged: + button(class="btn" type="button") Button \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/actions/story6.pug b/examples/server-kitchen-sink/views/addons/actions/story6.pug new file mode 100644 index 000000000000..7894bcc0743b --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/actions/story6.pug @@ -0,0 +1 @@ +include button.pug \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/actions/story7.pug b/examples/server-kitchen-sink/views/addons/actions/story7.pug new file mode 100644 index 000000000000..7894bcc0743b --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/actions/story7.pug @@ -0,0 +1 @@ +include button.pug \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/actions/story8.pug b/examples/server-kitchen-sink/views/addons/actions/story8.pug new file mode 100644 index 000000000000..7894bcc0743b --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/actions/story8.pug @@ -0,0 +1 @@ +include button.pug \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/backgrounds/story1.pug b/examples/server-kitchen-sink/views/addons/backgrounds/story1.pug new file mode 100644 index 000000000000..7d62c85d7cdf --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/backgrounds/story1.pug @@ -0,0 +1 @@ +span(style="color: white") You should be able to switch backgrounds for this story \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/backgrounds/story2.pug b/examples/server-kitchen-sink/views/addons/backgrounds/story2.pug new file mode 100644 index 000000000000..82abc4ee834a --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/backgrounds/story2.pug @@ -0,0 +1 @@ +span(style="color: white") This one too! \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/knobs/all.pug b/examples/server-kitchen-sink/views/addons/knobs/all.pug new file mode 100644 index 000000000000..f7003e395ec7 --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/knobs/all.pug @@ -0,0 +1,18 @@ +- nice = (nice === 'true') +- stock = parseInt(stock) +- stockMessage = stock > 0 ? `I have a stock of ${stock} ${fruit}, costing $${price} each.` : `I'm out of ${fruit}${nice ? ', Sorry!' : '.'}`; +- salutation = nice ? 'Nice to meet you!' : 'Leave me alone!'; +- dateOptions = { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' }; +- style = `border: 2px dotted ${colour}; padding: 8px 22px; border-radius: 8px`; +- today = new Date(parseInt(today, 10)); +- items = items.split(','); + +div(style=`${style}`) + h1 My name is #{name}, + h3 today is #{today.toLocaleDateString('en-US', dateOptions)} + p !{stockMessage} + p Also, I have: + ul + each item in items + li= item + p #{salutation} \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/knobs/css.pug b/examples/server-kitchen-sink/views/addons/knobs/css.pug new file mode 100644 index 000000000000..ec8996a53780 --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/knobs/css.pug @@ -0,0 +1 @@ +p(style=`transition: color 0.5s ease-out; color: ${textColor}`)= name \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/knobs/simple.pug b/examples/server-kitchen-sink/views/addons/knobs/simple.pug new file mode 100644 index 000000000000..899a25344ca4 --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/knobs/simple.pug @@ -0,0 +1,2 @@ +div. + I am #{name} and I'm #{age} years old. \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/knobs/xss_safety.pug b/examples/server-kitchen-sink/views/addons/knobs/xss_safety.pug new file mode 100644 index 000000000000..6e0f1c5d2d33 --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/knobs/xss_safety.pug @@ -0,0 +1 @@ + !{content} \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/addons/notes/story1.pug b/examples/server-kitchen-sink/views/addons/notes/story1.pug new file mode 100644 index 000000000000..dee88febdf62 --- /dev/null +++ b/examples/server-kitchen-sink/views/addons/notes/story1.pug @@ -0,0 +1,2 @@ +p + strong This is a fragment of HTML diff --git a/examples/server-kitchen-sink/views/demo/button.pug b/examples/server-kitchen-sink/views/demo/button.pug new file mode 100644 index 000000000000..69e440c555c6 --- /dev/null +++ b/examples/server-kitchen-sink/views/demo/button.pug @@ -0,0 +1 @@ +button Hello Button \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/demo/heading.pug b/examples/server-kitchen-sink/views/demo/heading.pug new file mode 100644 index 000000000000..23b68dd8f3e9 --- /dev/null +++ b/examples/server-kitchen-sink/views/demo/heading.pug @@ -0,0 +1 @@ +h1 Hello World \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/demo/headings.pug b/examples/server-kitchen-sink/views/demo/headings.pug new file mode 100644 index 000000000000..23a6d7f78ab6 --- /dev/null +++ b/examples/server-kitchen-sink/views/demo/headings.pug @@ -0,0 +1,4 @@ +h1 Hellow World +h2 Hellow World +h3 Hellow World +h4 Hellow World \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/params/override.pug b/examples/server-kitchen-sink/views/params/override.pug new file mode 100644 index 000000000000..b60cffc6349b --- /dev/null +++ b/examples/server-kitchen-sink/views/params/override.pug @@ -0,0 +1 @@ +include params.pug \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/params/params.pug b/examples/server-kitchen-sink/views/params/params.pug new file mode 100644 index 000000000000..5020bd41af24 --- /dev/null +++ b/examples/server-kitchen-sink/views/params/params.pug @@ -0,0 +1 @@ +h1(style=`color: ${color}`)= message \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/params/story.pug b/examples/server-kitchen-sink/views/params/story.pug new file mode 100644 index 000000000000..b60cffc6349b --- /dev/null +++ b/examples/server-kitchen-sink/views/params/story.pug @@ -0,0 +1 @@ +include params.pug \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/params/story_fn_override.pug b/examples/server-kitchen-sink/views/params/story_fn_override.pug new file mode 100644 index 000000000000..b60cffc6349b --- /dev/null +++ b/examples/server-kitchen-sink/views/params/story_fn_override.pug @@ -0,0 +1 @@ +include params.pug \ No newline at end of file diff --git a/examples/server-kitchen-sink/views/welcome/welcome.pug b/examples/server-kitchen-sink/views/welcome/welcome.pug new file mode 100644 index 000000000000..fe663b9d7c93 --- /dev/null +++ b/examples/server-kitchen-sink/views/welcome/welcome.pug @@ -0,0 +1,36 @@ +.main + h1 Welcome to Storybook for Server + p This is a UI component dev environment for your plain HTML snippets. + p. + We've added some basic stories inside the #[code.code stories] directory. + #[br] + A story is a single state of one or more UI components. You can have as many stories as you want. + #[br] + (Basically a story is like a visual test case.) + p. + See these sample #[a.link(href='#' data-sb-kind='Demo' data-sb-story='Headings') stories] + p. + Just like that, you can add your own snippets as stories. + #[br] + You can also edit those snippets and see changes right away. + #[br] + p. + Usually we create stories with smaller UI components in the app. + #[br] + Have a look at the #[a.link(href='https://storybook.js.org/basics/writing-stories' target='_blank') Writing Stories] section in our documentation. +style. + .main { + padding: 15px; + line-height: 1.4; + font-family: 'Helvetica Neue', Helvetica, 'Segoe UI', Arial, freesans, sans-serif; + background-color: #ffffff; + } + .code { + font-size: 15px; + font-weight: 600; + padding: 2px 5px; + border: 1px solid #eae9e9; + border-radius: 4px; + background-color: #f3f2f2; + color: #3a3a3a; + } \ No newline at end of file diff --git a/package.json b/package.json index 636afa2dcfac..3938bc48d116 100644 --- a/package.json +++ b/package.json @@ -81,14 +81,30 @@ } }, "lint-staged": { - "*.html": [ "yarn lint:js --fix" ], - "*.js": [ "yarn lint:js --fix" ], - "*.json": [ "yarn lint:js --fix" ], - "*.jsx": [ "yarn lint:js --fix" ], - "*.mjs": [ "yarn lint:js --fix" ], - "*.ts": [ "yarn lint:js --fix" ], - "*.tsx": [ "yarn lint:js --fix" ], - "package.json": [ "yarn lint:package" ] + "*.html": [ + "yarn lint:js --fix" + ], + "*.js": [ + "yarn lint:js --fix" + ], + "*.json": [ + "yarn lint:js --fix" + ], + "*.jsx": [ + "yarn lint:js --fix" + ], + "*.mjs": [ + "yarn lint:js --fix" + ], + "*.ts": [ + "yarn lint:js --fix" + ], + "*.tsx": [ + "yarn lint:js --fix" + ], + "package.json": [ + "yarn lint:package" + ] }, "browserslist": "defaults", "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 0c275011adac..da82482f22bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3696,6 +3696,18 @@ pretty-hrtime "^1.0.3" regenerator-runtime "^0.13.3" +"@storybook/node-logger@^5.2.8": + version "5.3.10" + resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.3.10.tgz#5bf4b8ce352901cd629401cc903b15e423ccd76c" + integrity sha512-ivasRTdJPFj9qi9q4ozkn6lhWi16cubf9AZwC5Or5HJE4OJkSsowecJBu28l2p887yexDkCDazOIvR9XtEIoIA== + dependencies: + "@types/npmlog" "^4.1.2" + chalk "^3.0.0" + core-js "^3.0.1" + npmlog "^4.1.2" + pretty-hrtime "^1.0.3" + regenerator-runtime "^0.13.3" + "@storybook/preset-create-react-app@^1.5.0": version "1.5.2" resolved "https://registry.yarnpkg.com/@storybook/preset-create-react-app/-/preset-create-react-app-1.5.2.tgz#9bbdb599623031155f866b738bab8f4b2d41ca1c" @@ -4475,7 +4487,7 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.7.tgz#51d42247473bc00e38cc8dfaf70d936842a36c03" integrity sha512-C2j2FWgQkF1ru12SjZJyMaTPxs/f6n90+5G5qNakBxKXjTBc/YTSelHh4Pz1HUDwxFXD9WvpQhOGCDC+/Y4mIQ== -"@types/webpack-env@^1.15.0", "@types/webpack-env@^1.15.1": +"@types/webpack-env@^1.13.9", "@types/webpack-env@^1.15.0", "@types/webpack-env@^1.15.1": version "1.15.1" resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.15.1.tgz#c8e84705e08eed430b5e15b39c65b0944e4d1422" integrity sha512-eWN5ElDTeBc5lRDh95SqA8x18D0ll2pWudU3uWiyfsRmIZcmUXpEsxPU+7+BsdCrO2vfLRC629u/MmjbmF+2tA== @@ -7194,6 +7206,22 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== +body-parser@1.18.3: + version "1.18.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" + integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ= + dependencies: + bytes "3.0.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "~1.6.3" + iconv-lite "0.4.23" + on-finished "~2.3.0" + qs "6.5.2" + raw-body "2.3.3" + type-is "~1.6.16" + body-parser@1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" @@ -8694,7 +8722,7 @@ cheerio@^1.0.0-rc.2, cheerio@^1.0.0-rc.3: lodash "^4.15.0" parse5 "^3.0.1" -"chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.1.1, chokidar@^3.3.1: +"chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.1.1, chokidar@^3.2.2, chokidar@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== @@ -9368,6 +9396,21 @@ concat-stream@^2.0.0: readable-stream "^3.0.2" typedarray "^0.0.6" +concurrently@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.0.2.tgz#4d2911018c0f15ddec34a8e668fc48dced7f3b1e" + integrity sha512-iUNVI6PzKO0RVXV9pHWM0khvEbELxf3XLIoChaV6hHyoIaJuxQWZiOwlNysnJX5khsfvIK66+OJqRdbYrdsR1g== + dependencies: + chalk "^2.4.2" + date-fns "^2.0.1" + lodash "^4.17.15" + read-pkg "^4.0.1" + rxjs "^6.5.2" + spawn-command "^0.0.2-1" + supports-color "^6.1.0" + tree-kill "^1.2.2" + yargs "^13.3.0" + concurrently@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.1.0.tgz#05523986ba7aaf4b58a49ddd658fab88fa783132" @@ -9500,6 +9543,11 @@ contains-path@^0.1.0: resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= + content-disposition@0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" @@ -10522,7 +10570,7 @@ debug@3.1.0, debug@=3.1.0, debug@~3.1.0: dependencies: ms "2.0.0" -debug@3.2.6, debug@^3.0.0, debug@^3.0.1, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5: +debug@3.2.6, debug@^3.0.0, debug@^3.0.1, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -12990,6 +13038,42 @@ express@^4.10.7, express@^4.13.1, express@^4.16.2, express@^4.16.3, express@^4.1 utils-merge "1.0.1" vary "~1.1.2" +express@~4.16.4: + version "4.16.4" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e" + integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg== + dependencies: + accepts "~1.3.5" + array-flatten "1.1.1" + body-parser "1.18.3" + content-disposition "0.5.2" + content-type "~1.0.4" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.1.1" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.2" + path-to-regexp "0.1.7" + proxy-addr "~2.0.4" + qs "6.5.2" + range-parser "~1.2.0" + safe-buffer "5.1.2" + send "0.16.2" + serve-static "1.13.2" + setprototypeof "1.1.0" + statuses "~1.4.0" + type-is "~1.6.16" + utils-merge "1.0.1" + vary "~1.1.2" + ext@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" @@ -13394,6 +13478,19 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +finalhandler@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" + integrity sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.4.0" + unpipe "~1.0.0" + finalhandler@1.1.2, finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -15282,6 +15379,16 @@ http-deceiver@^1.2.7: resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= +http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + http-errors@1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" @@ -15304,16 +15411,6 @@ http-errors@1.7.3, http-errors@^1.7.3, http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - "http-parser-js@>=0.4.0 <0.4.11": version "0.4.10" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" @@ -15465,6 +15562,13 @@ i18next@^17.0.16: dependencies: "@babel/runtime" "^7.3.1" +iconv-lite@0.4.23: + version "0.4.23" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" + integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -15508,6 +15612,11 @@ iferr@^0.1.5: resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= + ignore-walk@^3.0.1: version "3.0.3" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" @@ -20928,6 +21037,22 @@ node-uuid@~1.4.0: resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907" integrity sha1-sEDrCSOWivq/jTL7HxfxFn/auQc= +nodemon@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.2.tgz#9c7efeaaf9b8259295a97e5d4585ba8f0cbe50b0" + integrity sha512-GWhYPMfde2+M0FsHnggIHXTqPDHXia32HRhh6H0d75Mt9FKUoCBvumNHr7LdrpPBTKxsWmIEOjoN+P4IU6Hcaw== + dependencies: + chokidar "^3.2.2" + debug "^3.2.6" + ignore-by-default "^1.0.1" + minimatch "^3.0.4" + pstree.remy "^1.1.7" + semver "^5.7.1" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.2" + update-notifier "^2.5.0" + "nopt@2 || 3", nopt@^3.0.6: version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" @@ -20943,6 +21068,13 @@ nopt@^4.0.1: abbrev "1" osenv "^0.1.4" +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= + dependencies: + abbrev "1" + normalize-html-whitespace@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/normalize-html-whitespace/-/normalize-html-whitespace-1.0.0.tgz#5e3c8e192f1b06c3b9eee4b7e7f28854c7601e34" @@ -22302,9 +22434,9 @@ physical-cpu-count@^2.0.0: integrity sha1-GN4vl+S/epVRrXURlCtUlverpmA= picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7: - version "2.2.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" - integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== + version "2.1.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.1.1.tgz#ecdfbea7704adb5fe6fb47f9866c4c0e15e905c5" + integrity sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA== pify@^2.0.0, pify@^2.2.0, pify@^2.3.0: version "2.3.0" @@ -23676,7 +23808,7 @@ protractor@~5.4.3: webdriver-js-extender "2.1.0" webdriver-manager "^12.0.6" -proxy-addr@~2.0.5: +proxy-addr@~2.0.4, proxy-addr@~2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ== @@ -23709,6 +23841,11 @@ psl@^1.1.24, psl@^1.1.28: resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== +pstree.remy@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.7.tgz#c76963a28047ed61542dc361aa26ee55a7fa15f3" + integrity sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A== + public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" @@ -23812,7 +23949,7 @@ pug-walk@^1.1.8: resolved "https://registry.yarnpkg.com/pug-walk/-/pug-walk-1.1.8.tgz#b408f67f27912f8c21da2f45b7230c4bd2a5ea7a" integrity sha512-GMu3M5nUL3fju4/egXwZO0XLi6fW/K3T3VTgFQ14GxNi8btlxgT5qZL//JwZFm/2Fa64J/PNS8AZeys3wiMkVA== -pug@^2.0.3: +pug@^2.0.3, pug@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/pug/-/pug-2.0.4.tgz#ee7682ec0a60494b38d48a88f05f3b0ac931377d" integrity sha512-XhoaDlvi6NIzL49nu094R2NA6P37ijtgMDuWE+ofekDChvfKnzFal60bhSdiy8y2PBO6fmz3oMEIcfpBVRUdvw== @@ -23915,6 +24052,11 @@ qrcode-terminal@^0.12.0: resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== +qs@6.5.2, qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + qs@6.7.0: version "6.7.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" @@ -23935,11 +24077,6 @@ qs@~2.3.3: resolved "https://registry.yarnpkg.com/qs/-/qs-2.3.3.tgz#e9e85adbe75da0bbe4c8e0476a086290f863b404" integrity sha1-6eha2+ddoLvkyOBHaghikPhjtAQ= -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== - query-string@^4.1.0: version "4.3.4" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" @@ -24180,6 +24317,16 @@ raptor-util@^3.1.0, raptor-util@^3.2.0: resolved "https://registry.yarnpkg.com/raptor-util/-/raptor-util-3.2.0.tgz#23b0c803c8f1ac8a1cae67d9a6388b49161c9758" integrity sha1-I7DIA8jxrIocrmfZpjiLSRYcl1g= +raw-body@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" + integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw== + dependencies: + bytes "3.0.0" + http-errors "1.6.3" + iconv-lite "0.4.23" + unpipe "1.0.0" + raw-body@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" @@ -26328,6 +26475,11 @@ safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== +safe-identifier@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/safe-identifier/-/safe-identifier-0.3.1.tgz#466b956ef8558b10bbe15b87fedf470ab283cd39" + integrity sha512-+vr9lVsmciuoP1fz8w30qDcohwH2S/tb5dPGQ8zHmG9jQf7YHU2fIKGxxcDpeY38J0Dep+DdPMz8FszVZT0Mbw== + safe-json-parse@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57" @@ -26615,10 +26767,10 @@ semver@~5.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8= -send@0.17.1, send@latest: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== +send@0.16.2, send@^0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" + integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw== dependencies: debug "2.6.9" depd "~1.1.2" @@ -26627,17 +26779,17 @@ send@0.17.1, send@latest: escape-html "~1.0.3" etag "~1.8.1" fresh "0.5.2" - http-errors "~1.7.2" - mime "1.6.0" - ms "2.1.1" + http-errors "~1.6.2" + mime "1.4.1" + ms "2.0.0" on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" + range-parser "~1.2.0" + statuses "~1.4.0" -send@^0.16.2: - version "0.16.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" - integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw== +send@0.17.1, send@latest: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== dependencies: debug "2.6.9" depd "~1.1.2" @@ -26646,12 +26798,12 @@ send@^0.16.2: escape-html "~1.0.3" etag "~1.8.1" fresh "0.5.2" - http-errors "~1.6.2" - mime "1.4.1" - ms "2.0.0" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" on-finished "~2.3.0" - range-parser "~1.2.0" - statuses "~1.4.0" + range-parser "~1.2.1" + statuses "~1.5.0" serialize-javascript@^1.4.0, serialize-javascript@^1.7.0: version "1.9.1" @@ -26692,6 +26844,16 @@ serve-index@^1.7.2, serve-index@^1.9.1: mime-types "~2.1.17" parseurl "~1.3.2" +serve-static@1.13.2: + version "1.13.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" + integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.2" + send "0.16.2" + serve-static@1.14.1, serve-static@^1.12.4: version "1.14.1" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" @@ -28104,7 +28266,7 @@ sum-up@^1.0.1: dependencies: chalk "^1.0.0" -supports-color@5.5.0, supports-color@^5.0.0, supports-color@^5.3.0, supports-color@^5.4.0: +supports-color@5.5.0, supports-color@^5.0.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -28813,6 +28975,13 @@ topo@2.x.x: dependencies: hoek "4.x.x" +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + tough-cookie@>=0.12.0, tough-cookie@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" @@ -29233,7 +29402,7 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@~1.6.17, type-is@~1.6.18: +type-is@~1.6.16, type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -29375,6 +29544,13 @@ undeclared-identifiers@^1.1.2: simple-concat "^1.0.0" xtend "^4.0.1" +undefsafe@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76" + integrity sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY= + dependencies: + debug "^2.2.0" + underscore.string@^3.2.2, underscore.string@~3.3.4: version "3.3.5" resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.5.tgz#fc2ad255b8bd309e239cbc5816fd23a9b7ea4023" @@ -29776,7 +29952,7 @@ upath@^1.0.2, upath@^1.1.0, upath@^1.1.1, upath@^1.2.0: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== -update-notifier@^2.2.0: +update-notifier@^2.2.0, update-notifier@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==