Skip to content

Commit

Permalink
Merge pull request #10802 from storybookjs/tech/migrate-cli-to-TS
Browse files Browse the repository at this point in the history
CLI: Migrate CLI to TypeScript
  • Loading branch information
gaetanmaisse authored May 18, 2020
2 parents b451d60 + 9583c02 commit ee7c9ec
Show file tree
Hide file tree
Showing 44 changed files with 538 additions and 318 deletions.
8 changes: 7 additions & 1 deletion lib/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,13 @@
"@storybook/svelte": "6.0.0-beta.8",
"@storybook/ui": "6.0.0-beta.8",
"@storybook/vue": "6.0.0-beta.8",
"@storybook/web-components": "6.0.0-beta.8"
"@storybook/web-components": "6.0.0-beta.8",
"@types/cross-spawn": "^6.0.1",
"@types/inquirer": "^6.5.0",
"@types/puppeteer-core": "^2.0.0",
"@types/semver": "^7.2.0",
"@types/shelljs": "^0.8.7",
"@types/update-notifier": "^0.0.30"
},
"peerDependencies": {
"jest": "*"
Expand Down
5 changes: 5 additions & 0 deletions lib/cli/src/NpmOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type NpmOptions = {
useYarn: boolean;
skipInstall?: boolean;
installAsDevDependencies?: boolean;
};
5 changes: 5 additions & 0 deletions lib/cli/src/PackageJson.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type PackageJson = {
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
peerDependencies?: Record<string, string>;
};
File renamed without changes.
39 changes: 30 additions & 9 deletions lib/cli/src/add.js → lib/cli/src/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ import { sync as spawnSync } from 'cross-spawn';
import { hasYarn } from './has_yarn';
import { latestVersion } from './latest_version';
import { commandLog, getPackageJson } from './helpers';
import { PackageJson } from './PackageJson';

const logger = console;
export const storybookAddonScope = '@storybook/addon-';

const isAddon = async (name, npmOptions) => {
const isAddon = async (
name: string,
npmOptions: {
useYarn: boolean;
}
) => {
try {
await latestVersion(npmOptions, name);
return true;
Expand All @@ -17,19 +23,23 @@ const isAddon = async (name, npmOptions) => {
}
};

const isStorybookAddon = async (name, npmOptions) =>
const isStorybookAddon = async (name: string, npmOptions: { useYarn: boolean }) =>
isAddon(`${storybookAddonScope}${name}`, npmOptions);

export const getPackageName = (addonName, isOfficialAddon) =>
export const getPackageName = (addonName: string, isOfficialAddon: boolean) =>
isOfficialAddon ? storybookAddonScope + addonName : addonName;

export const getInstalledStorybookVersion = (packageJson) =>
export const getInstalledStorybookVersion = (packageJson: PackageJson) =>
packageJson.devDependencies[
// This only considers the first occurrence.
Object.keys(packageJson.devDependencies).find((devDep) => /@storybook/.test(devDep))
] || false;

export const getPackageArg = (addonName, isOfficialAddon, packageJson) => {
export const getPackageArg = (
addonName: string,
isOfficialAddon: boolean,
packageJson: PackageJson
) => {
if (isOfficialAddon) {
const addonNameNoTag = addonName.split('@')[0];
const installedStorybookVersion = getInstalledStorybookVersion(packageJson);
Expand All @@ -40,7 +50,11 @@ export const getPackageArg = (addonName, isOfficialAddon, packageJson) => {
return addonName;
};

const installAddon = (addonName, npmOptions, isOfficialAddon) => {
const installAddon = (
addonName: string,
npmOptions: { useYarn: boolean },
isOfficialAddon: boolean
) => {
const prepareDone = commandLog(`Preparing to install the ${addonName} Storybook addon`);
prepareDone();
logger.log();
Expand Down Expand Up @@ -69,7 +83,11 @@ const installAddon = (addonName, npmOptions, isOfficialAddon) => {
installDone();
};

export const addStorybookAddonToFile = (addonName, addonsFile, isOfficialAddon) => {
export const addStorybookAddonToFile = (
addonName: string,
addonsFile: string[],
isOfficialAddon: boolean
) => {
const addonNameNoTag = addonName.split('@')[0];
const alreadyRegistered = addonsFile.find((line) => line.includes(`${addonNameNoTag}/register`));

Expand All @@ -92,7 +110,7 @@ export const addStorybookAddonToFile = (addonName, addonsFile, isOfficialAddon)

const LEGACY_CONFIGS = ['addons', 'config', 'presets'];

const postinstallAddon = async (addonName, isOfficialAddon) => {
const postinstallAddon = async (addonName: string, isOfficialAddon: boolean) => {
let skipMsg = null;
if (!isOfficialAddon) {
skipMsg = 'unofficial addon';
Expand Down Expand Up @@ -128,7 +146,10 @@ const postinstallAddon = async (addonName, isOfficialAddon) => {
}
};

export async function add(addonName, options) {
export async function add(
addonName: string,
options: { useNpm: boolean; skipPostinstall: boolean }
) {
const useYarn = Boolean(options.useNpm !== true) && hasYarn();
const npmOptions = {
useYarn,
Expand Down
68 changes: 34 additions & 34 deletions lib/cli/src/detect.test.js → lib/cli/src/detect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fs from 'fs';

import { getBowerJson, getPackageJson } from './helpers';
import { isStorybookInstalled, detectFrameworkPreset, detect, detectLanguage } from './detect';
import { PROJECT_TYPES, SUPPORTED_FRAMEWORKS, SUPPORTED_LANGUAGES } from './project_types';
import { ProjectType, SUPPORTED_FRAMEWORKS, SupportedLanguage } from './project_types';

jest.mock('./helpers', () => ({
getBowerJson: jest.fn(),
Expand All @@ -20,13 +20,13 @@ jest.mock('path', () => ({

const MOCK_FRAMEWORK_FILES = [
{
name: PROJECT_TYPES.METEOR,
name: ProjectType.METEOR,
files: {
'.meteor': 'file content',
},
},
{
name: PROJECT_TYPES.SFC_VUE,
name: ProjectType.SFC_VUE,
files: {
'package.json': {
dependencies: {
Expand All @@ -39,7 +39,7 @@ const MOCK_FRAMEWORK_FILES = [
},
},
{
name: PROJECT_TYPES.VUE,
name: ProjectType.VUE,
files: {
'package.json': {
dependencies: {
Expand All @@ -49,7 +49,7 @@ const MOCK_FRAMEWORK_FILES = [
},
},
{
name: PROJECT_TYPES.EMBER,
name: ProjectType.EMBER,
files: {
'package.json': {
devDependencies: {
Expand All @@ -59,7 +59,7 @@ const MOCK_FRAMEWORK_FILES = [
},
},
{
name: PROJECT_TYPES.REACT_PROJECT,
name: ProjectType.REACT_PROJECT,
files: {
'package.json': {
peerDependencies: {
Expand All @@ -69,7 +69,7 @@ const MOCK_FRAMEWORK_FILES = [
},
},
{
name: PROJECT_TYPES.REACT_NATIVE,
name: ProjectType.REACT_NATIVE,
files: {
'package.json': {
dependencies: {
Expand All @@ -82,7 +82,7 @@ const MOCK_FRAMEWORK_FILES = [
},
},
{
name: PROJECT_TYPES.REACT_SCRIPTS,
name: ProjectType.REACT_SCRIPTS,
files: {
'package.json': {
devDependencies: {
Expand All @@ -92,7 +92,7 @@ const MOCK_FRAMEWORK_FILES = [
},
},
{
name: PROJECT_TYPES.WEBPACK_REACT,
name: ProjectType.WEBPACK_REACT,
files: {
'package.json': {
dependencies: {
Expand All @@ -105,7 +105,7 @@ const MOCK_FRAMEWORK_FILES = [
},
},
{
name: PROJECT_TYPES.REACT,
name: ProjectType.REACT,
files: {
'package.json': {
dependencies: {
Expand All @@ -115,7 +115,7 @@ const MOCK_FRAMEWORK_FILES = [
},
},
{
name: PROJECT_TYPES.ANGULAR,
name: ProjectType.ANGULAR,
files: {
'package.json': {
dependencies: {
Expand All @@ -125,7 +125,7 @@ const MOCK_FRAMEWORK_FILES = [
},
},
{
name: PROJECT_TYPES.WEB_COMPONENTS,
name: ProjectType.WEB_COMPONENTS,
files: {
'package.json': {
dependencies: {
Expand All @@ -135,7 +135,7 @@ const MOCK_FRAMEWORK_FILES = [
},
},
{
name: PROJECT_TYPES.MITHRIL,
name: ProjectType.MITHRIL,
files: {
'package.json': {
dependencies: {
Expand All @@ -145,7 +145,7 @@ const MOCK_FRAMEWORK_FILES = [
},
},
{
name: PROJECT_TYPES.MARIONETTE,
name: ProjectType.MARIONETTE,
files: {
'package.json': {
dependencies: {
Expand All @@ -155,7 +155,7 @@ const MOCK_FRAMEWORK_FILES = [
},
},
{
name: PROJECT_TYPES.MARKO,
name: ProjectType.MARKO,
files: {
'package.json': {
dependencies: {
Expand All @@ -165,7 +165,7 @@ const MOCK_FRAMEWORK_FILES = [
},
},
{
name: PROJECT_TYPES.RIOT,
name: ProjectType.RIOT,
files: {
'package.json': {
dependencies: {
Expand All @@ -175,7 +175,7 @@ const MOCK_FRAMEWORK_FILES = [
},
},
{
name: PROJECT_TYPES.PREACT,
name: ProjectType.PREACT,
files: {
'package.json': {
dependencies: {
Expand All @@ -185,7 +185,7 @@ const MOCK_FRAMEWORK_FILES = [
},
},
{
name: PROJECT_TYPES.SVELTE,
name: ProjectType.SVELTE,
files: {
'package.json': {
dependencies: {
Expand All @@ -195,7 +195,7 @@ const MOCK_FRAMEWORK_FILES = [
},
},
{
name: PROJECT_TYPES.RAX,
name: ProjectType.RAX,
files: {
'.rax': 'file content',
'package.json': {
Expand All @@ -209,28 +209,28 @@ const MOCK_FRAMEWORK_FILES = [

describe('Detect', () => {
it(`should return type HTML if html option is passed`, () => {
getPackageJson.mockImplementation(() => true);
expect(detect({ html: true })).toBe(PROJECT_TYPES.HTML);
(getPackageJson as jest.Mock).mockImplementation(() => true);
expect(detect({ html: true })).toBe(ProjectType.HTML);
});

it(`should return type UNDETECTED if neither packageJson or bowerJson exist`, () => {
getPackageJson.mockImplementation(() => false);
getBowerJson.mockImplementation(() => false);
expect(detect()).toBe(PROJECT_TYPES.UNDETECTED);
(getPackageJson as jest.Mock).mockImplementation(() => false);
(getBowerJson as jest.Mock).mockImplementation(() => false);
expect(detect()).toBe(ProjectType.UNDETECTED);
});

it(`should return language typescript if the dependency is present`, () => {
getPackageJson.mockImplementation(() => ({
(getPackageJson as jest.Mock).mockImplementation(() => ({
dependencies: {
typescript: '1.0.0',
},
}));
expect(detectLanguage()).toBe(SUPPORTED_LANGUAGES.TYPESCRIPT);
expect(detectLanguage()).toBe(SupportedLanguage.TYPESCRIPT);
});

it(`should return language javascript by default`, () => {
getPackageJson.mockImplementation(() => true);
expect(detectLanguage()).toBe(SUPPORTED_LANGUAGES.JAVASCRIPT);
(getPackageJson as jest.Mock).mockImplementation(() => true);
expect(detectLanguage()).toBe(SupportedLanguage.JAVASCRIPT);
});

describe('isStorybookInstalled should return', () => {
Expand Down Expand Up @@ -267,15 +267,15 @@ describe('Detect', () => {
isStorybookInstalled({
devDependencies: { '@storybook/react': '4.0.0-alpha.21' },
})
).toBe(PROJECT_TYPES.ALREADY_HAS_STORYBOOK);
).toBe(ProjectType.ALREADY_HAS_STORYBOOK);
});

it('UPDATE_PACKAGE_ORGANIZATIONS if legacy lib is detected', () => {
expect(
isStorybookInstalled({
devDependencies: { '@kadira/storybook': '4.0.0-alpha.21' },
})
).toBe(PROJECT_TYPES.UPDATE_PACKAGE_ORGANIZATIONS);
).toBe(ProjectType.UPDATE_PACKAGE_ORGANIZATIONS);
});
});

Expand All @@ -286,7 +286,7 @@ describe('Detect', () => {

MOCK_FRAMEWORK_FILES.forEach((structure) => {
it(`${structure.name}`, () => {
fs.existsSync.mockImplementation((filePath) => {
(fs.existsSync as jest.Mock).mockImplementation((filePath) => {
return Object.keys(structure.files).includes(filePath);
});

Expand All @@ -298,20 +298,20 @@ describe('Detect', () => {

it(`UNDETECTED for unknown frameworks`, () => {
const result = detectFrameworkPreset();
expect(result).toBe(PROJECT_TYPES.UNDETECTED);
expect(result).toBe(ProjectType.UNDETECTED);
});

it('REACT_SCRIPTS for custom react scripts config', () => {
const forkedReactScriptsConfig = {
'/node_modules/.bin/react-scripts': 'file content',
};

fs.existsSync.mockImplementation((filePath) => {
(fs.existsSync as jest.Mock).mockImplementation((filePath) => {
return Object.keys(forkedReactScriptsConfig).includes(filePath);
});

const result = detectFrameworkPreset();
expect(result).toBe(PROJECT_TYPES.REACT_SCRIPTS);
expect(result).toBe(ProjectType.REACT_SCRIPTS);
});
});
});
Loading

0 comments on commit ee7c9ec

Please sign in to comment.