diff --git a/.gitignore b/.gitignore index 4ae2a283700c..c8a737015be9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ dist .cache junit.xml /repros +/sandbox # Yarn stuff /**/.yarn/* @@ -17,4 +18,3 @@ junit.xml !/**/.yarn/versions /**/.pnp.* /yarn.lock -./examples/ \ No newline at end of file diff --git a/code/lib/cli/src/repro-generators/scripts.ts b/code/lib/cli/src/repro-generators/scripts.ts index 35ac70ad6454..1558be57817c 100644 --- a/code/lib/cli/src/repro-generators/scripts.ts +++ b/code/lib/cli/src/repro-generators/scripts.ts @@ -77,7 +77,6 @@ export const exec = async ( }); child.stderr.pipe(process.stderr); - child.stdout.pipe(process.stdout); child.on('exit', (code) => { if (code === 0) { diff --git a/code/package.json b/code/package.json index a4d29b408b16..c837be39f79d 100644 --- a/code/package.json +++ b/code/package.json @@ -62,7 +62,6 @@ "clean:dist": "del **/dist", "coverage": "codecov", "danger": "danger", - "example": "ts-node ../scripts/example.ts", "generate-repros": "zx ../scripts/repros-generator/index.mjs", "github-release": "github-release-from-changelog", "linear-export": "ts-node --project=../scripts/tsconfig.json ../scripts/linear-export.ts", @@ -80,6 +79,7 @@ "publish:latest": "lerna publish --exact --concurrency 1 --force-publish", "publish:next": "npm run publish:latest -- --npm-tag=next", "run-chromatics": "node -r esm ../scripts/run-chromatics.js", + "sandbox": "ts-node ../scripts/sandbox.ts", "serve-storybooks": "http-server ./built-storybooks -p 8001", "smoketest-storybooks": "cross-env STORYBOOK_DISPLAY_WARNING=true DISPLAY_WARNING=true node -r esm ../scripts/smoketest-storybooks.js", "start": "yarn workspace official-storybook storybook --no-manager-cache", diff --git a/code/renderers/react/template/components/Button.jsx b/code/renderers/react/template/components/Button.jsx index 7818fd856fa4..b3d1b59a88c7 100644 --- a/code/renderers/react/template/components/Button.jsx +++ b/code/renderers/react/template/components/Button.jsx @@ -10,4 +10,4 @@ export const Button = ({ onClick, children }) => ( Button.propTypes = { onClick: PropTypes.func.isRequired, children: PropTypes.node.isRequired, -} \ No newline at end of file +}; diff --git a/scripts/example.ts b/scripts/sandbox.ts similarity index 76% rename from scripts/example.ts rename to scripts/sandbox.ts index 683e58f69e4a..93afbea593c9 100644 --- a/scripts/example.ts +++ b/scripts/sandbox.ts @@ -9,8 +9,10 @@ import { exec } from '../code/lib/cli/src/repro-generators/scripts'; import { getInterpretedFile } from '../code/lib/core-common'; import { ConfigFile, readConfig, writeConfig } from '../code/lib/csf-tools'; import { babelParse } from '../code/lib/csf-tools/src/babelParse'; +import TEMPLATES from '../code/lib/cli/src/repro-templates'; -const frameworks = ['react', 'angular']; +type Template = keyof typeof TEMPLATES; +const templates: Template[] = Object.keys(TEMPLATES) as any; const addons = ['a11y', 'storysource']; const defaultAddons = [ 'actions', @@ -25,17 +27,14 @@ const defaultAddons = [ 'toolbars', 'viewport', ]; -const examplesDir = path.resolve(__dirname, '../examples'); +const sandboxDir = path.resolve(__dirname, '../sandbox'); const codeDir = path.resolve(__dirname, '../code'); -// TODO -- how to encode this information -const renderersMap = { react: 'react', angular: 'angular' }; - async function getOptions() { - return getOptionsOrPrompt('yarn example', { - framework: { - description: 'Which framework would you like to use?', - values: frameworks, + return getOptionsOrPrompt('yarn sandbox', { + template: { + description: 'Which template would you like to use?', + values: templates, required: true as const, }, addon: { @@ -48,14 +47,14 @@ async function getOptions() { promptType: (_, { framework }) => framework === 'react', }, create: { - description: 'Create the example from scratch (rather than degitting it)?', + description: 'Create the template from scratch (rather than degitting it)?', }, forceDelete: { - description: 'Always delete an existing example, even if it has the same configuration?', + description: 'Always delete an existing sandbox, even if it has the same configuration?', promptType: false, }, forceReuse: { - description: 'Always reuse an existing example, even if it has a different configuration?', + description: 'Always reuse an existing sandbox, even if it has a different configuration?', promptType: false, }, link: { @@ -63,14 +62,14 @@ async function getOptions() { inverse: true, }, start: { - description: 'Start the example Storybook?', + description: 'Start the Storybook?', inverse: true, }, build: { - description: 'Build the example Storybook?', + description: 'Build the Storybook?', }, watch: { - description: 'Start building used packages in watch mode as well as the example Storybook?', + description: 'Start building used packages in watch mode as well as the Storybook?', }, dryRun: { description: "Don't execute commands, just list them (dry run)?", @@ -80,13 +79,15 @@ async function getOptions() { const steps = { repro: { - command: 'repro', - description: 'Bootstrapping example', + command: 'repro-next', + description: 'Bootstrapping Template', icon: '👷', hasArgument: true, options: { - template: { values: frameworks }, - e2e: {}, + // TODO allow string valued options without fixed values + output: { values: [] as string[] }, + // TODO allow default values for strings + branch: { values: ['next'] }, }, }, add: { @@ -105,13 +106,13 @@ const steps = { }, build: { command: 'build', - description: 'Building example', + description: 'Building Storybook', icon: '🔨', options: {}, }, dev: { command: 'dev', - description: 'Starting example', + description: 'Starting Storybook', icon: '🖥 ', options: {}, }, @@ -119,13 +120,20 @@ const steps = { const logger = console; -const addPackageScripts = async ({ +async function findFirstPath(paths: string[], { cwd }: { cwd: string }) { + for (const filePath of paths) { + if (await pathExists(path.join(cwd, filePath))) return filePath; + } + return null; +} + +async function addPackageScripts({ cwd, scripts, }: { cwd: string; scripts: Record; -}) => { +}) { logger.info(`🔢 Adding package resolutions:`); const packageJsonPath = path.join(cwd, 'package.json'); const packageJson = await readJSON(packageJsonPath); @@ -134,7 +142,7 @@ const addPackageScripts = async ({ ...scripts, }; await writeJSON(packageJsonPath, packageJson, { spaces: 2 }); -}; +} async function readMainConfig({ cwd }: { cwd: string }) { const configDir = path.join(cwd, '.storybook'); @@ -164,10 +172,7 @@ const webpackFinalCode = ` })`; // paths are of the form 'node_modules/@storybook/react' -async function addStories( - paths: string[], - { mainConfig, cwd }: { mainConfig: ConfigFile; cwd: string } -) { +async function addStories(paths: string[], { mainConfig }: { mainConfig: ConfigFile }) { const stories = mainConfig.getFieldValue(['stories']) as string[]; const extraStoryDirsAndExistence = await Promise.all( paths @@ -190,8 +195,8 @@ async function addStories( async function main() { const optionValues = await getOptions(); - const { framework, forceDelete, forceReuse, link, dryRun } = optionValues; - const cwd = path.join(examplesDir, framework); + const { template, forceDelete, forceReuse, link, dryRun } = optionValues; + const cwd = path.join(sandboxDir, template.replace('/', '-')); const exists = await pathExists(cwd); let shouldDelete = exists && !forceReuse; @@ -211,20 +216,19 @@ async function main() { if (!exists || shouldDelete) { await executeCLIStep(steps.repro, { - argument: cwd, - optionValues: { template: framework }, - cwd: examplesDir, + argument: template, + optionValues: { output: cwd, branch: 'next' }, + cwd: sandboxDir, dryRun, }); const mainConfig = await readMainConfig({ cwd }); - // TODO -- can we get the options type to return something more specific - const renderer = renderersMap[framework as 'react' | 'angular']; - const storiesPath = 'stories'; // This may differ in different projects + const templateConfig = TEMPLATES[template as Template]; + const storiesPath = await findFirstPath([path.join('src', 'stories'), 'stories'], { cwd }); // Link in the template/components/index.js from the renderer - const rendererPath = path.join('node_modules', '@storybook', renderer); + const rendererPath = path.join('node_modules', templateConfig.expected.renderer); await ensureSymlink( path.join(codeDir, rendererPath, 'template', 'components'), path.resolve(cwd, storiesPath, 'components') @@ -250,11 +254,15 @@ async function main() { for (const addon of [...defaultAddons, ...optionValues.addon]) { storiesToAdd.push(path.join('node_modules', '@storybook', `addon-${addon}`)); } - await addStories(storiesToAdd, { mainConfig, cwd }); + await addStories(storiesToAdd, { mainConfig }); await writeConfig(mainConfig); if (link) { + await exec('yarn set version berry', { cwd }, { dryRun }); + await exec('yarn config set enableGlobalCache true', { cwd }, { dryRun }); + await exec('yarn config set nodeLinker node-modules', { cwd }, { dryRun }); + await executeCLIStep(steps.link, { argument: cwd, cwd: codeDir,