Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a new yarn example command that will drive our new dev experience #18781

Merged
merged 28 commits into from Jul 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
217fa08
Add options library, and use it to drive a new example command
tmeasday Jul 25, 2022
5a5bb09
Automatically infer flags
tmeasday Jul 25, 2022
c3c1018
Add option validation/massaging
tmeasday Jul 25, 2022
1b447a5
Small simplification
tmeasday Jul 25, 2022
85fac87
Simply TS usage
tmeasday Jul 25, 2022
a742fe7
Some UX improvements
tmeasday Jul 25, 2022
3872a0f
Update example command to run SB commands
tmeasday Jul 25, 2022
a067ce5
Don't need this
tmeasday Jul 25, 2022
48e87e3
Cleanup
tmeasday Jul 26, 2022
6cf650e
Add a dry-run option to command
tmeasday Jul 26, 2022
38d53e5
Add script tests as a separate circle job
tmeasday Jul 27, 2022
c9df02b
Do typing shenanigans for options
tmeasday Jul 27, 2022
b99d16d
Drop scripts from code tests
tmeasday Jul 27, 2022
8e95156
Add basic logic to prompt to reuse or delete existing projects
tmeasday Jul 27, 2022
dd56d4a
Fix typing problem
tmeasday Jul 27, 2022
c928629
Fix typing problems
tmeasday Jul 27, 2022
a8ce72c
Simple updates from @yannbf's comments
tmeasday Jul 28, 2022
4f2dac0
Address issues with optional prompts
tmeasday Jul 28, 2022
7a47b38
I renamed it
tmeasday Jul 28, 2022
4f27fd6
Switch to executing `yarn storybook` in the project
tmeasday Jul 28, 2022
43d142b
Update trailing CSF version
tmeasday Jul 28, 2022
9bd059a
Got linking going
tmeasday Jul 28, 2022
ce461d6
Drop unused var
tmeasday Jul 29, 2022
457714d
Add examples to gitignore
tmeasday Jul 29, 2022
5a37d8d
Get rid of any casts, thanks @kasperpeulen!
tmeasday Jul 31, 2022
9d831be
Use `as const` on booleans rather than weak matching
tmeasday Jul 31, 2022
99d6c52
Allow running link without starting
tmeasday Jul 31, 2022
93f3e0e
Get react18 example working via preserve missing
tmeasday Jul 31, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions .circleci/config.yml
Expand Up @@ -410,6 +410,20 @@ jobs:
command: |
cd code
yarn lint
script-unit-tests:
executor: sb_node_14_browsers
steps:
- git-shallow-clone/checkout_advanced:
clone_options: '--depth 1 --verbose'
- attach_workspace:
at: .
- run:
name: Test
command: |
cd scripts
yarn test --coverage --runInBand --ci
- store_test_results:
path: scripts/junit.xml
unit-tests:
executor: sb_node_14_browsers
steps:
Expand Down Expand Up @@ -462,6 +476,9 @@ workflows:
- unit-tests:
requires:
- build
- script-unit-tests:
requires:
- build
- coverage:
requires:
- unit-tests
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -16,4 +16,5 @@ junit.xml
!/**/.yarn/sdks
!/**/.yarn/versions
/**/.pnp.*
/yarn.lock
/yarn.lock
./examples/
2 changes: 1 addition & 1 deletion code/lib/blocks/package.json
Expand Up @@ -46,7 +46,7 @@
"@storybook/client-logger": "7.0.0-alpha.17",
"@storybook/components": "7.0.0-alpha.17",
"@storybook/core-events": "7.0.0-alpha.17",
"@storybook/csf": "0.0.2--canary.7c6c115.0",
"@storybook/csf": "0.0.2--canary.0899bb7.0",
"@storybook/docs-tools": "7.0.0-alpha.17",
"@storybook/preview-web": "7.0.0-alpha.17",
"@storybook/store": "7.0.0-alpha.17",
Expand Down
5 changes: 3 additions & 2 deletions code/lib/cli/src/generate.ts
Expand Up @@ -150,8 +150,9 @@ program
.command('link <repo-url-or-directory>')
.description('Pull down a repro from a URL (or a local directory), link it, and run storybook')
.option('--local', 'Link a local directory already in your file system')
.action((target, { local }) =>
link({ target, local }).catch((e) => {
.option('--no-start', 'Start the storybook', true)
.action((target, { local, start }) =>
link({ target, local, start }).catch((e) => {
logger.error(e);
process.exit(1);
})
Expand Down
9 changes: 6 additions & 3 deletions code/lib/cli/src/link.ts
Expand Up @@ -7,9 +7,10 @@ import { exec } from './repro-generators/scripts';
interface LinkOptions {
target: string;
local?: boolean;
start: boolean;
}

export const link = async ({ target, local }: LinkOptions) => {
export const link = async ({ target, local, start }: LinkOptions) => {
const storybookDir = process.cwd();
try {
const packageJson = JSON.parse(fse.readFileSync('package.json', 'utf8'));
Expand Down Expand Up @@ -58,6 +59,8 @@ export const link = async ({ target, local }: LinkOptions) => {
);
await exec(`yarn add -D webpack-hot-middleware`, { cwd: reproDir });

logger.info(`Running ${reproName} storybook`);
await exec(`yarn run storybook`, { cwd: reproDir });
if (start) {
logger.info(`Running ${reproName} storybook`);
await exec(`yarn run storybook`, { cwd: reproDir });
}
};
14 changes: 11 additions & 3 deletions code/lib/cli/src/repro-generators/scripts.ts
Expand Up @@ -51,11 +51,19 @@ export interface Options extends Parameters {
export const exec = async (
command: string,
options: ExecOptions = {},
{ startMessage, errorMessage }: { startMessage?: string; errorMessage?: string } = {}
{
startMessage,
errorMessage,
dryRun,
}: { startMessage?: string; errorMessage?: string; dryRun?: boolean } = {}
) => {
if (startMessage) {
logger.info(startMessage);
if (startMessage) logger.info(startMessage);

if (dryRun) {
logger.info(`\n> ${command}\n`);
return undefined;
}

logger.debug(command);
return new Promise((resolve, reject) => {
const defaultOptions: ExecOptions = {
Expand Down
1 change: 1 addition & 0 deletions code/package.json
Expand Up @@ -62,6 +62,7 @@
"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",
Expand Down
11 changes: 1 addition & 10 deletions code/yarn.lock
Expand Up @@ -7522,7 +7522,7 @@ __metadata:
"@storybook/client-logger": 7.0.0-alpha.17
"@storybook/components": 7.0.0-alpha.17
"@storybook/core-events": 7.0.0-alpha.17
"@storybook/csf": 0.0.2--canary.7c6c115.0
"@storybook/csf": 0.0.2--canary.0899bb7.0
"@storybook/docs-tools": 7.0.0-alpha.17
"@storybook/preview-web": 7.0.0-alpha.17
"@storybook/store": 7.0.0-alpha.17
Expand Down Expand Up @@ -8076,15 +8076,6 @@ __metadata:
languageName: node
linkType: hard

"@storybook/csf@npm:0.0.2--canary.7c6c115.0":
version: 0.0.2--canary.7c6c115.0
resolution: "@storybook/csf@npm:0.0.2--canary.7c6c115.0"
dependencies:
lodash: ^4.17.15
checksum: 85a179664d18eeca8462c1b6ff36f9b68b856c9f9c5143aa6f19b17e4cc97bc08ed69921a5287a61d8c90f61366ff6a5ab89930d158402e7c04d07a3ffaad8bb
languageName: node
linkType: hard

"@storybook/csf@npm:^0.0.1":
version: 0.0.1
resolution: "@storybook/csf@npm:0.0.1"
Expand Down
1 change: 0 additions & 1 deletion examples/react
Submodule react deleted from b7ef5b
235 changes: 235 additions & 0 deletions scripts/example.ts
@@ -0,0 +1,235 @@
import path from 'path';
import { remove, pathExists, readJSON, writeJSON } from 'fs-extra';
import prompts from 'prompts';

import { getOptionsOrPrompt } from './utils/options';
import { executeCLIStep } from './utils/cli-step';
import { exec } from '../code/lib/cli/src/repro-generators/scripts';
import type { Parameters } from '../code/lib/cli/src/repro-generators/configs';
import { getInterpretedFile } from '../code/lib/core-common';
import { readConfig, writeConfig } from '../code/lib/csf-tools';
import { babelParse } from '../code/lib/csf-tools/src/babelParse';

const frameworks = ['react', 'angular'];
const addons = ['a11y', 'storysource'];
const examplesDir = path.resolve(__dirname, '../examples');
const codeDir = path.resolve(__dirname, '../code');

async function getOptions() {
return getOptionsOrPrompt('yarn example', {
framework: {
description: 'Which framework would you like to use?',
values: frameworks,
required: true as const,
},
addon: {
description: 'Which extra addons (beyond the CLI defaults) would you like installed?',
values: addons,
multiple: true as const,
},
includeStories: {
description: "Include Storybook's own stories?",
promptType: (_, { framework }) => framework === 'react',
},
create: {
description: 'Create the example from scratch (rather than degitting it)?',
},
tmeasday marked this conversation as resolved.
Show resolved Hide resolved
forceDelete: {
description: 'Always delete an existing example, even if it has the same configuration?',
tmeasday marked this conversation as resolved.
Show resolved Hide resolved
promptType: false,
},
forceReuse: {
description: 'Always reuse an existing example, even if it has a different configuration?',
promptType: false,
},
link: {
description: 'Link the storybook to the local code?',
inverse: true,
},
start: {
description: 'Start the example Storybook?',
inverse: true,
},
build: {
description: 'Build the example Storybook?',
},
watch: {
description: 'Start building used packages in watch mode as well as the example Storybook?',
},
dryRun: {
description: "Don't execute commands, just list them (dry run)?",
},
});
}

const steps = {
repro: {
command: 'repro',
description: 'Bootstrapping example',
icon: '👷',
hasArgument: true,
options: {
template: { values: frameworks },
e2e: {},
},
},
add: {
command: 'add',
description: 'Adding addon',
icon: '+',
hasArgument: true,
options: {},
},
link: {
command: 'link',
description: 'Linking packages',
icon: '🔗',
hasArgument: true,
options: { local: {}, start: { inverse: true } },
},
build: {
command: 'build',
description: 'Building example',
icon: '🔨',
options: {},
},
dev: {
command: 'dev',
description: 'Starting example',
icon: '🖥 ',
options: {},
},
};

const logger = console;

export const overrideMainConfig = async ({
cwd,
mainOverrides,
}: {
cwd: string;
mainOverrides: Parameters['mainOverrides'];
}) => {
logger.info(`📝 Overwriting main.js with the following configuration:`);
const configDir = path.join(cwd, '.storybook');
const mainConfigPath = getInterpretedFile(path.resolve(configDir, 'main'));
logger.debug(mainOverrides);
const mainConfig = await readConfig(mainConfigPath);

Object.keys(mainOverrides).forEach((field) => {
// NOTE: using setFieldNode and passing the output of babelParse()
mainConfig.setFieldNode([field], mainOverrides[field]);
});

await writeConfig(mainConfig);
};

const addPackageScripts = async ({
cwd,
scripts,
}: {
cwd: string;
scripts: Record<string, string>;
}) => {
logger.info(`🔢 Adding package resolutions:`);
const packageJsonPath = path.join(cwd, 'package.json');
const packageJson = await readJSON(packageJsonPath);
packageJson.scripts = {
...packageJson.scripts,
...scripts,
};
await writeJSON(packageJsonPath, packageJson, { spaces: 2 });
};

async function main() {
const optionValues = await getOptions();

const { framework, forceDelete, forceReuse, link, dryRun } = optionValues;
const cwd = path.join(examplesDir, framework as string);

const exists = await pathExists(cwd);
let shouldDelete = exists && !forceReuse;
if (exists && !forceDelete && !forceReuse) {
const relativePath = path.relative(process.cwd(), cwd);
({ shouldDelete } = await prompts({
type: 'toggle',
message: `${relativePath} already exists, should delete it and create a new one?`,
name: 'shouldDelete',
initial: false,
active: 'yes',
inactive: 'no',
}));
}

if (exists && shouldDelete && !dryRun) await remove(cwd);

if (!exists || shouldDelete) {
await executeCLIStep(steps.repro, {
argument: cwd,
optionValues: { template: framework },
cwd: examplesDir,
dryRun,
});

// TODO -- sb add <addon> doesn't actually work properly:
// - installs in `deps` not `devDeps`
// - does a `workspace:^` install (what does that mean?)
// - doesn't add to `main.js`

// eslint-disable-next-line no-restricted-syntax
for (const addon of optionValues.addon as string[]) {
const addonName = `@storybook/addon-${addon}`;
// eslint-disable-next-line no-await-in-loop
await executeCLIStep(steps.add, { argument: addonName, cwd, dryRun });
}

// TODO copy stories

if (link) {
await executeCLIStep(steps.link, {
argument: cwd,
cwd: codeDir,
dryRun,
optionValues: { local: true, start: false },
});

// TODO -- work out exactly where this should happen
const code = '(c) => ({ ...c, resolve: { ...c.resolve, symlinks: false } })';
const mainOverrides = {
// @ts-ignore (not sure why TS complains here, it does exist)
webpackFinal: babelParse(code).program.body[0].expression,
};
await overrideMainConfig({ cwd, mainOverrides } as any);

await addPackageScripts({
cwd,
scripts: {
storybook:
'NODE_OPTIONS="--preserve-symlinks --preserve-symlinks-main" storybook dev -p 6006',
'build-storybook':
'NODE_OPTIONS="--preserve-symlinks --preserve-symlinks-main" storybook build',
},
});
}
}

const { start } = optionValues;
if (start) {
await exec(
'yarn storybook',
{ cwd },
{
dryRun,
startMessage: `⬆️ Starting Storybook`,
errorMessage: `🚨 Starting Storybook failed`,
}
);
} else {
await executeCLIStep(steps.build, { cwd, dryRun });
// TODO serve
}

// TODO start dev
}

main().catch((err) => console.error(err));
1 change: 1 addition & 0 deletions scripts/jest.config.js
@@ -0,0 +1 @@
module.exports = {};