-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from storybookjs/migrate-from-monorepo
Extract @storybook/csf from monorepo
- Loading branch information
Showing
9 changed files
with
6,588 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module.exports = { | ||
presets: [ | ||
'@babel/preset-env', | ||
'@babel/preset-typescript', | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
version: 2 | ||
|
||
jobs: | ||
test: | ||
docker: | ||
- image: circleci/node:10 | ||
steps: | ||
- checkout | ||
- run: yarn install | ||
- run: yarn build | ||
- run: yarn lint | ||
- run: yarn test | ||
|
||
workflows: | ||
version: 2 | ||
|
||
build_and_test: | ||
jobs: | ||
- test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
node_modules | ||
*.log | ||
dist | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,33 @@ | ||
# Storybook Component Story Format (CSF) | ||
|
||
A minimal set of utility functions for dealing with Storybook [Component Story Format (CSF)](https://storybook.js.org/docs/formats/component-story-format/). | ||
|
||
## Install | ||
|
||
```sh | ||
yarn add @storybook/csf | ||
``` | ||
|
||
## API | ||
|
||
See package source for function definitions and types: | ||
|
||
- `isExportStory(key, { includeStories, excludeStories })` - Does a named export match CSF inclusion/exclusion options? | ||
|
||
- `parseKind(kind, { rootSeparator, groupSeparator })` - Parse out the component/kind name from a path, using the given separator config. | ||
|
||
- `sanitize(string)` - Remove punctuation and illegal characters from a story ID. | ||
|
||
- `toId(kind, name)` - Generate a storybook ID from a component/kind and story name. | ||
|
||
## Contributing | ||
|
||
If you have any suggestions, please open an issue or a PR. | ||
|
||
All contributions are welcome! | ||
|
||
### run tests: | ||
|
||
```sh | ||
yarn test | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
{ | ||
"name": "@storybook/csf", | ||
"version": "0.0.1", | ||
"description": "Storybook Component Story Format (CSF) utilities", | ||
"keywords": [ | ||
"storybook", | ||
"component story format", | ||
"csf", | ||
"stories" | ||
], | ||
"homepage": "https://github.com/storybookjs/csf", | ||
"bugs": { | ||
"url": "https://github.com/storybookjs/csf/issues" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/storybookjs/csf.git" | ||
}, | ||
"license": "MIT", | ||
"files": [ | ||
"dist/**/*", | ||
"README.md", | ||
"*.js", | ||
"*.d.ts" | ||
], | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"scripts": { | ||
"build": "babel src --out-dir dist --extensions \".ts\" && tsc --emitDeclarationOnly", | ||
"lint": "eslint src --ext .js,.ts", | ||
"prepublish": "yarn build", | ||
"test": "jest" | ||
}, | ||
"eslintConfig": { | ||
"extends": [ | ||
"@storybook/eslint-config-storybook" | ||
] | ||
}, | ||
"prettier": "@storybook/linter-config/prettier.config", | ||
"jest": { | ||
"testEnvironment": "node" | ||
}, | ||
"dependencies": { | ||
"lodash": "^4.17.15" | ||
}, | ||
"devDependencies": { | ||
"@babel/cli": "^7.7.4", | ||
"@babel/core": "^7.7.4", | ||
"@babel/preset-env": "^7.7.4", | ||
"@babel/preset-typescript": "^7.7.4", | ||
"@storybook/eslint-config-storybook": "^2.1.0", | ||
"@types/jest": "^24.0.23", | ||
"@types/lodash": "^4.14.149", | ||
"babel-core": "7.0.0-bridge.0", | ||
"babel-jest": "^24.9.0", | ||
"common-tags": "^1.8.0", | ||
"eslint": "^6.7.1", | ||
"jest": "^24.9.0", | ||
"prettier": "^1.19.1", | ||
"typescript": "^3.7.2" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { toId, storyNameFromExport, isExportStory } from '.'; | ||
|
||
describe('toId', () => { | ||
[ | ||
// name, kind, story, output | ||
['handles simple cases', 'kind', 'story', 'kind--story'], | ||
['handles basic substitution', 'a b$c?d😀e', '1-2:3', 'a-b-c-d😀e--1-2-3'], | ||
['handles runs of non-url chars', 'a?&*b', 'story', 'a-b--story'], | ||
['removes non-url chars from start and end', '?ab-', 'story', 'ab--story'], | ||
['downcases', 'KIND', 'STORY', 'kind--story'], | ||
['non-latin', 'Кнопки', 'нормальный', 'кнопки--нормальный'], | ||
['korean', 'kind', '바보 (babo)', 'kind--바보-babo'], | ||
['all punctuation', 'kind', 'unicorns,’–—―′¿`"<>()!.!!!{}[]%^&$*#&', 'kind--unicorns'], | ||
].forEach(([name, kind, story, output]) => { | ||
it(name, () => { | ||
expect(toId(kind, story)).toBe(output); | ||
}); | ||
}); | ||
|
||
it('does not allow kind with *no* url chars', () => { | ||
expect(() => toId('?', 'asdf')).toThrow( | ||
`Invalid kind '?', must include alphanumeric characters` | ||
); | ||
}); | ||
|
||
it('does not allow empty kind', () => { | ||
expect(() => toId('', 'asdf')).toThrow(`Invalid kind '', must include alphanumeric characters`); | ||
}); | ||
|
||
it('does not allow story with *no* url chars', () => { | ||
expect(() => toId('kind', '?')).toThrow( | ||
`Invalid name '?', must include alphanumeric characters` | ||
); | ||
}); | ||
|
||
it('does not allow empty story', () => { | ||
expect(() => toId('kind', '')).toThrow(`Invalid name '', must include alphanumeric characters`); | ||
}); | ||
}); | ||
|
||
describe('storyNameFromExport', () => { | ||
it('should format CSF exports with sensible defaults', () => { | ||
const testCases = { | ||
name: 'Name', | ||
someName: 'Some Name', | ||
someNAME: 'Some NAME', | ||
some_custom_NAME: 'Some Custom NAME', | ||
someName1234: 'Some Name 1234', | ||
someName1_2_3_4: 'Some Name 1 2 3 4', | ||
}; | ||
Object.entries(testCases).forEach(([key, val]) => expect(storyNameFromExport(key)).toBe(val)); | ||
}); | ||
}); | ||
|
||
describe('isExportStory', () => { | ||
it('should exclude __esModule', () => { | ||
expect(isExportStory('__esModule', {})).toBeFalsy(); | ||
}); | ||
|
||
it('should include all stories when there are no filters', () => { | ||
expect(isExportStory('a', {})).toBeTruthy(); | ||
}); | ||
|
||
it('should filter stories by arrays', () => { | ||
expect(isExportStory('a', { includeStories: ['a'] })).toBeTruthy(); | ||
expect(isExportStory('a', { includeStories: [] })).toBeFalsy(); | ||
expect(isExportStory('a', { includeStories: ['b'] })).toBeFalsy(); | ||
|
||
expect(isExportStory('a', { excludeStories: ['a'] })).toBeFalsy(); | ||
expect(isExportStory('a', { excludeStories: [] })).toBeTruthy(); | ||
expect(isExportStory('a', { excludeStories: ['b'] })).toBeTruthy(); | ||
|
||
expect(isExportStory('a', { includeStories: ['a'], excludeStories: ['a'] })).toBeFalsy(); | ||
expect(isExportStory('a', { includeStories: [], excludeStories: [] })).toBeFalsy(); | ||
expect(isExportStory('a', { includeStories: ['a'], excludeStories: ['b'] })).toBeTruthy(); | ||
}); | ||
|
||
it('should filter stories by regex', () => { | ||
expect(isExportStory('a', { includeStories: /a/ })).toBeTruthy(); | ||
expect(isExportStory('a', { includeStories: /.*/ })).toBeTruthy(); | ||
expect(isExportStory('a', { includeStories: /b/ })).toBeFalsy(); | ||
|
||
expect(isExportStory('a', { excludeStories: /a/ })).toBeFalsy(); | ||
expect(isExportStory('a', { excludeStories: /.*/ })).toBeFalsy(); | ||
expect(isExportStory('a', { excludeStories: /b/ })).toBeTruthy(); | ||
|
||
expect(isExportStory('a', { includeStories: /a/, excludeStories: ['a'] })).toBeFalsy(); | ||
expect(isExportStory('a', { includeStories: /.*/, excludeStories: /.*/ })).toBeFalsy(); | ||
expect(isExportStory('a', { includeStories: /a/, excludeStories: /b/ })).toBeTruthy(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import startCase from 'lodash/startCase'; | ||
|
||
/** | ||
* Remove punctuation and illegal characters from a story ID. | ||
* | ||
* See https://gist.github.com/davidjrice/9d2af51100e41c6c4b4a | ||
*/ | ||
export const sanitize = (string: string) => { | ||
return ( | ||
string | ||
.toLowerCase() | ||
// eslint-disable-next-line no-useless-escape | ||
.replace(/[ ’–—―′¿'`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '-') | ||
.replace(/-+/g, '-') | ||
.replace(/^-+/, '') | ||
.replace(/-+$/, '') | ||
); | ||
}; | ||
|
||
const sanitizeSafe = (string: string, part: string) => { | ||
const sanitized = sanitize(string); | ||
if (sanitized === '') { | ||
throw new Error(`Invalid ${part} '${string}', must include alphanumeric characters`); | ||
} | ||
return sanitized; | ||
}; | ||
|
||
/** | ||
* Generate a storybook ID from a component/kind and story name. | ||
*/ | ||
export const toId = (kind: string, name: string) => | ||
`${sanitizeSafe(kind, 'kind')}--${sanitizeSafe(name, 'name')}`; | ||
|
||
/** | ||
* Transform a CSF named export into a readable story name | ||
*/ | ||
export const storyNameFromExport = (key: string) => startCase(key); | ||
|
||
type StoryDescriptor = string[] | RegExp; | ||
export interface IncludeExcludeOptions { | ||
includeStories?: StoryDescriptor; | ||
excludeStories?: StoryDescriptor; | ||
} | ||
|
||
function matches(storyKey: string, arrayOrRegex: StoryDescriptor) { | ||
if (Array.isArray(arrayOrRegex)) { | ||
return arrayOrRegex.includes(storyKey); | ||
} | ||
return storyKey.match(arrayOrRegex); | ||
} | ||
|
||
/** | ||
* Does a named export match CSF inclusion/exclusion options? | ||
*/ | ||
export function isExportStory( | ||
key: string, | ||
{ includeStories, excludeStories }: IncludeExcludeOptions | ||
) { | ||
return ( | ||
// https://babeljs.io/docs/en/babel-plugin-transform-modules-commonjs | ||
key !== '__esModule' && | ||
(!includeStories || matches(key, includeStories)) && | ||
(!excludeStories || !matches(key, excludeStories)) | ||
); | ||
} | ||
|
||
export interface SeparatorOptions { | ||
rootSeparator: string | RegExp; | ||
groupSeparator: string | RegExp; | ||
} | ||
|
||
/** | ||
* Parse out the component/kind name from a path, using the given separator config. | ||
*/ | ||
export const parseKind = (kind: string, { rootSeparator, groupSeparator }: SeparatorOptions) => { | ||
const [root, remainder] = kind.split(rootSeparator, 2); | ||
const groups = (remainder || kind).split(groupSeparator).filter(i => !!i); | ||
|
||
// when there's no remainder, it means the root wasn't found/split | ||
return { | ||
root: remainder ? root : null, | ||
groups, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "es5", | ||
"lib": ["es2015", "es2017", "dom"], | ||
"module": "commonjs", | ||
"declaration": true, | ||
"removeComments": true, | ||
"strict": true, | ||
"noImplicitAny": true, | ||
"esModuleInterop": true, | ||
"types": ["jest"], | ||
"outDir": "dist" | ||
}, | ||
"include": ["src"], | ||
"exclude": ["dist"] | ||
} |
Oops, something went wrong.