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

Cli framework choice #4184

Merged
merged 14 commits into from
Oct 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/cli/bin/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ if (process.argv[1].includes('getstorybook')) {
.option('-s --skip-install', 'Skip installing deps')
.option('-N --use-npm', 'Use npm to install deps')
.option('-p --parser <babel | babylon | flow>', 'jscodeshift parser')
.option('-h --html', 'Add storybook for HTML')
.option('-t --type <type>', 'Add Storybook for a specific project type')
.action(options => initiate(options, pkg));

program.command('*', { noHelp: true }).action(cmd => {
Expand Down
14 changes: 2 additions & 12 deletions lib/cli/lib/detect.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'path';
import fs from 'fs';
import types from './project_types';
import types, { supportedFrameworks } from './project_types';
import { getBowerJson, getPackageJson } from './helpers';

function detectFramework(dependencies) {
Expand Down Expand Up @@ -108,22 +108,12 @@ function detectFramework(dependencies) {
return false;
}

function isStorybookInstalled(dependencies, force) {
export function isStorybookInstalled(dependencies, force) {
if (!dependencies) {
return false;
}

if (!force && dependencies.devDependencies) {
const supportedFrameworks = [
'react',
'react-native',
'vue',
'angular',
'polymer',
'mithril',
'riot',
'ember',
];
if (
supportedFrameworks.reduce(
(storybookPresent, framework) =>
Expand Down
27 changes: 27 additions & 0 deletions lib/cli/lib/detect.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { isStorybookInstalled } from './detect';
import { supportedFrameworks } from './project_types';

describe('isStorybookInstalled should return', () => {
it('false if empty devDependency', () => {
expect(isStorybookInstalled({ devDependencies: {} }, false)).toBe(false);
});
it('false if no devDependency', () => {
expect(isStorybookInstalled({}, false)).toBe(false);
});

supportedFrameworks.forEach(framework => {
it(`true if devDependencies has ${framework} Storybook version`, () => {
const devDependencies = {};
devDependencies[`@storybook/${framework}`] = '4.0.0-alpha.21';
expect(isStorybookInstalled({ devDependencies }, false)).toBeTruthy();
});
});

it('true if forced flag', () => {
expect(
isStorybookInstalled({
devDependencies: { 'storybook/react': '4.0.0-alpha.21' },
})
).toBe(false);
});
});
104 changes: 74 additions & 30 deletions lib/cli/lib/initiate.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import updateNotifier from 'update-notifier';
import chalk from 'chalk';
import detect from './detect';
import inquirer from 'inquirer';
import detect, { isStorybookInstalled } from './detect';
import hasYarn from './has_yarn';
import types from './project_types';
import { commandLog, codeLog, paddedLog, installDeps } from './helpers';
import types, { installableProjectTypes } from './project_types';
import { commandLog, codeLog, paddedLog, installDeps, getPackageJson } from './helpers';
import angularGenerator from '../generators/ANGULAR';
import emberGenerator from '../generators/EMBER';
import meteorGenerator from '../generators/METEOR';
Expand All @@ -23,10 +24,7 @@ import riotGenerator from '../generators/RIOT';

const logger = console;

export default function(options, pkg) {
const welcomeMessage = 'sb init - the simplest way to add a storybook to your project.';
logger.log(chalk.inverse(`\n ${welcomeMessage} \n`));

const installStorybook = (projectType, options) => {
const useYarn = Boolean(options.useNpm !== true) && hasYarn();

const npmOptions = {
Expand All @@ -35,23 +33,6 @@ export default function(options, pkg) {

const runStorybookCommand = useYarn ? 'yarn storybook' : 'npm run storybook';

// Update notify code.
updateNotifier({
pkg,
updateCheckInterval: 1000 * 60 * 60, // every hour (we could increase this later on.)
}).notify();

let projectType;

const done = commandLog('Detecting project type');
try {
projectType = detect(options);
} catch (ex) {
done(ex.message);
process.exit(1);
}
done();

const end = () => {
if (!options.skipInstall) {
installDeps(npmOptions);
Expand All @@ -74,7 +55,7 @@ export default function(options, pkg) {
logger.log();
paddedLog('There seems to be a storybook already available in this project.');
paddedLog('Apply following command to force:\n');
codeLog(['sb init -f']);
codeLog(['sb init [options] -f']);

// Add a new line for the clear visibility.
logger.log();
Expand Down Expand Up @@ -179,20 +160,83 @@ export default function(options, pkg) {
default:
paddedLog(`We couldn't detect your project type. (code: ${projectType})`);
paddedLog(
"Please make sure you are running the `sb init` command in your project's root directory."
);
paddedLog(
'You can also install storybook for plain HTML snippets with `sb init --html` or follow some of the slow start guides: https://storybook.js.org/basics/slow-start-guide/'
'You can specify a project type explicitly via `sb init --type <type>` or follow some of the slow start guides: https://storybook.js.org/basics/slow-start-guide/'
);

// Add a new line for the clear visibility.
logger.log();
return Promise.resolve();
// eslint-disable-next-line no-use-before-define
return projectTypeInquirer(options);
}
};

return runGenerator().catch(ex => {
logger.error(`\n ${chalk.red(ex.stack)}`);
process.exit(1);
});
};

const projectTypeInquirer = async options => {
const manualAnswer = await inquirer.prompt([
{
type: 'confirm',
name: 'manual',
message: 'Do you want to manually choose a Storybook project type to install?',
default: false,
},
]);

if (manualAnswer.manual) {
const frameworkAnswer = await inquirer.prompt([
{
type: 'list',
name: 'manualFramework',
message: 'Please choose a project type from the following list:',
choices: installableProjectTypes.map(type => type.toUpperCase()),
},
]);
return installStorybook(frameworkAnswer.manualFramework, options);
}
return Promise.resolve();
};

export default function(options, pkg) {
const welcomeMessage = 'sb init - the simplest way to add a storybook to your project.';
logger.log(chalk.inverse(`\n ${welcomeMessage} \n`));

// Update notify code.
updateNotifier({
pkg,
updateCheckInterval: 1000 * 60 * 60, // every hour (we could increase this later on.)
}).notify();

let projectType;
const projectTypeProvided = options.type;
const infoText = projectTypeProvided
? 'Installing Storybook for user specified project type'
: 'Detecting project type';
const done = commandLog(infoText);

try {
if (projectTypeProvided) {
if (installableProjectTypes.includes(options.type)) {
const storybookInstalled = isStorybookInstalled(getPackageJson(), options.force);
projectType = storybookInstalled ? types.ALREADY_HAS_STORYBOOK : options.type.toUpperCase();
} else {
done(`The provided project type was not recognized by Storybook.`);
logger.log(`\nThe project types currently supported by Storybook are:\n`);
installableProjectTypes.sort().forEach(framework => paddedLog(`- ${framework}`));
logger.log();
process.exit(1);
}
} else {
projectType = detect(options);
}
} catch (ex) {
done(ex.message);
process.exit(1);
}
done();

return installStorybook(projectType, options);
}
27 changes: 26 additions & 1 deletion lib/cli/lib/project_types.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default {
const projectTypes = {
UNDETECTED: 'UNDETECTED',
REACT_SCRIPTS: 'REACT_SCRIPTS',
METEOR: 'METEOR',
Expand All @@ -19,3 +19,28 @@ export default {
HTML: 'HTML',
RIOT: 'RIOT',
};

export default projectTypes;

export const supportedFrameworks = [
'react',
'react-native',
'vue',
'angular',
'polymer',
'mithril',
'riot',
'ember',
'marko',
'meteor',
];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ember
marko
meteor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm I just extracted this array to another file. Ember support wasn't present yet when I made this PR, but marko en meteor def should have been. I don't see them in the removed code either tho 😱 will add them I'm a few hours


const notInstallableProjectTypes = [
projectTypes.UNDETECTED,
projectTypes.ALREADY_HAS_STORYBOOK,
projectTypes.UPDATE_PACKAGE_ORGANIZATIONS,
];

export const installableProjectTypes = Object.values(projectTypes)
.filter(type => !notInstallableProjectTypes.includes(type))
.map(type => type.toLowerCase());
9 changes: 9 additions & 0 deletions lib/cli/lib/project_types.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { installableProjectTypes, supportedFrameworks } from './project_types';

describe('installableProjectTypes should have an entry for the supported framework', () => {
supportedFrameworks.forEach(framework => {
it(`${framework}`, () => {
expect(installableProjectTypes.includes(framework.replace(/-/g, '_'))).toBe(true);
});
});
});
1 change: 1 addition & 0 deletions lib/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"child-process-promise": "^2.2.1",
"commander": "^2.17.0",
"cross-spawn": "^6.0.5",
"inquirer": "^6.2.0",
"jscodeshift": "^0.5.1",
"json5": "^2.0.1",
"merge-dirs": "^0.2.1",
Expand Down