Skip to content

Commit

Permalink
Update example command to run SB commands
Browse files Browse the repository at this point in the history
  • Loading branch information
tmeasday committed Jul 25, 2022
1 parent a563af4 commit 805111c
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 36 deletions.
135 changes: 109 additions & 26 deletions scripts/example.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,119 @@
import path from 'path';
import { existsSync } from 'fs';

import { getOptionsOrPrompt } from './utils/options';
import type { CLIStep } from './utils/cli-step';
import { executeCLIStep } from './utils/cli-step';

const frameworks = ['react'];
const addons = ['a11y', 'storysource'];

getOptionsOrPrompt('yarn example', {
framework: {
name: 'Which framework would you like to use?',
values: frameworks,
required: true,
},
addon: {
name: 'Which extra addons (beyond the CLI defaults) would you like installed?',
values: addons,
multiple: true,
},
includeStories: {
name: "Include Storybook's own stories (only applies if a react-based framework is used)?",
},
create: {
name: 'Create the example from scratch (rather than degitting it)?',
},
verdaccio: {
name: 'Use verdaccio rather than yarn linking stories?',
async function getOptions() {
return getOptionsOrPrompt('yarn example', {
framework: {
description: 'Which framework would you like to use?',
values: frameworks,
required: true,
},
addon: {
description: 'Which extra addons (beyond the CLI defaults) would you like installed?',
values: addons,
multiple: true,
},
includeStories: {
description:
"Include Storybook's own stories (only applies if a react-based framework is used)?",
},
create: {
description: 'Create the example from scratch (rather than degitting it)?',
},
verdaccio: {
description: 'Use verdaccio rather than yarn linking stories?',
},
start: {
description: 'Start the example app?',
inverse: true,
},
build: {
description: 'Build the example app?',
},
watch: {
description: 'Start building used packages in watch mode as well as the example app?',
},
});
}

const steps: Record<string, CLIStep> = {
repro: {
command: 'repro',
description: 'Bootstrapping example',
icon: '👷',
hasArgument: true,
options: {
template: { values: frameworks },
e2e: {},
},
},
start: {
name: 'Start the example app?',
inverse: true,
add: {
command: 'add',
description: 'Adding addon',
icon: '+',
hasArgument: true,
options: {},
},
build: {
name: 'Build the example app?',
command: 'build',
description: 'Building example',
icon: '🔨',
options: {},
},
watch: {
name: 'Start building used packages in watch mode as well as the example app?',
dev: {
command: 'dev',
description: 'Starting example',
icon: '🖥',
options: {},
},
}).then((r) => console.log(r));
};

async function main() {
const optionValues = await getOptions();
const examplesDir = path.resolve(__dirname, '../examples');

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

// TODO -- what to do when the directory already exists?
if (!existsSync(cwd)) {
await executeCLIStep(steps.repro, {
argument: cwd,
optionValues: { template: framework },
cwd: examplesDir,
});

// 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 });
}

// TODO copy stories
}

const { start } = optionValues;
if (start) {
await executeCLIStep(steps.dev, { cwd });
} else {
await executeCLIStep(steps.build, { cwd });
// TODO serve
}

// TODO start dev
}

main().catch((err) => console.error(err));
41 changes: 41 additions & 0 deletions scripts/utils/cli-step.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { getCommand, OptionSpecifier, OptionValues } from './options';
import { exec } from '../../lib/cli/src/repro-generators/scripts';

const cliExecutable = require.resolve('../../lib/cli/bin/index.js');

export type CLIStep = {
command: string;
description: string;
hasArgument?: boolean;
icon: string;
// It would be kind of great to be able to share these with `lib/cli/src/generate.ts`
options: OptionSpecifier;
};

export async function executeCLIStep(
cliStep: CLIStep,
options: {
argument?: string;
optionValues?: OptionValues;
cwd: string;
}
) {
if (cliStep.hasArgument && !options.argument)
throw new Error(`Argument required for ${cliStep.command} command.`);

const prefix = `node ${cliExecutable} ${cliStep.command}`;
const command = getCommand(
cliStep.hasArgument ? `${prefix} ${options.argument}` : prefix,
cliStep.options,
options.optionValues || {}
);

await exec(
command,
{ cwd: options.cwd },
{
startMessage: `${cliStep.icon} ${cliStep.description}`,
errorMessage: `🚨 ${cliStep.description} failed`,
}
);
}
8 changes: 4 additions & 4 deletions scripts/utils/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ import { getOptions, areOptionsSatisfied, getCommand } from './options';

const allOptions: OptionSpecifier = {
first: {
name: 'first',
description: 'first',
},
second: {
name: 'second',
description: 'second',
inverse: true,
},
third: {
name: 'third',
description: 'third',
values: ['one', 'two', 'three'],
required: true,
},
fourth: {
name: 'fourth',
description: 'fourth',
values: ['a', 'b', 'c'],
multiple: true,
},
Expand Down
15 changes: 9 additions & 6 deletions scripts/utils/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ import type { PromptObject } from 'prompts';
import program from 'commander';
import type { Command } from 'commander';
import kebabCase from 'lodash/kebabCase';
import { raw } from 'express';

export type OptionId = string;
export type OptionValue = string | boolean;
export type BaseOption = {
name: string;
description?: string;
/**
* By default the one-char version of the option key will be used as short flag. Override here,
* e.g. `shortFlag: 'c'`
Expand Down Expand Up @@ -72,9 +71,13 @@ function getRawOptions(command: Command, options: OptionSpecifier, argv: string[
.reduce((acc, [key, option]) => {
const flags = optionFlags(key, option);
if (isStringOption(option) && option.multiple) {
return acc.option(flags, option.name, (x, l) => [...l, x], []);
return acc.option(flags, option.description, (x, l) => [...l, x], []);
}
return acc.option(flags, option.name, isStringOption(option) ? undefined : !!option.inverse);
return acc.option(
flags,
option.description,
isStringOption(option) ? undefined : !!option.inverse
);
}, command)
.parse(argv);

Expand Down Expand Up @@ -115,7 +118,7 @@ export async function promptOptions(
const currentValue = values[key];
return {
type: option.multiple ? 'autocompleteMultiselect' : 'select',
message: option.name,
message: option.description,
name: key,
// warn: ' ',
// pageSize: Object.keys(tasks).length + Object.keys(groups).length,
Expand All @@ -130,7 +133,7 @@ export async function promptOptions(
}
return {
type: 'toggle',
message: option.name,
message: option.description,
name: key,
initial: option.inverse,
active: 'yes',
Expand Down

0 comments on commit 805111c

Please sign in to comment.