Merge branch 'release/3.2' into update-global-hook

This commit is contained in:
Norbert de Langen 2017-07-22 22:56:58 +02:00 committed by GitHub
commit ecd054d590
67 changed files with 2902 additions and 467 deletions

185
.circleci/config.yml Normal file
View File

@ -0,0 +1,185 @@
defaults: &defaults
working_directory: /tmp/storybook
docker:
- image: node:8
version: 2
dependencies:
pre:
- npm install -g npm
jobs:
validate:
<<: *defaults
steps:
- run:
name: "Checking Versions"
command: |
node --version
npm --version
yarn --version
build:
<<: *defaults
steps:
- checkout
- restore_cache:
keys:
- root-dependencies-{{ checksum "package.json" }}
- root-dependencies-
- run:
name: "Install root dependencies"
command: |
yarn install
- save_cache:
key: root-dependencies-{{ checksum "package.json" }}
paths:
- node_modules
- restore_cache:
keys:
- package-dependencies-{{ checksum "package.json" }}
- package-dependencies-
- run:
name: "Bootstrapping"
command: |
npm run bootstrap
- save_cache:
key: package-dependencies-{{ checksum "package.json" }}
paths:
- app/**/node_modules
- docs/**/node_modules
- examples/**/node_modules
- lib/**/node_modules
example-kitchen-sink:
<<: *defaults
steps:
- run:
name: "Running kitchen-sink"
command: |
echo "TODO"
example-test-cra:
<<: *defaults
steps:
- checkout
- restore_cache:
keys:
- root-dependencies-{{ checksum "package.json" }}
- root-dependencies-
- run:
name: "Install root dependencies"
command: |
yarn install
- run:
name: "Bootstrapping"
command: |
npm run bootstrap
npm run bootstrap:test-cra
- run:
name: "Running test-cra"
command: |
echo "TODO"
example-react-native:
<<: *defaults
steps:
- checkout
- restore_cache:
keys:
- root-dependencies-{{ checksum "package.json" }}
- root-dependencies-
- run:
name: "Install root dependencies"
command: |
yarn install
- run:
name: "Bootstrapping packages"
command: |
npm run bootstrap
npm run bootstrap:react-native-vanilla
- run:
name: "Running react-native"
command: |
echo "TODO"
docs:
<<: *defaults
steps:
- checkout
- restore_cache:
keys:
- root-dependencies-{{ checksum "package.json" }}
- root-dependencies-
- run:
name: "Install root dependencies"
command: |
yarn install
- run:
name: "Bootstrapping"
command: |
npm run bootstrap:docs
- run:
name: "Running docs"
command: |
npm run docs:build
lint:
<<: *defaults
steps:
- checkout
- restore_cache:
keys:
- root-dependencies-{{ checksum "package.json" }}
- root-dependencies-
- run:
name: "Install root dependencies"
command: |
yarn install
- run:
name: "Linting"
command: |
npm run lint
unit-test:
<<: *defaults
steps:
- checkout
- restore_cache:
keys:
- root-dependencies-{{ checksum "package.json" }}
- root-dependencies-
- run:
name: "Install root dependencies"
command: |
yarn install
- run:
name: "Bootstrapping"
command: |
npm run bootstrap
npm run bootstrap:docs
npm run bootstrap:test-cra
npm run bootstrap:react-native-vanilla
- run:
name: "Unit testing"
command: |
npm run test -- --coverage -i
npm run coverage
deploy:
<<: *defaults
steps:
- run:
name: "Deploy"
command: |
echo "TODO"
workflows:
version: 2
build_accept_deploy:
jobs:
- validate
- build
- example-kitchen-sink
- example-test-cra
- example-react-native
- docs
- lint
- unit-test
- deploy:
type: approval
requires:
- lint
- unit-test
- docs

View File

@ -1,33 +0,0 @@
language: node_js
cache:
directories:
- node_modules
- ".cache"
notifications:
email: false
node_js:
- node
before_install: "./scripts/travis/before_install.sh"
after_success: "./scripts/travis/after_success.sh"
script:
- npm run bootstrap
- npm run bootstrap:test-cra
- npm run bootstrap:react-native-vanilla
- npm run bootstrap:docs
- npm run lint
- npm run test -- --coverage
- npm run coverage
- ([ -z "$DANGER_GITHUB_API_TOKEN" ] && echo "DANGER_GITHUB_API_TOKEN not set") || npm run danger
git:
depth: 1
env:
global:
- CXX=g++-4.8
- secure: cAde4wBX75KtTWcyOOLAG3Z9ODdqvmbkL7+8fVNj/+QkZZWE8pFa3deaTIHF9NyVO2h6/jutSkzmsz/nOyBYVPHhGsxBTmsyXoko48Wg+iNm7epoH5uts/kmAPiwpzaWGXwuiAvsOGZjYYFzM335jyaOAcZW3f0C5gIJ5XCCdWBQRaFFLq+ZLKsLSME6xTfV2OMVH24hxXvbF9wvO0aj6p/GaT0cS8Rpg4sQ9eeih2IM/uLiqWzp9UUM2m8SUiFfveqYJFkBtzqAus9pbwsoQjnAT5e3CKJUpPiruCAe5FOt57Hl+mH1N1xqP1ei8j2ZNF+E6zuDdAcMpArTIMM69L+D7wzJYDoF2PuF+jeet7ytAFxSgnZHSTsBJn5cZMPh2tuX7aWwgrpMknVe3bdoINwkyVCaIW+Ur6vc37l/Kuw25eiMBtRDyMhUf4V3FAFi3PV1XKn+34cR4kvpOHt6vk8v5CobBHfQdwU+6FMZMo3GFIkDBcLydLn3WLQ3jKa4OcLqWws6o85k+bHZkLhlADjbiX/PzG23D+sT7Inzj//Tef93SIL02yN+ooZdIUtDus3+qZzhcSrDeSb2octjLXRzPiGn5cFNI86HVcu7qF0+4zCPconhM4+mfAh5S19fmnRdTQctQQxbsObuT9jcMvgJdhIX89aA7Ry3pAx2b6Q=
- secure: 1iZAjDqikmJaXvql/bpNZay9u5yyQCwNyz3OmcvhJDds6lIKUUU24S3srsYatmE2lkpUuRertieGtzMPkhNx/rFRePD7SZAnSMADyA+lxW9SWu8LHEBcjgsDY89iJH6+4BiYIrldP0vjORYJDwtiEKB3bSJX88zkLpTVTS/L5EHS9kQ/xtQsvoNB69C1ExCt7EVgSL9cDVccFvrU1FhC8w4eH5j3fNikQduaPhF+iqoTo8GW/m22/95Xhmngcu7n6Fm8G/YA2xmkvdCBLL7rLSwccwG9j9MRei704NrBOux7xN/2euVg5jYbFCaDQNjuu4UrvVB2YFxqvpsjYByktfqNMTheqcGdVuh3Jd7cMGmkMSBhUgmUW5KPH4v73mLUlHroywQUU3iiMiCvVMmTd5Xy0o0X0ks5mvXAXWHTmnQkvPyy/V6cwcUfgJC5k3M/V1hDCRMeMVPZsmh73lZffwpAkfx3xJsNf46f7h9aN9Q++danf9JryoS9YsdRBtjfJTtNaO2uH+iUMNSNzAbJmgxtsi3NuX+G+2N02mtaUCzN/SOmKKoZsuPfKVcpHgVa41mgio6QJs7wy9/VWYYASNZddj2HnvLGLBLnJpv+uKKpdSB0Xe6k8dOl1MbaWJT4Xy0/NUD3gawLCvhfN9qL0+QnlJAXCbOoms6EoAyc6fY=
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8

View File

@ -1,3 +1,29 @@
# 3.1.9
2017-July-16
#### Features
- React fiber support [#1443](https://github.com/storybooks/storybook/pull/1443)
#### Documentation
- Refine docs about loading stories dynamically for react-native [#1420](https://github.com/storybooks/storybook/pull/1420)
#### Bug Fixes
- Verify that name is a string in addons/actions [#1415](https://github.com/storybooks/storybook/pull/1415)
- Knobs: fix label alignment [#1471](https://github.com/storybooks/storybook/pull/1471)
- Fix display of large components [#1237](https://github.com/storybooks/storybook/pull/1237)
#### Dependency Upgrades
- Dependency updates [#1439](https://github.com/storybooks/storybook/pull/1439)
- chore(package): update husky to version 0.14.3 [#1437](https://github.com/storybooks/storybook/pull/1437)
- Update danger to the latest version 🚀 [#1393](https://github.com/storybooks/storybook/pull/1393)
- Update lerna to the latest version 🚀 [#1423](https://github.com/storybooks/storybook/pull/1423)
- Pin gatsby version and upgrade gh-pages [#1462](https://github.com/storybooks/storybook/pull/1462)
# 3.1.8
2017-July-06

View File

@ -1,11 +1,11 @@
# Storybook
[![Greenkeeper badge](https://badges.greenkeeper.io/storybooks/storybook.svg)](https://greenkeeper.io/)
[![Build Status](https://travis-ci.org/storybooks/storybook.svg?branch=master)](https://travis-ci.org/storybooks/storybook)
[![Build Status on CircleCI](https://circleci.com/gh/storybooks/storybook.svg?style=shield)](https://circleci.com/gh/storybooks/storybook)
[![CodeFactor](https://www.codefactor.io/repository/github/storybooks/storybook/badge)](https://www.codefactor.io/repository/github/storybooks/storybook)
[![Known Vulnerabilities](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847/badge.svg)](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847)
[![BCH compliance](https://bettercodehub.com/edge/badge/storybooks/storybook)](https://bettercodehub.com/results/storybooks/storybook) [![codecov](https://codecov.io/gh/storybooks/storybook/branch/master/graph/badge.svg)](https://codecov.io/gh/storybooks/storybook)
[![Storybook Slack](https://storybooks-slackin.herokuapp.com/badge.svg)](https://storybooks-slackin.herokuapp.com/)
[![BCH compliance](https://bettercodehub.com/edge/badge/storybooks/storybook)](https://bettercodehub.com/results/storybooks/storybook) [![codecov](https://codecov.io/gh/storybooks/storybook/branch/master/graph/badge.svg)](https://codecov.io/gh/storybooks/storybook)
[![Storybook Slack](https://now-examples-slackin-nqnzoygycp.now.sh/badge.svg)](https://now-examples-slackin-nqnzoygycp.now.sh/)
[![Backers on Open Collective](https://opencollective.com/storybook/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/storybook/sponsors/badge.svg)](#sponsors)
* * *

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-actions",
"version": "3.2.0-alpha.7",
"version": "3.2.0-alpha.8",
"description": "Action Logger addon for storybook",
"keywords": [
"storybook"
@ -21,10 +21,10 @@
"storybook": "start-storybook -p 9001"
},
"dependencies": {
"@storybook/addons": "^3.1.6",
"@storybook/addons": "^3.2.0-alpha.8",
"deep-equal": "^1.0.1",
"json-stringify-safe": "^5.0.1",
"prop-types": "^15.5.8",
"prop-types": "^15.5.10",
"react-inspector": "^2.1.1",
"uuid": "^3.1.0"
},

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-centered",
"version": "3.1.2",
"version": "3.2.0-alpha.8",
"description": "Storybook decorator to center components",
"license": "MIT",
"author": "Muhammed Thanish <mnmtanish@gmail.com>",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-comments",
"version": "3.1.6",
"version": "3.2.0-alpha.8",
"description": "Comments addon for Storybook",
"keywords": [
"storybook"
@ -23,7 +23,7 @@
"storybook-remote": "start-storybook -p 3006"
},
"dependencies": {
"@storybook/addons": "^3.1.6",
"@storybook/addons": "^3.2.0-alpha.8",
"babel-runtime": "^6.23.0",
"deep-equal": "^1.0.1",
"events": "^1.1.1",
@ -31,7 +31,7 @@
"insert-css": "^1.0.0",
"marked": "^0.3.6",
"moment": "^2.18.1",
"prop-types": "^15.5.8",
"prop-types": "^15.5.10",
"react-render-html": "^0.1.6",
"react-textarea-autosize": "^4.3.0"
},

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-events",
"version": "3.1.6",
"version": "3.2.0-alpha.8",
"description": "Add events to your Storybook stories.",
"keywords": [
"addon",
@ -20,16 +20,16 @@
"storybook": "start-storybook -p 6006"
},
"dependencies": {
"@storybook/addons": "^3.1.6",
"babel-runtime": "^6.5.0",
"@storybook/addons": "^3.2.0-alpha.8",
"babel-runtime": "^6.23.0",
"format-json": "^1.0.3",
"prop-types": "^15.5.10",
"react-textarea-autosize": "^4.0.5",
"uuid": "^3.0.1"
"react-textarea-autosize": "^4.3.0",
"uuid": "^3.1.0"
},
"devDependencies": {
"react": "^15.3.2",
"react-dom": "^15.3.2"
"react": "^15.5.4",
"react-dom": "^15.5.4"
},
"peerDependencies": {
"react": "*"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-info",
"version": "3.1.6",
"version": "3.2.0-alpha.8",
"description": "A Storybook addon to show additional information for your stories.",
"license": "MIT",
"main": "dist/index.js",
@ -14,11 +14,11 @@
"storybook": "start-storybook -p 9010"
},
"dependencies": {
"@storybook/addons": "^3.1.6",
"@storybook/addons": "^3.2.0-alpha.8",
"babel-runtime": "^6.23.0",
"global": "^4.3.2",
"marksy": "^2.0.0",
"prop-types": "^15.5.8",
"prop-types": "^15.5.10",
"react-addons-create-fragment": "^15.5.3",
"util-deprecate": "^1.0.2"
},

View File

@ -234,10 +234,11 @@ const value = date(label, defaultValue);
If you feel like this addon is not performing well enough there is an option to use `withKnobsOptions` instead of `withKnobs`.
Usage:
```
```js
story.addDecorator(withKnobsOptions({
debounce: { wait: number, leading: boolean}, // Same as lodash debounce.
timestamps: true // Doesn't emit events while user is typing.
debounce: { wait: number, leading: boolean}, // Same as lodash debounce.
timestamps: true // Doesn't emit events while user is typing.
}));
```

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-knobs",
"version": "3.2.0-alpha.7",
"version": "3.2.0-alpha.8",
"description": "Storybook Addon Prop Editor Component",
"license": "MIT",
"main": "dist/index.js",
@ -15,18 +15,18 @@
"storybook": "start-storybook -p 9010"
},
"dependencies": {
"@storybook/addons": "^3.1.6",
"@storybook/addons": "^3.2.0-alpha.8",
"babel-runtime": "^6.23.0",
"deep-equal": "^1.0.1",
"global": "^4.3.2",
"insert-css": "^1.0.0",
"lodash.debounce": "^4.0.8",
"moment": "^2.18.1",
"prop-types": "^15.5.8",
"prop-types": "^15.5.10",
"react-color": "^2.11.4",
"react-datetime": "^2.8.10",
"react-textarea-autosize": "^4.3.0",
"util-deprecate": "1.0.2"
"util-deprecate": "^1.0.2"
},
"devDependencies": {
"@types/node": "^7.0.12",
@ -38,7 +38,7 @@
"style-loader": "^0.17.0",
"typescript": "^2.2.2",
"typescript-definition-tester": "^0.0.5",
"vue": "2.3.4"
"vue": "^2.4.1"
},
"peerDependencies": {
"react": "*",

View File

@ -16,7 +16,7 @@ const stylesheet = {
boxSizing: 'border-box',
verticalAlign: 'top',
paddingRight: 5,
paddingTop: 7,
paddingTop: 5,
textAlign: 'right',
width: 80,
fontSize: 12,

View File

@ -5,7 +5,7 @@ const styles = {
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
height: '26px',
height: '25px',
width: '100%',
outline: 'none',
border: '1px solid #f7f4f4',

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-links",
"version": "3.2.0-alpha.5",
"version": "3.2.0-alpha.8",
"description": "Story Links addon for storybook",
"keywords": [
"storybook"
@ -21,7 +21,7 @@
"storybook": "start-storybook -p 9001"
},
"dependencies": {
"@storybook/addons": "^3.1.6"
"@storybook/addons": "^3.2.0-alpha.8"
},
"devDependencies": {
"react": "^15.5.4",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-notes",
"version": "3.2.0-alpha.5",
"version": "3.2.0-alpha.8",
"description": "Write notes for your Storybook stories.",
"keywords": [
"addon",
@ -19,7 +19,7 @@
"storybook": "start-storybook -p 9010"
},
"dependencies": {
"@storybook/addons": "^3.1.6",
"@storybook/addons": "^3.2.0-alpha.8",
"babel-runtime": "^6.23.0",
"util-deprecate": "^1.0.2"
},

View File

@ -5,7 +5,7 @@ import { WithNotes as ReactWithNotes } from './react';
export const addonNotes = ({ notes }) => {
const channel = addons.getChannel();
return getStory => (context) => {
return getStory => context => {
// send the notes to the channel before the story is rendered
channel.emit('storybook/notes/add_notes', notes);
return getStory(context);

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-options",
"version": "3.2.0-alpha.5",
"version": "3.2.0-alpha.8",
"description": "Options addon for storybook",
"keywords": [
"storybook"
@ -20,7 +20,7 @@
"storybook": "start-storybook -p 9001"
},
"dependencies": {
"@storybook/addons": "^3.1.6"
"@storybook/addons": "^3.2.0-alpha.8"
},
"devDependencies": {
"react": "^15.5.4",

View File

@ -32,6 +32,8 @@ Usually, you might already have completed this step. If not, here are some resou
- If you are using Create React App, it's already configured for Jest. You just need to create a filename with the extension `.test.js`.
- Otherwise check this Egghead [lesson](https://egghead.io/lessons/javascript-test-javascript-with-jest).
> Note: If you use React 16, you'll need to follow [these additional instructions](https://github.com/facebook/react/issues/9102#issuecomment-283873039).
## Configure Storyshots
Create a new test file with the name `Storyshots.test.js`. (Or whatever the name you prefer).

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-storyshots",
"version": "3.2.0-alpha.7",
"version": "3.2.0-alpha.8",
"description": "StoryShots is a Jest Snapshot Testing Addon for Storybook.",
"license": "MIT",
"main": "dist/index.js",
@ -16,13 +16,13 @@
"dependencies": {
"babel-runtime": "^6.23.0",
"global": "^4.3.2",
"prop-types": "^15.5.8",
"prop-types": "^15.5.10",
"read-pkg-up": "^2.0.0"
},
"devDependencies": {
"@storybook/addons": "^3.1.6",
"@storybook/addons": "^3.2.0-alpha.8",
"@storybook/channels": "^3.1.6",
"@storybook/react": "^3.2.0-alpha.7",
"@storybook/react": "^3.2.0-alpha.8",
"babel-cli": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015": "^6.24.1",
@ -31,9 +31,9 @@
"react-dom": "^15.5.4"
},
"peerDependencies": {
"@storybook/addons": "^3.1.6",
"@storybook/addons": "^3.2.0-alpha.8",
"@storybook/channels": "^3.1.6",
"@storybook/react": "^3.2.0-alpha.7",
"@storybook/react": "^3.2.0-alpha.8",
"babel-core": "^6.24.1",
"react": "*",
"react-test-renderer": "*"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/react-native",
"version": "3.2.0-alpha.7",
"version": "3.2.0-alpha.8",
"description": "A better way to develop React Native Components for your app",
"keywords": [
"react",
@ -24,12 +24,12 @@
"prepublish": "node ../../scripts/prepublish.js"
},
"dependencies": {
"@storybook/addon-actions": "^3.2.0-alpha.7",
"@storybook/addon-links": "^3.2.0-alpha.5",
"@storybook/addons": "^3.1.6",
"@storybook/addon-actions": "^3.2.0-alpha.8",
"@storybook/addon-links": "^3.2.0-alpha.8",
"@storybook/addons": "^3.2.0-alpha.8",
"@storybook/channel-websocket": "^3.1.6",
"@storybook/ui": "^3.2.0-alpha.7",
"autoprefixer": "^7.0.1",
"@storybook/ui": "^3.2.0-alpha.8",
"autoprefixer": "^7.1.1",
"babel-core": "^6.24.1",
"babel-loader": "^7.0.0",
"babel-plugin-syntax-async-functions": "^6.13.0",
@ -47,22 +47,22 @@
"babel-runtime": "^6.23.0",
"case-sensitive-paths-webpack-plugin": "^2.0.0",
"commander": "^2.9.0",
"css-loader": "^0.28.0",
"css-loader": "^0.28.1",
"events": "^1.1.1",
"express": "^4.15.2",
"express": "^4.15.3",
"file-loader": "^0.11.1",
"find-cache-dir": "^1.0.0",
"global": "^4.3.2",
"json-loader": "^0.5.4",
"json5": "^0.5.1",
"postcss-loader": "^2.0.3",
"postcss-loader": "^2.0.5",
"shelljs": "^0.7.7",
"style-loader": "^0.17.0",
"url-loader": "^0.5.8",
"util-deprecate": "^1.0.2",
"uuid": "^3.0.1",
"webpack": "^2.4.1",
"webpack-dev-middleware": "^1.10.1",
"uuid": "^3.1.0",
"webpack": "^2.5.1 || ^3.0.0",
"webpack-dev-middleware": "^1.10.2",
"webpack-hot-middleware": "^2.18.0",
"ws": "^3.0.0"
},

View File

@ -0,0 +1,37 @@
import React, { PropTypes } from 'react';
import { View } from 'react-native';
import style from './style';
import StoryListView from '../StoryListView';
import StoryView from '../StoryView';
export default function OnDeviceUI(props) {
const { stories, events, url } = props;
return (
<View style={style.main}>
<View style={style.leftPanel}>
<StoryListView stories={stories} events={events} />
</View>
<View style={style.rightPanel}>
<View style={style.preview}>
<StoryView url={url} events={events} />
</View>
</View>
</View>
);
}
OnDeviceUI.propTypes = {
stories: PropTypes.shape({
dumpStoryBook: PropTypes.func.isRequired,
on: PropTypes.func.isRequired,
emit: PropTypes.func.isRequired,
removeListener: PropTypes.func.isRequired,
}).isRequired,
events: PropTypes.shape({
on: PropTypes.func.isRequired,
emit: PropTypes.func.isRequired,
removeListener: PropTypes.func.isRequired,
}).isRequired,
url: PropTypes.string.isRequired,
};

View File

@ -0,0 +1,28 @@
import { StyleSheet } from 'react-native';
export default {
main: {
flex: 1,
flexDirection: 'row',
paddingTop: 20,
backgroundColor: 'rgba(247, 247, 247, 1)',
},
leftPanel: {
flex: 1,
maxWidth: 250,
paddingHorizontal: 8,
paddingBottom: 8,
},
rightPanel: {
flex: 1,
backgroundColor: 'rgba(255, 255, 255, 1)',
borderWidth: StyleSheet.hairlineWidth,
borderColor: 'rgba(236, 236, 236, 1)',
borderRadius: 4,
marginBottom: 8,
marginHorizontal: 8,
},
preview: {
...StyleSheet.absoluteFillObject,
},
};

View File

@ -0,0 +1,121 @@
import React, { Component, PropTypes } from 'react';
import { SectionList, View, Text, TouchableOpacity } from 'react-native';
import style from './style';
const SectionHeader = ({ title, selected }) =>
<View key={title} style={style.header}>
<Text style={[style.headerText, selected && style.headerTextSelected]}>
{title}
</Text>
</View>;
SectionHeader.propTypes = {
title: PropTypes.string.isRequired,
selected: PropTypes.bool.isRequired,
};
const ListItem = ({ title, selected, onPress }) =>
<TouchableOpacity key={title} style={style.item} onPress={onPress}>
<Text style={[style.itemText, selected && style.itemTextSelected]}>
{title}
</Text>
</TouchableOpacity>;
ListItem.propTypes = {
title: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired,
selected: PropTypes.bool.isRequired,
};
export default class StoryListView extends Component {
constructor(props, ...args) {
super(props, ...args);
this.state = {
sections: [],
selectedKind: null,
selectedStory: null,
};
this.storyAddedHandler = this.handleStoryAdded.bind(this);
this.storyChangedHandler = this.handleStoryChanged.bind(this);
this.changeStoryHandler = this.changeStory.bind(this);
this.props.stories.on('storyAdded', this.storyAddedHandler);
this.props.events.on('story', this.storyChangedHandler);
}
componentDidMount() {
this.handleStoryAdded();
}
componentWillUnmount() {
this.props.stories.removeListener('storyAdded', this.storiesHandler);
this.props.events.removeListener('story', this.storyChangedHandler);
}
handleStoryAdded() {
if (this.props.stories) {
const data = this.props.stories.dumpStoryBook();
this.setState({
sections: data.map(section => ({
key: section.kind,
title: section.kind,
data: section.stories.map(story => ({
key: story,
kind: section.kind,
name: story,
})),
})),
});
}
}
handleStoryChanged(storyFn, selection) {
const { kind, story } = selection;
this.setState({
selectedKind: kind,
selectedStory: story,
});
}
changeStory(kind, story) {
this.props.events.emit('setCurrentStory', { kind, story });
}
render() {
return (
<SectionList
style={style.list}
renderItem={({ item }) =>
<ListItem
title={item.name}
selected={
item.kind === this.state.selectedKind && item.name === this.state.selectedStory
}
onPress={() => this.changeStory(item.kind, item.name)}
/>}
renderSectionHeader={({ section }) =>
<SectionHeader
title={section.title}
selected={section.title === this.state.selectedKind}
/>}
sections={this.state.sections}
stickySectionHeadersEnabled={false}
/>
);
}
}
StoryListView.propTypes = {
stories: PropTypes.shape({
dumpStoryBook: PropTypes.func.isRequired,
on: PropTypes.func.isRequired,
emit: PropTypes.func.isRequired,
removeListener: PropTypes.func.isRequired,
}).isRequired,
events: PropTypes.shape({
on: PropTypes.func.isRequired,
emit: PropTypes.func.isRequired,
removeListener: PropTypes.func.isRequired,
}).isRequired,
};

View File

@ -0,0 +1,26 @@
export default {
list: {
flex: 1,
maxWidth: 250,
},
header: {
paddingTop: 24,
paddingBottom: 4,
},
headerText: {
fontSize: 16,
},
headerTextSelected: {
fontWeight: 'bold',
},
item: {
paddingVertical: 4,
paddingHorizontal: 16,
},
itemText: {
fontSize: 14,
},
itemTextSelected: {
fontWeight: 'bold',
},
};

View File

@ -6,6 +6,7 @@ import createChannel from '@storybook/channel-websocket';
import { EventEmitter } from 'events';
import StoryStore from './story_store';
import StoryKindApi from './story_kind';
import OnDeviceUI from './components/OnDeviceUI';
import StoryView from './components/StoryView';
export default class Preview {
@ -70,11 +71,14 @@ export default class Preview {
}
channel.on('getStories', () => this._sendSetStories());
channel.on('setCurrentStory', d => this._selectStory(d));
this._events.on('setCurrentStory', d => this._selectStory(d));
this._sendSetStories();
this._sendGetCurrentStory();
// finally return the preview component
return <StoryView url={webUrl} events={this._events} />;
return params.onDeviceUI
? <OnDeviceUI stories={this._stories} events={this._events} url={webUrl} />
: <StoryView url={webUrl} events={this._events} />;
};
}

View File

@ -1,8 +1,11 @@
/* eslint no-underscore-dangle: 0 */
import { EventEmitter } from 'events';
let count = 0;
export default class StoryStore {
export default class StoryStore extends EventEmitter {
constructor() {
super();
this._data = {};
}
@ -21,6 +24,8 @@ export default class StoryStore {
index: count,
fn,
};
this.emit('storyAdded', kind, name, fn);
}
getStoryKinds() {

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/react",
"version": "3.2.0-alpha.7",
"version": "3.2.0-alpha.8",
"description": "Storybook for React: Develop React Component in isolation with Hot Reloading.",
"homepage": "https://github.com/storybooks/storybook/tree/master/apps/react",
"bugs": {
@ -22,11 +22,11 @@
"prepublish": "node ../../scripts/prepublish.js"
},
"dependencies": {
"@storybook/addon-actions": "^3.2.0-alpha.7",
"@storybook/addon-links": "^3.2.0-alpha.5",
"@storybook/addons": "^3.1.6",
"@storybook/addon-actions": "^3.2.0-alpha.8",
"@storybook/addon-links": "^3.2.0-alpha.8",
"@storybook/addons": "^3.2.0-alpha.8",
"@storybook/channel-postmessage": "^3.1.6",
"@storybook/ui": "^3.2.0-alpha.7",
"@storybook/ui": "^3.2.0-alpha.8",
"airbnb-js-shims": "^1.1.1",
"autoprefixer": "^7.1.1",
"babel-core": "^6.24.1",
@ -39,7 +39,7 @@
"babel-preset-stage-0": "^6.24.1",
"babel-runtime": "^6.23.0",
"case-sensitive-paths-webpack-plugin": "^2.0.0",
"chalk": "^1.1.3",
"chalk": "^2.0.1",
"commander": "^2.9.0",
"common-tags": "^1.4.0",
"configstore": "^3.1.0",
@ -53,6 +53,7 @@
"json-loader": "^0.5.4",
"json-stringify-safe": "^5.0.1",
"json5": "^0.5.1",
"lodash.flattendeep": "^4.4.0",
"lodash.pick": "^4.4.0",
"postcss-flexbugs-fixes": "^3.0.0",
"postcss-loader": "^2.0.5",
@ -66,7 +67,7 @@
"style-loader": "^0.17.0",
"url-loader": "^0.5.8",
"util-deprecate": "^1.0.2",
"uuid": "^3.0.1",
"uuid": "^3.1.0",
"webpack": "^2.5.1 || ^3.0.0",
"webpack-dev-middleware": "^1.10.2",
"webpack-hot-middleware": "^2.18.0"

View File

@ -0,0 +1,41 @@
import React from 'react';
import flattenDeep from 'lodash.flattendeep';
// return true if the element is renderable with react fiber
export const isValidFiberElement = element =>
typeof element === 'string' || typeof element === 'number' || React.isValidElement(element);
export const isPriorToFiber = version => {
const [majorVersion] = version.split('.');
return Number(majorVersion) < 16;
};
// accepts an element and return true if renderable else return false
const isReactRenderable = element => {
// storybook is running with a version prior to fiber,
// run a simple check on the element
if (isPriorToFiber(React.version)) {
return React.isValidElement(element);
}
// the element is not an array, check if its a fiber renderable element
if (!Array.isArray(element)) {
return isValidFiberElement(element);
}
// the element is in fact a list of elements (array),
// loop on its elements to see if its ok to render them
const elementsList = element.map(isReactRenderable);
// flatten the list of elements (possibly deep nested)
const flatList = flattenDeep(elementsList);
// keep only invalid elements
const invalidElements = flatList.filter(elementIsRenderable => elementIsRenderable === false);
// it's ok to render this list if there is no invalid elements inside
return !invalidElements.length;
};
export default isReactRenderable;

View File

@ -0,0 +1,86 @@
import React from 'react';
import isReactRenderable, { isValidFiberElement, isPriorToFiber } from './element_check';
describe('element_check.utils.isValidFiberElement', () => {
it('should accept to render a string', () => {
const string = 'react is awesome';
expect(isValidFiberElement(string)).toBe(true);
});
it('should accept to render a number', () => {
const number = 42;
expect(isValidFiberElement(number)).toBe(true);
});
it('should accept to render a valid React element', () => {
const element = <button>Click me</button>;
expect(isValidFiberElement(element)).toBe(true);
});
it("shouldn't accept to render an arbitrary object", () => {
const object = { key: 'bee bop' };
expect(isValidFiberElement(object)).toBe(false);
});
it("shouldn't accept to render a function", () => {
const noop = () => {};
expect(isValidFiberElement(noop)).toBe(false);
});
it("shouldn't accept to render undefined", () => {
expect(isValidFiberElement(undefined)).toBe(false);
});
});
describe('element_check.utils.isPriorToFiber', () => {
it('should return true if React version is prior to Fiber (< 16)', () => {
const oldVersion = '0.14.5';
const version = '15.5.4';
expect(isPriorToFiber(oldVersion)).toBe(true);
expect(isPriorToFiber(version)).toBe(true);
});
it('should return false if React version is using Fiber features (>= 16)', () => {
const alphaVersion = '16.0.0-alpha.13';
const version = '18.3.1';
expect(isPriorToFiber(alphaVersion)).toBe(false);
expect(isPriorToFiber(version)).toBe(false);
});
});
describe('element_check.isReactRenderable', () => {
const string = 'yo';
const number = 1337;
const element = <span>what's up</span>;
const array = [string, number, element];
const object = { key: null };
it('allows rendering React elements only prior to React Fiber', () => {
// mutate version for the purpose of the test
React.version = '15.5.4';
expect(isReactRenderable(string)).toBe(false);
expect(isReactRenderable(number)).toBe(false);
expect(isReactRenderable(element)).toBe(true);
expect(isReactRenderable(array)).toBe(false);
expect(isReactRenderable(object)).toBe(false);
});
it('allows rendering string, numbers, arrays and React elements with React Fiber', () => {
// mutate version for the purpose of the test
React.version = '16.0.0-alpha.13';
expect(isReactRenderable(string)).toBe(true);
expect(isReactRenderable(number)).toBe(true);
expect(isReactRenderable(element)).toBe(true);
expect(isReactRenderable(array)).toBe(true);
expect(isReactRenderable(object)).toBe(false);
});
});

View File

@ -3,6 +3,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { stripIndents } from 'common-tags';
import isReactRenderable from './element_check';
import ErrorDisplay from './error_display';
// check whether we're running on node/browser
@ -83,13 +84,13 @@ export function renderMain(data, storyStore) {
return renderError(error);
}
if (element.type === undefined) {
if (!isReactRenderable(element)) {
const error = {
title: `Expecting a valid React element from the story: "${selectedStory}" of "${selectedKind}".`,
description: stripIndents`
Seems like you are not returning a correct React element from the story.
Could you double check that?
`,
Seems like you are not returning a correct React element from the story.
Could you double check that?
`,
};
return renderError(error);
}

View File

@ -73,7 +73,10 @@ export default function(configType, baseConfig, configDir) {
...config.module,
// We need to use our and custom rules.
...customConfig.module,
rules: [...config.module.rules, ...(customConfig.module.rules || [])],
rules: [
...config.module.rules,
...((customConfig.module && customConfig.module.rules) || []),
],
},
resolve: {
...config.resolve,

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/vue",
"version": "3.2.0-alpha.7",
"version": "3.2.0-alpha.8",
"description": "Storybook for Vue: Develop Vue Component in isolation with Hot Reloading.",
"homepage": "https://github.com/storybooks/storybook/tree/master/apps/vue",
"bugs": {
@ -22,23 +22,23 @@
"prepublish": "node ../../scripts/prepublish.js"
},
"dependencies": {
"@storybook/addon-actions": "^3.1.2",
"@storybook/addon-links": "^3.2.0-alpha.5",
"@storybook/addons": "^3.1.2",
"@storybook/channel-postmessage": "^3.1.2",
"@storybook/ui": "^3.2.0-alpha.5",
"@storybook/addon-actions": "^3.2.0-alpha.8",
"@storybook/addon-links": "^3.2.0-alpha.8",
"@storybook/addons": "^3.2.0-alpha.8",
"@storybook/channel-postmessage": "^3.1.6",
"@storybook/ui": "^3.2.0-alpha.8",
"airbnb-js-shims": "^1.1.1",
"autoprefixer": "^7.1.1",
"babel-core": "^6.24.1",
"babel-loader": "^7.0.0",
"babel-plugin-react-docgen": "^1.5.0",
"babel-preset-env": "1.5.2",
"babel-preset-env": "^1.5.2",
"babel-preset-react": "^6.24.1",
"babel-preset-react-app": "^3.0.0",
"babel-preset-stage-0": "^6.24.1",
"babel-runtime": "^6.23.0",
"case-sensitive-paths-webpack-plugin": "^2.0.0",
"chalk": "^1.1.3",
"chalk": "^2.0.1",
"commander": "^2.9.0",
"common-tags": "^1.4.0",
"configstore": "^3.1.0",
@ -65,13 +65,13 @@
"style-loader": "^0.17.0",
"url-loader": "^0.5.8",
"util-deprecate": "^1.0.2",
"uuid": "^3.0.1",
"vue": "2.3.4",
"vue-hot-reload-api": "2.1.0",
"uuid": "^3.1.0",
"vue": "^2.4.1",
"vue-hot-reload-api": "^2.1.0",
"vue-loader": "^12.2.1",
"vue-style-loader": "3.0.1",
"vue-template-compiler": "^2.3.4",
"webpack": "^2.5.1",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.4.1",
"webpack": "^2.5.1 || ^3.0.0",
"webpack-dev-middleware": "^1.10.2",
"webpack-hot-middleware": "^2.18.0"
},

View File

@ -1,7 +1,7 @@
<template>
<div class="main">
<div class="heading">{{ message }}</div>
<pre class="code">
<div class="errordisplay_main">
<div class="errordisplay_heading">{{ message }}</div>
<pre class="errordisplay_code">
<code>
{{ stack }}
</code>
@ -26,7 +26,7 @@
</script>
<style>
.main {
.errordisplay_main {
position: fixed;
top: 0;
bottom: 0;
@ -38,7 +38,7 @@
webkit-font-smoothing: antialiased;
}
.heading {
.errordisplay_heading {
font-size: 20;
font-weight: 600;
letter-spacing: 0.2;
@ -46,7 +46,7 @@
font-family: -apple-system, ".SFNSText-Regular", "San Francisco", Roboto, "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif;
}
.code {
.errordisplay_code {
font-size: 14;
width: 100vw;
overflow: auto;

View File

@ -0,0 +1,49 @@
<template>
<div class="nopreview_wrapper">
<div class="nopreview_main">
<h1 class="nopreview_heading">No Preview</h1>
<p>Sorry, but you either have no stories or none are selected somehow.</p>
<ul>
<li>Please check the storybook config.</li>
<li>Try reloading the page.</li>
</ul>
</div>
</div>
</template>
<script>
export default {
name: 'no-preview',
}
</script>
<style>
.nopreview_wrapper {
position: fixed;
display: flex;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: 20;
align-content: center;
justify-content: center;
font-family: -apple-system, ".SFNSText-Regular", "San Francisco", Roboto, "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif;
webkit-font-smoothing: antialiased;
}
.nopreview_main {
margin: auto;
padding: 30px;
border-radius: 10px;
background: rgba(0,0,0,0.03);
}
.nopreview_heading {
font-size: 20;
font-weight: 600;
letter-spacing: 0.2;
margin: 10px 0;
text-align: center;
}
</style>

View File

@ -1,5 +1,4 @@
/* eslint no-underscore-dangle: 0 */
import Vue from 'vue';
export default class ClientApi {
constructor({ channel, storyStore }) {

View File

@ -1,14 +1,14 @@
import Vue from 'vue';
import ErrorDisplay from './ErrorDisplay.vue';
import NoPreview from './NoPreview.vue';
import { window } from 'global';
// import { stripIndents } from 'common-tags';
import { stripIndents } from 'common-tags';
// check whether we're running on node/browser
const isBrowser = typeof window !== 'undefined';
const logger = console;
// let rootEl = null;
let previousKind = '';
let previousStory = '';
let app = null;
@ -41,8 +41,16 @@ export function renderException(error) {
logger.error(error.stack);
}
// const NoPreview = () => <p>No Preview Available!</p>;
// const noPreview = <NoPreview />;
function renderRoot(options) {
if (err) {
renderErrorDisplay(null); // clear
err = null;
}
if (app) app.$destroy();
app = new Vue(options);
}
export function renderMain(data, storyStore) {
if (storyStore.size() === 0) return null;
@ -50,11 +58,6 @@ export function renderMain(data, storyStore) {
const { selectedKind, selectedStory } = data;
const story = storyStore.getStory(selectedKind, selectedStory);
if (!story) {
// ReactDOM.render(noPreview, rootEl);
logger.log('no story');
return null;
}
// Unmount the previous story only if selectedKind or selectedStory has changed.
// renderMain() gets executed after each action. Actions will cause the whole
@ -67,7 +70,6 @@ export function renderMain(data, storyStore) {
// https://github.com/storybooks/react-storybook/issues/81
previousKind = selectedKind;
previousStory = selectedStory;
// ReactDOM.unmountComponentAtNode(rootEl);
}
const context = {
@ -75,41 +77,23 @@ export function renderMain(data, storyStore) {
story: selectedStory,
};
const element = story(context);
const component = story ? story(context) : NoPreview;
// if (!element) {
// const error = {
// title: `Expecting a React element from the story: "${selectedStory}" of "${selectedKind}".`,
// description: stripIndents`
// Did you forget to return the React element from the story?
// Use "() => (<MyComp/>)" or "() => { return <MyComp/>; }" when defining the story.
// `,
// };
// return renderError(error);
// }
// if (element.type === undefined) {
// const error = {
// title: `Expecting a valid React element from the story: "${selectedStory}" of "${selectedKind}".`,
// description: stripIndents`
// Seems like you are not returning a correct React element from the story.
// Could you double check that?
// `,
// };
// return renderError(error);
// }
if (err) {
renderErrorDisplay(null); // clear
err = null;
if (!component) {
const error = {
message: `Expecting a Vue component from the story: "${selectedStory}" of "${selectedKind}".`,
stack: stripIndents`
Did you forget to return the Vue component from the story?
Use "() => ({ template: '<my-comp></my-comp>' })" or "() => ({ components: MyComp, template: '<my-comp></my-comp>' })" when defining the story.
`,
};
return renderError(error);
}
if (app) app.$destroy();
app = new Vue({
renderRoot({
el: '#root',
render(h) {
return h('div', {attrs: { id: 'root' } }, [h(element)]);
return h('div', { attrs: { id: 'root' } }, [h(component)]);
},
});
}

View File

@ -25,7 +25,7 @@
"babel-core": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.23.0",
"babel-preset-env": "^1.4.0",
"babel-preset-env": "^1.5.2",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"bootstrap": "^3.3.7",

View File

@ -97,3 +97,7 @@ With this addon you will have an additional panel at the bottom which provides y
### [Versions](https://github.com/buildit/storybook-addon-versions)
This addon lets you navigate different versions of static Storybook builds. As such you can see how a component has changed over time.
### [Apollo](https://github.com/abhiaiyer91/apollo-storybook-decorator)
Wrap your stories with the Apollo client for mocking GraphQL queries/mutations.

View File

@ -6,7 +6,7 @@
"build-storybook": "build-storybook -s public",
"eject": "react-scripts eject",
"start": "react-scripts start",
"storybook": "start-storybook -p 9009 -s public",
"storybook": "start-storybook -p 9010 -s public",
"test": "react-scripts test --env=jsdom"
},
"dependencies": {
@ -16,20 +16,20 @@
"prop-types": "^15.5.10",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"uuid": "^3.0.1"
"uuid": "^3.1.0"
},
"devDependencies": {
"@storybook/addon-actions": "3.2.0-alpha.7",
"@storybook/addon-centered": "^3.0.0",
"@storybook/addon-events": "^3.0.0",
"@storybook/addon-knobs": "3.2.0-alpha.7",
"@storybook/addon-info": "^3.0.0",
"@storybook/addon-links": "3.2.0-alpha.5",
"@storybook/addon-notes": "3.2.0-alpha.5",
"@storybook/addon-options": "3.2.0-alpha.5",
"@storybook/addon-storyshots": "3.2.0-alpha.7",
"@storybook/addons": "^3.0.0",
"@storybook/react": "3.2.0-alpha.7",
"@storybook/addon-actions": "3.2.0-alpha.8",
"@storybook/addon-centered": "3.2.0-alpha.8",
"@storybook/addon-events": "3.2.0-alpha.8",
"@storybook/addon-knobs": "3.2.0-alpha.8",
"@storybook/addon-info": "3.2.0-alpha.8",
"@storybook/addon-links": "3.2.0-alpha.8",
"@storybook/addon-notes": "3.2.0-alpha.8",
"@storybook/addon-options": "3.2.0-alpha.8",
"@storybook/addon-storyshots": "3.2.0-alpha.8",
"@storybook/addons": "3.2.0-alpha.8",
"@storybook/react": "3.2.0-alpha.8",
"react-scripts": "1.0.1"
},
"private": true

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
import React from 'react';
import PropTypes from 'prop-types';
const Container = ({ children, title, age, isAmazing }) =>
<div title={title}>
{children}
{isAmazing ? '!!!' : ''}
{age
? <div>
age = {age}
</div>
: null}
</div>;
Container.propTypes = {
children: PropTypes.node.isRequired,
isAmazing: PropTypes.bool,
age: PropTypes.number,
title: PropTypes.string,
};
Container.defaultProps = {
isAmazing: false,
age: 0,
title: 'the best container ever',
};
export default Container;

View File

@ -25,6 +25,7 @@ import { Button, Welcome } from '@storybook/react/demo';
import App from '../App';
import Logger from './Logger';
import Container from './Container';
const EVENTS = {
TEST_EVENT_1: 'test-event-1',
@ -119,18 +120,18 @@ storiesOf('Button', module)
'with some info',
'Use the [info addon](https://github.com/storybooks/storybook/tree/master/addons/info) with its painful API.',
context =>
<div>
<Container>
click the <InfoButton /> label in top right for info about "{context.story}"
</div>
</Container>
)
.add(
'with new info',
withInfo(
'Use the [info addon](https://github.com/storybooks/storybook/tree/master/addons/info) with its new painless API.'
)(context =>
<div>
<Container>
click the <InfoButton /> label in top right for info about "{context.story}"
</div>
</Container>
)
)
.add(
@ -251,7 +252,7 @@ storiesOf('Addon Knobs', module).add(
})
);
storiesOf('component.base.Link')
storiesOf('component.base.Link', module)
.addDecorator(withKnobs)
.add('first', () =>
<a>
@ -264,15 +265,15 @@ storiesOf('component.base.Link')
</a>
);
storiesOf('component.base.Span')
storiesOf('component.base.Span', module)
.add('first', () => <span>first span</span>)
.add('second', () => <span>second span</span>);
storiesOf('component.common.Div')
storiesOf('component.common.Div', module)
.add('first', () => <div>first div</div>)
.add('second', () => <div>second div</div>);
storiesOf('component.common.Table')
storiesOf('component.common.Table', module)
.add('first', () =>
<table>
<tr>
@ -288,7 +289,7 @@ storiesOf('component.common.Table')
</table>
);
storiesOf('component.Button')
storiesOf('component.Button', module)
.add('first', () => <button>first button</button>)
.add('second', () => <button>first second</button>);

View File

@ -1,30 +1,17 @@
describe('Storyshots', () => {
xit('should run snapshot tests, but we\'ve disabled this temporarily', () => {});
});
import initStoryshots, { snapshotWithOptions } from '@storybook/addon-storyshots';
import path from 'path';
// NOTE: this file should contain a snapshot test, but it is temporarily disabled.
//
// From @tmeasday: "Both Lerna/npm5 and Jest are incompatible, so we cannot run
// Jest tests right now, unless we go to great lengths (see `test-cra`'s build process)."
//
// A succinct repro here: https://github.com/tmeasday/preserve-symlinks-test
//
// Once this difference is resolved, we should uncomment the following code:
//
// import initStoryshots, { snapshotWithOptions } from '@storybook/addon-storyshots';
// import path from 'path';
//
// function createNodeMock(element) {
// if (element.type === 'div') {
// return { scrollWidth: 123 };
// }
// return null;
// }
//
// initStoryshots({
// framework: 'react',
// configPath: path.join(__dirname, '..', '.storybook'),
// test: snapshotWithOptions({
// createNodeMock,
// }),
// });
function createNodeMock(element) {
if (element.type === 'div') {
return { scrollWidth: 123 };
}
return null;
}
initStoryshots({
framework: 'react',
configPath: path.join(__dirname, '..', '.storybook'),
test: snapshotWithOptions({
createNodeMock,
}),
});

View File

@ -1,15 +0,0 @@
{
"name": "@storybook/examples",
"version": "1.0.0",
"description": "A set of examples of how to use storybook, also used for regression testing",
"main": "index.js",
"scripts": {
"test:automated-cra-getstorybook": "node scripts/automated-cra-getstorybook.js"
},
"license": "ISC",
"dependencies": {
"child-process-promise": "^2.2.1",
"getstorybook": "^1.7.0",
"rimraf": "^2.6.1"
}
}

View File

@ -25,7 +25,7 @@
2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
2D02E4C21E0B4AEC006451C7 /* libRCTAnimation-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157351DD0AC6500FF2AA8 /* libRCTAnimation-tvOS.a */; };
2D02E4C21E0B4AEC006451C7 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157351DD0AC6500FF2AA8 /* libRCTAnimation.a */; };
2D02E4C31E0B4AEC006451C7 /* libRCTImage-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E841DF850E9000B6D8A /* libRCTImage-tvOS.a */; };
2D02E4C41E0B4AEC006451C7 /* libRCTLinking-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E881DF850E9000B6D8A /* libRCTLinking-tvOS.a */; };
2D02E4C51E0B4AEC006451C7 /* libRCTNetwork-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E8C1DF850E9000B6D8A /* libRCTNetwork-tvOS.a */; };
@ -289,7 +289,7 @@
buildActionMask = 2147483647;
files = (
2D02E4C91E0B4AEC006451C7 /* libReact.a in Frameworks */,
2D02E4C21E0B4AEC006451C7 /* libRCTAnimation-tvOS.a in Frameworks */,
2D02E4C21E0B4AEC006451C7 /* libRCTAnimation.a in Frameworks */,
2D02E4C31E0B4AEC006451C7 /* libRCTImage-tvOS.a in Frameworks */,
2D02E4C41E0B4AEC006451C7 /* libRCTLinking-tvOS.a in Frameworks */,
2D02E4C51E0B4AEC006451C7 /* libRCTNetwork-tvOS.a in Frameworks */,
@ -419,7 +419,7 @@
isa = PBXGroup;
children = (
5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */,
5E9157351DD0AC6500FF2AA8 /* libRCTAnimation-tvOS.a */,
5E9157351DD0AC6500FF2AA8 /* libRCTAnimation.a */,
);
name = Products;
sourceTree = "<group>";
@ -804,10 +804,10 @@
remoteRef = 5E9157321DD0AC6500FF2AA8 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
5E9157351DD0AC6500FF2AA8 /* libRCTAnimation-tvOS.a */ = {
5E9157351DD0AC6500FF2AA8 /* libRCTAnimation.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libRCTAnimation-tvOS.a";
path = libRCTAnimation.a;
remoteRef = 5E9157341DD0AC6500FF2AA8 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
@ -1006,6 +1006,7 @@
"-lc++",
);
PRODUCT_NAME = ReactNativeVanilla;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
@ -1023,6 +1024,7 @@
"-lc++",
);
PRODUCT_NAME = ReactNativeVanilla;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;

View File

@ -14,7 +14,7 @@
"devDependencies": {
"babel-jest": "20.0.3",
"babel-preset-react-native": "1.9.2",
"jest": "20.0.4",
"jest": "^20.0.4",
"react-test-renderer": "16.0.0-alpha.6",
"@storybook/addon-actions": "file:../../addons/actions",
"@storybook/addon-links": "file:../../addons/links",

View File

@ -8,7 +8,7 @@ configure(() => {
require('./stories');
}, module);
const StorybookUI = getStorybookUI({ port: 7007, host: 'localhost' });
const StorybookUI = getStorybookUI({ port: 7007, host: 'localhost', onDeviceUI: true });
setTimeout(
() =>
@ -19,4 +19,5 @@ setTimeout(
);
AppRegistry.registerComponent('ReactNativeVanilla', () => StorybookUI);
export default StorybookUI;
export { StorybookUI as default };

View File

@ -1,25 +0,0 @@
#!/usr/bin/env node
/* eslint-disable */
/* This is an automated install of create-react-app & getstorybook */
const { exec } = require('child-process-promise');
const rimraf = require('rimraf');
const targetFolder = 'automated-cra-getstorybook';
const cleanDir = () => new Promise(resolve => rimraf(`./${targetFolder}`, resolve));
const craInstaller = () => exec('npm install create-react-app');
const craBoot = () => exec(`create-react-app ${targetFolder}`);
const storybookBoot = () => exec(`cd ${targetFolder} && getstorybook`);
const storybookBuild = () => exec(`cd ${targetFolder} && npm run build-storybook`);
Promise.all([craInstaller(), cleanDir()])
.then(craBoot)
.then(storybookBoot)
.then(storybookBuild)
.catch(error => {
console.log('rejected: ', error);
});

View File

@ -19,7 +19,6 @@
"devDependencies": {
"@storybook/addon-actions": "file:../../packs/storybook-addon-actions.tgz",
"@storybook/addon-links": "file:../../packs/storybook-addon-links.tgz",
"@storybook/addon-storyshots": "file:../../packs/storybook-addon-storyshots.tgz",
"@storybook/addons": "file:../../packs/storybook-addons.tgz",
"@storybook/channel-postmessage": "file:../../packs/storybook-channel-postmessage.tgz",
"@storybook/channels": "file:../../packs/storybook-channels.tgz",

View File

@ -1,124 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Button with some emoji 1`] = `
<button
className="css-1yjiefr"
onClick={[Function]}
>
😀 😎 👍 💯
</button>
`;
exports[`Storyshots Button with text 1`] = `
<button
className="css-1yjiefr"
onClick={[Function]}
>
Hello Button
</button>
`;
exports[`Storyshots ComponentWithRef basic 1`] = `<div />`;
exports[`Storyshots Welcome to Storybook 1`] = `
<article
className="css-1fqbdip"
>
<h1
className="css-nil"
>
Welcome to storybook
</h1>
<p>
This is a UI component dev environment for your app.
</p>
<p>
We've added some basic stories inside the
<code
className="css-mteq83"
>
src/stories
</code>
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>
<p>
See these sample
<a
className="css-ca0824"
onClick={[Function]}
role="button"
tabIndex="0"
>
stories
</a>
for a component called
<code
className="css-mteq83"
>
Button
</code>
.
</p>
<p>
Just like that, you can add your own components as stories.
<br />
You can also edit those components and see changes right away.
<br />
(Try editing the
<code
className="css-mteq83"
>
Button
</code>
stories located at
<code
className="css-mteq83"
>
src/stories/index.js
</code>
.)
</p>
<p>
Usually we create stories with smaller UI components in the app.
<br />
Have a look at the
<a
className="css-ca0824"
href="https://storybook.js.org/basics/writing-stories"
rel="noopener noreferrer"
target="_blank"
>
Writing Stories
</a>
section in our documentation.
</p>
<p
className="css-bwdon3"
>
<b>
NOTE:
</b>
<br />
Have a look at the
<code
className="css-mteq83"
>
.storybook/webpack.config.js
</code>
to add webpack loaders and plugins you are using in this project.
</p>
</article>
`;

View File

@ -1,17 +0,0 @@
import initStoryshots, { snapshotWithOptions } from '@storybook/addon-storyshots';
import path from 'path';
function createNodeMock(element) {
if (element.type === 'div') {
return { scrollWidth: 123 };
}
return null;
}
initStoryshots({
framework: 'react',
configPath: path.join(__dirname, '..', '.storybook'),
test: snapshotWithOptions({
createNodeMock,
}),
});

View File

@ -3,28 +3,28 @@
"version": "0.1.0",
"private": true,
"devDependencies": {
"@storybook/vue": "^3.0.0-alpha.0",
"@storybook/addon-actions": "^3.0.0",
"@storybook/addon-links": "^3.0.0",
"@storybook/addons": "^3.0.0",
"@storybook/addon-notes": "^3.2.0-alpha.0",
"@storybook/addon-knobs": "^3.2.0-alpha.0",
"@storybook/vue": "3.2.0-alpha.8",
"@storybook/addon-actions": "3.2.0-alpha.8",
"@storybook/addon-links": "3.2.0-alpha.8",
"@storybook/addons": "3.2.0-alpha.8",
"@storybook/addon-notes": "3.2.0-alpha.8",
"@storybook/addon-knobs": "3.2.0-alpha.8",
"vue-hot-reload-api": "^2.1.0",
"vue-style-loader": "^3.0.1",
"vue-loader": "^12.2.1",
"babel-core": "^6.0.0",
"babel-loader": "^6.0.0",
"babel-preset-env": "^1.5.1",
"babel-core": "^6.24.1",
"babel-loader": "^7.0.0",
"babel-preset-env": "^1.5.2",
"cross-env": "^3.0.0",
"css-loader": "^0.25.0",
"file-loader": "^0.9.0",
"vue-template-compiler": "^2.3.3",
"webpack": "^2.6.1",
"css-loader": "^0.28.1",
"file-loader": "^0.11.1",
"vue-template-compiler": "^2.4.1",
"webpack": "^2.5.1 || ^3.0.0",
"webpack-dev-server": "^2.4.5"
},
"dependencies": {
"vue": "^2.0.0",
"vuex": "^2.0.0"
"vue": "^2.4.1",
"vuex": "^2.3.1"
},
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",

View File

@ -14,7 +14,6 @@ module.exports = {
'<rootDir>/examples/test-cra',
],
testPathIgnorePatterns: ['/node_modules/'],
projects: ['./', './examples/react-native-vanilla'],
collectCoverage: false,
collectCoverageFrom: [
'app/**/*.{js,jsx}',

View File

@ -1,5 +1,5 @@
{
"lerna": "2.0.0-rc.5",
"lerna": "2.0.0",
"commands": {
"bootstrap": {
"ignore": [
@ -13,8 +13,7 @@
"test-cra",
"react-native-vanilla",
"vue-example",
"@storybook/components",
"@storybook/vue"
"@storybook/components"
]
}
},
@ -25,5 +24,5 @@
"examples/*"
],
"concurrency": 1,
"version": "3.2.0-alpha.7"
"version": "3.2.0-alpha.8"
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addons",
"version": "3.1.6",
"version": "3.2.0-alpha.8",
"description": "Storybook addons store",
"keywords": [
"storybook"

View File

@ -2,7 +2,7 @@ export class AddonStore {
constructor() {
this.loaders = {};
this.panels = {};
this.channel = null;
this.channel = { on() {}, emit() {} };
this.preview = null;
this.database = null;
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/cli",
"version": "3.2.0-alpha.5",
"version": "3.2.0-alpha.8",
"description": "Storybook's CLI - easiest method of adding storybook to your projects",
"keywords": [
"cli",
@ -25,7 +25,7 @@
},
"dependencies": {
"@storybook/codemod": "^3.1.6",
"chalk": "^1.1.3",
"chalk": "^2.0.1",
"child-process-promise": "^2.2.1",
"commander": "^2.9.0",
"cross-spawn": "^5.0.1",

View File

@ -79,7 +79,6 @@ export default class ReactProvider extends Provider {
this.api.setOptions({
name: 'REACT-STORYBOOK',
sortStoriesByKind: true,
hierarchySeparator: '/'
});
// set stories

View File

@ -8,13 +8,13 @@
"devDependencies": {
"babel-core": "^6.24.1",
"babel-eslint": "^7.2.2",
"babel-loader": "^6.4.1",
"babel-loader": "^7.0.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"eslint": "^3.19.0",
"eslint-plugin-react": "^6.10.3",
"webpack": "^2.4.1",
"webpack": "^2.5.1 || ^3.0.0",
"webpack-dev-server": "^2.4.2"
},
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/ui",
"version": "3.2.0-alpha.7",
"version": "3.2.0-alpha.8",
"description": "Core Storybook UI",
"license": "MIT",
"main": "dist/index.js",
@ -26,15 +26,15 @@
"lodash.sortby": "^4.7.0",
"mantra-core": "^1.7.0",
"podda": "^1.2.2",
"prop-types": "^15.5.8",
"prop-types": "^15.5.10",
"qs": "^6.4.0",
"react-icons": "^2.2.5",
"react-inspector": "^2.0.0",
"react-inspector": "^2.1.1",
"react-komposer": "^2.0.0",
"react-modal": "^1.7.6",
"react-modal": "^1.7.7",
"react-split-pane": "^0.1.63",
"redux": "^3.6.0",
"storybook-react-treebeard": "^1.1.6"
"react-treebeard": "^2.0.3",
"redux": "^3.6.0"
},
"devDependencies": {
"enzyme": "^2.8.2"

View File

@ -8,7 +8,7 @@ export default {
name: 'STORYBOOK',
url: 'https://github.com/storybooks/storybook',
sortStoriesByKind: false,
hierarchySeparator: '',
hierarchySeparator: '/',
},
},
load({ clientStore, provider }, _actions) {

View File

@ -1,4 +1,4 @@
import { Treebeard } from 'storybook-react-treebeard';
import { Treebeard } from 'react-treebeard';
import PropTypes from 'prop-types';
import React from 'react';
import deepEqual from 'deep-equal';
@ -18,9 +18,8 @@ function getSelectedNodes(selectedHierarchy) {
.reduce((nodes, namespace, index) => {
const node = {};
node.type = selectedHierarchy.length - 1 === index
? treeNodeTypes.COMPONENT
: treeNodeTypes.NAMESPACE;
node.type =
selectedHierarchy.length - 1 === index ? treeNodeTypes.COMPONENT : treeNodeTypes.NAMESPACE;
if (!nodes.length) {
node.namespaces = [namespace];

View File

@ -1,15 +1,26 @@
import { decorators } from 'storybook-react-treebeard';
import { IoFolder, IoDocumentText, IoCode } from 'react-icons/lib/io';
import { decorators } from 'react-treebeard';
import { IoChevronRight } from 'react-icons/lib/io';
import React from 'react';
import PropTypes from 'prop-types';
import treeNodeTypes from './tree_node_type';
const iconsColor = '#7d8890';
function ToggleDecorator({ style }) {
const { height, width, arrow } = style;
const iconsMap = {
[treeNodeTypes.NAMESPACE]: IoFolder,
[treeNodeTypes.COMPONENT]: IoDocumentText,
[treeNodeTypes.STORY]: IoCode,
return (
<div style={style.base}>
<div style={style.wrapper}>
<IoChevronRight height={height} width={width} style={arrow} />
</div>
</div>
);
}
ToggleDecorator.propTypes = {
style: PropTypes.shape({
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
arrow: PropTypes.object.isRequired,
}).isRequired,
};
function ContainerDecorator(props) {
@ -55,15 +66,12 @@ function createHeaderDecoratorScope(parent) {
...style.title,
};
const Icon = iconsMap[node.type];
if (!node.children || !node.children.length) {
newStyleTitle.fontSize = '13px';
}
return (
<div style={style.base} role="menuitem" tabIndex="0" onKeyDown={this.onKeyDown}>
{Icon && <Icon color={iconsColor} />}
<a style={newStyleTitle}>
{node.name}
</a>
@ -90,5 +98,6 @@ export default function(parent) {
...decorators,
Header: createHeaderDecoratorScope(parent),
Container: ContainerDecorator,
Toggle: ToggleDecorator,
};
}

View File

@ -39,13 +39,12 @@ export default {
position: 'absolute',
top: '50%',
left: '50%',
margin: '-10px 0 0 -4px',
margin: '-12px 0 0 -4px',
},
height: 10,
width: 10,
arrow: {
fill: '#9DA5AB',
strokeWidth: 0,
},
},
header: {

View File

@ -28,7 +28,7 @@ function fillHierarchy(namespaces, hierarchy, story) {
fillHierarchy(namespaces.slice(1), childHierarchy, story);
}
export function resolveStoryHierarchy(storyName, hierarchySeparator) {
export function resolveStoryHierarchy(storyName = '', hierarchySeparator) {
if (!hierarchySeparator) {
return [storyName];
}

View File

@ -1,14 +1,15 @@
{
"name": "storybook",
"version": "3.0.0",
"repository": {
"type": "git",
"url": "https://github.com/storybooks/storybook.git"
},
"scripts": {
"bootstrap": "lerna bootstrap",
"bootstrap:docs": "cd docs && npmc install",
"bootstrap:test-cra": "npm run build-packs && lerna exec --scope test-cra -- npmc install",
"bootstrap:react-native-vanilla": "lerna exec --scope react-native-vanilla -- npmc install",
"bootstrap": "lerna bootstrap --concurrency 1 --npm-client=\"yarn\" --hoist && node ./scripts/hoist-internals.js",
"bootstrap:docs": "cd docs && yarn install",
"bootstrap:react-native-vanilla": "lerna exec --scope react-native-vanilla -- yarn install",
"bootstrap:test-cra": "npm run build-packs && lerna exec --scope test-cra -- yarn install",
"build-packs": "lerna exec --scope '@storybook/*' --parallel -- ../../scripts/build-pack.sh ../../packs",
"changelog": "pr-log --sloppy",
"precommit": "lint-staged",
@ -27,16 +28,14 @@
"publish": "lerna publish",
"test": "jest --projects ./ ./examples/react-native-vanilla"
},
"engines": {
"node": "node"
},
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
"babel-eslint": "^7.2.3",
"babel-plugin-transform-md-import-to-string": "^1.0.6",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.23.0",
"babel-preset-env": "^1.5.1",
"babel-preset-env": "^1.5.2",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"chalk": "^2.0.1",
@ -52,15 +51,17 @@
"eslint-plugin-jsx-a11y": "^5.0.3",
"eslint-plugin-prettier": "^2.1.1",
"eslint-plugin-react": "^7.0.1",
"fs-extra": "^4.0.0",
"gh-pages": "^1.0.0",
"github-release-from-changelog": "^1.2.1",
"glob": "^7.1.2",
"husky": "^0.14.3",
"jest": "^20.0.4",
"jest-enzyme": "^3.2.0",
"lerna": "2.0.0",
"lint-staged": "^4.0.0",
"nodemon": "^1.11.0",
"npmc": "^5.1.0-canary.2",
"npmlog": "^4.1.2",
"prettier": "^1.5.2",
"react": "^15.5.4",
"react-dom": "^15.5.4",
@ -71,7 +72,11 @@
"remark-lint-code-eslint": "^2.0.0",
"remark-preset-lint-recommended": "^2.0.0",
"remark-toc": "^4.0.0",
"shelljs": "^0.7.7"
"shelljs": "^0.7.7",
"symlink-dir": "^1.1.0"
},
"engines": {
"node": "node"
},
"collective": {
"type": "opencollective",
@ -82,6 +87,10 @@
"npm run lint:js -- --fix",
"git add"
],
"*.json": [
"npm run lint:js -- --fix",
"git add"
],
"*.md": [
"npm run lint:md -- -o",
"git add"

112
scripts/hoist-internals.js Normal file
View File

@ -0,0 +1,112 @@
const path = require('path');
const fs = require('fs-extra');
const fse = require('fs-extra');
const shell = require('shelljs');
const glob = require('glob');
const symlink = require('symlink-dir');
const log = require('npmlog');
const targetPath = path.join(__dirname, '..', 'node_modules', '@storybook');
const prefix = 'hoist-internals';
const cwd = path.join(__dirname, '..');
log.heading = 'lerna+';
log.addLevel('success', 3001, { fg: 'green', bold: true });
log.info(prefix, 'Hoisting internal packages');
const getLernaPackages = () =>
fse.readJson(path.join(__dirname, '..', 'lerna.json')).then(json => json.packages);
const passingLog = fn => i => {
fn(i);
return i;
};
const getPackageNameOfFolder = sourcePath =>
fse
.readJson(path.join(sourcePath, 'package.json'))
.then(json => json.name.replace('@storybook/', ''));
const task = getLernaPackages()
.then(
passingLog(packages => {
log.verbose(prefix, 'working dir paths: %j', cwd);
log.verbose(prefix, 'source paths: %j', packages);
log.verbose(prefix, 'target paths: %j', targetPath);
})
)
.then(packages => `@(${packages.map(s => s.replace('/*', '')).join('|')})/*/`)
.then(
passingLog(pattern => {
log.silly(prefix, 'pattern to look for packages: %j', pattern);
})
)
.then(
pattern =>
new Promise((resolve, reject) => {
glob(pattern, { cwd }, (error, results) => (error ? reject(error) : resolve(results)));
})
)
.then(results =>
Promise.all(
results
.map(sourcePath => path.resolve(fs.realpathSync(sourcePath)))
.reduce((acc, item) => {
if (!acc.includes(item)) {
acc.push(item);
}
return acc;
}, [])
.map(
passingLog(item => {
log.silly(prefix, 'found package path', item);
})
)
.map(sourcePath =>
getPackageNameOfFolder(sourcePath)
.then(
passingLog(packageName => {
log.silly(prefix, 'found package name', packageName);
})
)
.then(packageName => path.join(targetPath, packageName))
.then(localTargetPath =>
symlink(sourcePath, localTargetPath)
.then(
passingLog(() => {
log.silly(prefix, 'symlinked ', [sourcePath, localTargetPath]);
})
)
.then(() => localTargetPath)
.catch(error => {
log.error(prefix, 'symlink', error);
throw new Error('failed symlink');
})
)
)
)
)
.then(locations =>
Promise.all(
locations
.map(location => path.join(location, 'node_modules', '@storybook'))
.map(
passingLog(removePath => {
log.verbose(prefix, 'removing ', removePath);
})
)
.map(removePath => shell.rm('-rf', removePath))
.map(
(item, index) =>
item.code === 0 ? Promise.resolve(locations[index]) : Promise.reject(item)
)
)
);
task
.then(packages => {
log.info(prefix, packages.map(dir => dir.replace(cwd, '')).join(',\n'));
log.success(prefix, 'complete');
})
.catch(error => {
log.error(prefix, 'failed', error);
shell.exit(1);
});