Add basic contributing instructions and improve prompts

This commit is contained in:
Tom Coleman 2022-10-05 23:31:33 +11:00
parent 843811a33d
commit 431162b321
15 changed files with 94 additions and 50 deletions

View File

@ -2,17 +2,34 @@
- Ensure you have node version 14 installed (suggestion: v14.18.1).
- Ensure if you are using Windows to use the Windows Subsystem for Linux (WSL).
- Run `./bootstrap.sh` to install the dependencies, and get the repo ready to be developed on.
- Run `yarn start` inside of the `code` directory to start the development server.
- Run `yarn install` to initialize the repo (it shouldn't install anything).
- Run `yarn start` directory to run a basic test Storybook "sandbox".
# Generating reproductions
# Running against different sandbox templates
The monorepo has a script that generates Storybook reproductions based on configurations set in the `code/lib/cli/src/repro-templates.ts` file. This makes it possible to quickly bootstrap examples with and without Storybook, for given configurations (e.g. CRA, Angular, Vue, etc.)
The `yarn start` script will generate a Create React App TypeScript sandbox with a set of test stories inside it, as well as taking all steps required to get it running (building the various packages we need etc).
You can also pick a specific template to use as your sandbox by running `yarn task`, which will prompt you to make further choices about which template you want and which task you want to run.
# Making code changes
If you want to make code changes to Storybook packages while running a sandbox, you'll need to do the following:
1. In a second terminal run `yarn build --watch <package-1> <package-2>` in the `code/` directory. The package names is the bit after the `@storybook/` in the published package. For instance:
```bash
cd code
yarn build --watch react core-server api addon-docs
```
2. If you are running the sandbox in "linked" mode (the default), you should see the changes reflected on a refresh (you may need to restart it if changing server packages)
3. If you are running the sandbox in "unlinked" mode you'll need to re-run the sandbox from the `publish` step to see the changes:
```
yarn task --task dev --template <your template> --start-from=publish
```
To do so:
- Check the `code/lib/cli/src/repro-templates.ts` if you want to see what will be generated
- Run `./generate-repros.sh`
- Check the result in the `repros` directory
# Contributing to Storybook

View File

@ -58,6 +58,10 @@ export type TemplateDetails = {
type MaybePromise<T> = T | Promise<T>;
export type Task = {
/**
* A description of the task for a prompt
*/
description: string;
/**
* Does this task represent a service for another task?
*
@ -104,29 +108,45 @@ export const tasks = {
chromatic,
'e2e-tests': e2eTests,
};
type TaskKey = keyof typeof tasks;
export const sandboxOptions = createOptions({
template: {
function isSandboxTask(taskKey: TaskKey) {
return !['install', 'compile', 'publish', 'run-registry'].includes(taskKey);
}
export const options = createOptions({
task: {
type: 'string',
description: 'What template are you running against?',
values: Object.keys(TEMPLATES) as TemplateKey[],
description: 'Which task would you like to run?',
values: Object.keys(tasks) as TaskKey[],
valueDescriptions: Object.values(tasks).map((t) => `${t.description} (${getTaskKey(t)})`),
required: true,
},
// TODO -- feature flags
sandboxDir: {
startFrom: {
type: 'string',
description: 'What is the name of the directory the sandbox runs in?',
description: 'Which task should we reset back to?',
values: [...(Object.keys(tasks) as TaskKey[]), 'never', 'auto'] as const,
// This is prompted later based on information about what's ready
promptType: false,
},
template: {
type: 'string',
description: 'What template would you like to make a sandbox for?',
values: Object.keys(TEMPLATES) as TemplateKey[],
promptType: (_, { task }) => isSandboxTask(task),
},
// // TODO -- feature flags
// sandboxDir: {
// type: 'string',
// description: 'What is the name of the directory the sandbox runs in?',
// promptType: false,
// },
addon: {
type: 'string[]',
description: 'Which extra addons (beyond the CLI defaults) would you like installed?',
values: extraAddons,
promptType: (_, { task }) => isSandboxTask(task),
},
});
export const runOptions = createOptions({
link: {
type: 'boolean',
description: 'Link the storybook to the local code?',
@ -135,6 +155,7 @@ export const runOptions = createOptions({
fromLocalRepro: {
type: 'boolean',
description: 'Create the template from a local repro (rather than degitting it)?',
promptType: (_, { task }) => isSandboxTask(task),
},
dryRun: {
type: 'boolean',
@ -146,27 +167,14 @@ export const runOptions = createOptions({
description: 'Print all the logs to the console',
promptType: false,
},
});
export const taskOptions = createOptions({
task: {
type: 'string',
description: 'What task are you performing (corresponds to CI job)?',
values: Object.keys(tasks) as TaskKey[],
required: true,
},
startFrom: {
type: 'string',
description: 'Which task should we reset back to?',
values: [...(Object.keys(tasks) as TaskKey[]), 'never', 'auto'] as const,
},
junit: {
type: 'boolean',
description: 'Store results in junit format?',
promptType: false,
},
});
type PassedOptionValues = OptionValues<typeof sandboxOptions & typeof runOptions>;
type PassedOptionValues = Omit<OptionValues<typeof options>, 'task' | 'startFrom' | 'junit'>;
const logger = console;
@ -293,12 +301,7 @@ async function runTask(task: Task, details: TemplateDetails, optionValues: Passe
}
async function run() {
const allOptions = {
...sandboxOptions,
...runOptions,
...taskOptions,
};
const allOptionValues = await getOptionsOrPrompt('yarn task', allOptions);
const allOptionValues = await getOptionsOrPrompt('yarn task', options);
const { task: taskKey, startFrom, junit, ...optionValues } = allOptionValues;
@ -363,20 +366,24 @@ async function run() {
setUnready(tasks[startFrom]);
} else if (firstUnready === sortedTasks[0]) {
// We need to do everything, no need to change anything
} else if (sortedTasks.length === 1) {
setUnready(sortedTasks[0]);
} else {
// We don't know what to do! Let's ask
const { startFromTask } = await prompt({
type: 'select',
message: `We need to run all tasks after ${getTaskKey(
firstUnready
)}, would you like to go further back?`,
message: firstUnready
? `We need to run all tasks after ${getTaskKey(
firstUnready
)}, would you like to go further back?`
: `Which task would you like to start from?`,
name: 'startFromTask',
choices: sortedTasks
.slice(0, sortedTasks.indexOf(firstUnready) + 1)
.slice(0, firstUnready && sortedTasks.indexOf(firstUnready) + 1)
.filter((t) => !t.service)
.reverse()
.map((t) => ({
title: getTaskKey(t),
title: `${t.description} (${getTaskKey(t)})`,
value: t,
})),
});
@ -412,7 +419,7 @@ async function run() {
dedent`
To reproduce this error locally, run:
${getCommand('yarn task', allOptions, {
${getCommand('yarn task', options, {
...allOptionValues,
link: true,
startFrom: 'auto',
@ -420,7 +427,7 @@ async function run() {
Note this uses locally linking which in rare cases behaves differently to CI. For a closer match, run:
${getCommand('yarn task', allOptions, {
${getCommand('yarn task', options, {
...allOptionValues,
startFrom: 'auto',
})}`,

View File

@ -3,6 +3,7 @@ import type { Task } from '../task';
import { exec } from '../utils/exec';
export const build: Task = {
description: 'Build the static version of the sandbox',
before: ['sandbox'],
async ready({ builtSandboxDir }) {
return pathExists(builtSandboxDir);

View File

@ -2,6 +2,7 @@ import type { Task } from '../task';
import { exec } from '../utils/exec';
export const chromatic: Task = {
description: 'Run Chromatic against the sandbox',
before: ['build'],
junit: true,
async ready() {

View File

@ -12,6 +12,7 @@ const noLinkCommand = `nx run-many --target="prep" --all --parallel=8 ${
} -- --reset --optimized`;
export const compile: Task = {
description: 'Compile the source code of the monorepo',
before: ['install'],
async ready({ codeDir }, { link }) {
try {

View File

@ -7,6 +7,7 @@ import { exec } from '../utils/exec';
export const START_PORT = 6006;
export const dev: Task = {
description: 'Run the sandbox in development mode',
service: true,
before: ['sandbox'],
async ready() {

View File

@ -3,6 +3,7 @@ import { exec } from '../utils/exec';
import { PORT } from './serve';
export const e2eTests: Task = {
description: 'Run the end-to-end test suite against the sandbox',
before: ['serve'],
junit: true,
async ready() {

View File

@ -4,6 +4,7 @@ import type { Task } from '../task';
import { exec } from '../utils/exec';
export const install: Task = {
description: 'Install the dependencies of the monorepo',
async ready({ codeDir }) {
return pathExists(join(codeDir, 'node_modules'));
},

View File

@ -7,6 +7,7 @@ import type { Task } from '../task';
const verdaccioCacheDir = resolve(__dirname, '../../.verdaccio-cache');
export const publish: Task = {
description: 'Publish the packages of the monorepo to an internal npm server',
before: ['compile'],
async ready() {
return pathExists(verdaccioCacheDir);

View File

@ -25,6 +25,7 @@ export async function runRegistry({ dryRun, debug }: { dryRun?: boolean; debug?:
const REGISTRY_PORT = 6001;
export const runRegistryTask: Task = {
description: 'Run the internal npm server',
service: true,
before: ['publish'],
async ready() {

View File

@ -5,6 +5,7 @@ import { Task } from '../task';
const logger = console;
export const sandbox: Task = {
description: 'Create the sandbox from a template',
before: ({ link }) => (link ? ['compile'] : ['compile', 'run-registry']),
async ready({ sandboxDir }) {
return pathExists(sandboxDir);

View File

@ -6,6 +6,7 @@ import { exec } from '../utils/exec';
export const PORT = 8001;
export const serve: Task = {
description: 'Serve the build storybook for a sandbox',
service: true,
before: ['build'],
async ready() {

View File

@ -2,6 +2,7 @@ import type { Task } from '../task';
import { exec } from '../utils/exec';
export const smokeTest: Task = {
description: 'Run the smoke tests of a sandbox',
before: ['sandbox'],
async ready() {
return false;

View File

@ -3,6 +3,7 @@ import { exec } from '../utils/exec';
import { PORT } from './serve';
export const testRunner: Task = {
description: 'Run the test runner against a sandbox',
junit: true,
before: ['run-registry', 'build'],
async ready() {

View File

@ -40,6 +40,10 @@ export type StringOption = BaseOption & {
* What values are allowed for this option?
*/
values?: readonly string[];
/**
* How to describe the values when selecting them
*/
valueDescriptions?: readonly string[];
/**
* Is a value required for this option?
*/
@ -52,6 +56,10 @@ export type StringArrayOption = BaseOption & {
* What values are allowed for this option?
*/
values?: readonly string[];
/**
* How to describe the values when selecting them
*/
valueDescriptions?: readonly string[];
};
export type Option = BooleanOption | StringOption | StringArrayOption;
@ -203,7 +211,7 @@ export async function promptOptions<TOptions extends OptionSpecifier>(
const chosenType = passedType(...args);
return chosenType === true ? defaultType : chosenType;
};
} else if (passedType) {
} else if (typeof passedType !== 'undefined') {
type = passedType;
}
@ -215,8 +223,8 @@ export async function promptOptions<TOptions extends OptionSpecifier>(
name: key,
// warn: ' ',
// pageSize: Object.keys(tasks).length + Object.keys(groups).length,
choices: option.values?.map((value) => ({
title: value,
choices: option.values?.map((value, index) => ({
title: option.valueDescriptions?.[index] || value,
value,
selected:
currentValue === value ||