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: refactor to simplify works with multiple package managers #11074

Merged
merged 23 commits into from
Jun 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ed79e5b
refactor: introduce a JsPackageManager abstract class and specialized…
gaetanmaisse Jun 5, 2020
0334c80
refactor: create a JsPackageManagerFactory
gaetanmaisse Jun 5, 2020
100ffac
refactor: use `getRunStorybookCommand` from package manager
gaetanmaisse Jun 7, 2020
aa02e87
refactor: add tmp extract check to avoid issue in unit tests
gaetanmaisse Jun 7, 2020
a858990
refactor: move `installDepsFromPackageJson` function to JsPackageMana…
gaetanmaisse Jun 7, 2020
10e4101
refactor: introduce JsPackageManager in all generators
gaetanmaisse Jun 7, 2020
56de607
refactor: move `retrievePackageJson` to JsPackageManager
gaetanmaisse Jun 7, 2020
c714aca
refactor: move `installDependencies` to JsPackageManager
gaetanmaisse Jun 7, 2020
b6497c2
refactor: move `getVersionedPackages` to JsPackageManager
gaetanmaisse Jun 7, 2020
2760021
refactor: move `getVersions` to JsPackageManager
gaetanmaisse Jun 7, 2020
126e7e2
refactor: move `getVersion` to JsPackageManager
gaetanmaisse Jun 7, 2020
a23e4a0
refactor: move `latestVersion` to JsPackageManager and split specific…
gaetanmaisse Jun 7, 2020
2912bcb
refactor: remove unneeded params and variables
gaetanmaisse Jun 7, 2020
d400003
refactor: remove usage of `has_yarn`
gaetanmaisse Jun 7, 2020
41dc957
refactor: move PackageJson.ts inside `js-package-manager` directory
gaetanmaisse Jun 7, 2020
9462016
refactor: create function to add SB command in `scripts` attribute of…
gaetanmaisse Jun 7, 2020
6c383a0
fix: update versions.json
gaetanmaisse Jun 8, 2020
0c1a974
refactor: update signature of `runInstall` function
gaetanmaisse Jun 8, 2020
f5c788b
refactor: update signature of `runAddDeps` function
gaetanmaisse Jun 8, 2020
b0e1d93
refactor: create a `executeCommand` function and use it for every `ya…
gaetanmaisse Jun 8, 2020
26dd11b
refactor: move `readPackageJson` and `writePackageJson` in their own …
gaetanmaisse Jun 8, 2020
9dc7c55
refactor: ensure `dep` and `devDeps` are always defined when retrievi…
gaetanmaisse Jun 8, 2020
b9ed269
Merge branch 'next' into refactor-cli
ndelangen Jun 10, 2020
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
1 change: 0 additions & 1 deletion lib/cli/src/NpmOptions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export type NpmOptions = {
useYarn: boolean;
skipInstall?: boolean;
installAsDevDependencies?: boolean;
};
5 changes: 0 additions & 5 deletions lib/cli/src/PackageJson.ts

This file was deleted.

54 changes: 21 additions & 33 deletions lib/cli/src/add.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,23 @@
import path from 'path';
import fs from 'fs';
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';
import { commandLog } from './helpers';
import { JsPackageManager, JsPackageManagerFactory, PackageJson } from './js-package-manager';

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

const isAddon = async (
name: string,
npmOptions: {
useYarn: boolean;
}
) => {
const isAddon = async (packageManager: JsPackageManager, name: string) => {
try {
await latestVersion(npmOptions, name);
await packageManager.latestVersion(name);
return true;
} catch (e) {
return false;
}
};

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

export const getPackageName = (addonName: string, isOfficialAddon: boolean) =>
isOfficialAddon ? storybookAddonScope + addonName : addonName;
Expand All @@ -51,29 +44,26 @@ export const getPackageArg = (
};

const installAddon = (
packageManager: JsPackageManager,
addonName: string,
npmOptions: { useYarn: boolean },
isOfficialAddon: boolean
) => {
const prepareDone = commandLog(`Preparing to install the ${addonName} Storybook addon`);
prepareDone();
logger.log();

let result;
const packageArg = getPackageArg(addonName, isOfficialAddon, getPackageJson());
if (npmOptions.useYarn) {
result = spawnSync('yarn', ['add', packageArg, '--dev'], {
stdio: 'inherit',
});
} else {
result = spawnSync('npm', ['install', packageArg, '--save-dev'], {
stdio: 'inherit',
});
}
const packageArg = getPackageArg(
addonName,
isOfficialAddon,
packageManager.retrievePackageJson()
);

logger.log();
const installDone = commandLog(`Installing the ${addonName} Storybook addon`);
if (result.status !== 0) {

try {
packageManager.addDependencies({}, [packageArg]);
} catch (e) {
installDone(
`Something went wrong installing the addon: "${getPackageName(addonName, isOfficialAddon)}"`
);
Expand Down Expand Up @@ -150,20 +140,18 @@ export async function add(
addonName: string,
options: { useNpm: boolean; skipPostinstall: boolean }
) {
const useYarn = Boolean(options.useNpm !== true) && hasYarn();
const npmOptions = {
useYarn,
};
const packageManager = JsPackageManagerFactory.getPackageManager(options.useNpm);

const addonCheckDone = commandLog(`Verifying that ${addonName} is an addon`);
const isOfficialAddon = await isStorybookAddon(addonName, npmOptions);
const isOfficialAddon = await isStorybookAddon(packageManager, addonName);
if (!isOfficialAddon) {
if (!(await isAddon(addonName, npmOptions))) {
if (!(await isAddon(packageManager, addonName))) {
addonCheckDone(`The provided package was not a Storybook addon: ${addonName}.`);
return;
}
}
addonCheckDone();
installAddon(addonName, npmOptions, isOfficialAddon);
installAddon(packageManager, addonName, isOfficialAddon);
if (!options.skipPostinstall) {
await postinstallAddon(addonName, isOfficialAddon);
}
Expand Down
16 changes: 10 additions & 6 deletions lib/cli/src/detect.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import fs from 'fs';

import { getBowerJson, getPackageJson } from './helpers';
import { getBowerJson } from './helpers';
import { isStorybookInstalled, detectFrameworkPreset, detect, detectLanguage } from './detect';
import { ProjectType, SUPPORTED_FRAMEWORKS, SupportedLanguage } from './project_types';
import { readPackageJson } from './js-package-manager';

jest.mock('./helpers', () => ({
getBowerJson: jest.fn(),
getPackageJson: jest.fn(),
}));

jest.mock('./js-package-manager', () => ({
readPackageJson: jest.fn(),
}));

jest.mock('fs', () => ({
Expand Down Expand Up @@ -209,18 +213,18 @@ const MOCK_FRAMEWORK_FILES = [

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

it(`should return type UNDETECTED if neither packageJson or bowerJson exist`, () => {
(getPackageJson as jest.Mock).mockImplementation(() => false);
(readPackageJson 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 as jest.Mock).mockImplementation(() => ({
(readPackageJson as jest.Mock).mockImplementation(() => ({
dependencies: {
typescript: '1.0.0',
},
Expand All @@ -229,7 +233,7 @@ describe('Detect', () => {
});

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

Expand Down
10 changes: 5 additions & 5 deletions lib/cli/src/detect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
TemplateConfiguration,
TemplateMatcher,
} from './project_types';
import { getBowerJson, getPackageJson } from './helpers';
import { PackageJson } from './PackageJson';
import { getBowerJson } from './helpers';
import { PackageJson, readPackageJson } from './js-package-manager';

const hasDependency = (packageJson: PackageJson, name: string) => {
return !!packageJson.dependencies?.[name] || !!packageJson.devDependencies?.[name];
Expand Down Expand Up @@ -55,7 +55,7 @@ export function detectFrameworkPreset(packageJson = {}) {
return result ? result.preset : ProjectType.UNDETECTED;
}

export function isStorybookInstalled(dependencies: PackageJson, force?: boolean) {
export function isStorybookInstalled(dependencies: PackageJson | false, force?: boolean) {
if (!dependencies) {
return false;
}
Expand Down Expand Up @@ -83,7 +83,7 @@ export function isStorybookInstalled(dependencies: PackageJson, force?: boolean)

export function detectLanguage() {
let language = SupportedLanguage.JAVASCRIPT;
const packageJson = getPackageJson();
const packageJson = readPackageJson();
const bowerJson = getBowerJson();
if (!packageJson && !bowerJson) {
return language;
Expand All @@ -97,7 +97,7 @@ export function detectLanguage() {
}

export function detect(options: { force?: boolean; html?: boolean } = {}) {
const packageJson = getPackageJson();
const packageJson = readPackageJson();
const bowerJson = getBowerJson();

if (!packageJson && !bowerJson) {
Expand Down
41 changes: 17 additions & 24 deletions lib/cli/src/generators/ANGULAR/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,17 @@ import {
getAngularAppTsConfigJson,
getAngularAppTsConfigPath,
} from './angular-helpers';
import {
retrievePackageJson,
getVersionedPackages,
writePackageJson,
getBabelDependencies,
installDependencies,
writeFileAsJson,
copyTemplate,
} from '../../helpers';
import { getBabelDependencies, writeFileAsJson, copyTemplate } from '../../helpers';
import { StoryFormat } from '../../project_types';
import { NpmOptions } from '../../NpmOptions';
import { Generator, GeneratorOptions } from '../Generator';
import { JsPackageManager } from '../../js-package-manager';

async function addDependencies(npmOptions: NpmOptions, { storyFormat }: GeneratorOptions) {
async function addDependencies(
packageManager: JsPackageManager,
npmOptions: NpmOptions,
{ storyFormat }: GeneratorOptions
) {
const packages = [
'@storybook/angular',
'@storybook/addon-actions',
Expand All @@ -30,22 +27,18 @@ async function addDependencies(npmOptions: NpmOptions, { storyFormat }: Generato
packages.push('@storybook/addon-docs');
}

const versionedPackages = await getVersionedPackages(npmOptions, ...packages);

const packageJson = await retrievePackageJson();

packageJson.dependencies = packageJson.dependencies || {};
packageJson.devDependencies = packageJson.devDependencies || {};
const versionedPackages = await packageManager.getVersionedPackages(...packages);

packageJson.scripts = packageJson.scripts || {};
packageJson.scripts.storybook = 'start-storybook -p 6006';
packageJson.scripts['build-storybook'] = 'build-storybook';
const packageJson = packageManager.retrievePackageJson();

writePackageJson(packageJson);
const babelDependencies = await getBabelDependencies(packageManager, packageJson);

const babelDependencies = await getBabelDependencies(npmOptions, packageJson);
packageManager.addDependencies({ ...npmOptions, packageJson }, [
...versionedPackages,
...babelDependencies,
]);

installDependencies({ ...npmOptions, packageJson }, [...versionedPackages, ...babelDependencies]);
packageManager.addStorybookCommandInScripts();
}

function editAngularAppTsConfig() {
Expand All @@ -64,7 +57,7 @@ function editAngularAppTsConfig() {
writeFileAsJson(getAngularAppTsConfigPath(), tsConfigJson);
}

const generator: Generator = async (npmOptions, { storyFormat }) => {
const generator: Generator = async (packageManager, npmOptions, { storyFormat }) => {
if (!isDefaultProjectSet()) {
throw new Error(
'Could not find a default project in your Angular workspace.\nSet a defaultProject in your angular.json and re-run the installation.'
Expand All @@ -73,7 +66,7 @@ const generator: Generator = async (npmOptions, { storyFormat }) => {

copyTemplate(__dirname, storyFormat);

await addDependencies(npmOptions, { storyFormat });
await addDependencies(packageManager, npmOptions, { storyFormat });
editAngularAppTsConfig();
editStorybookTsConfig(path.resolve('./.storybook/tsconfig.json'));
};
Expand Down
36 changes: 15 additions & 21 deletions lib/cli/src/generators/AURELIA/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
import {
getVersionedPackages,
writePackageJson,
getBabelDependencies,
installDependencies,
getPackageJson,
writeFileAsJson,
copyTemplate,
readFileAsJson,
} from '../../helpers';
import { getBabelDependencies, writeFileAsJson, copyTemplate, readFileAsJson } from '../../helpers';
import { Generator } from '../Generator';
import { StoryFormat } from '../../project_types';

Expand All @@ -26,7 +17,11 @@ function addStorybookExcludeGlobToTsConfig() {
tsConfigJson.exclude = [...exclude, glob];
writeFileAsJson('tsconfig.json', tsConfigJson);
}
const generator: Generator = async (npmOptions, { storyFormat = StoryFormat.CSF }) => {
const generator: Generator = async (
packageManager,
npmOptions,
{ storyFormat = StoryFormat.CSF }
) => {
copyTemplate(__dirname, storyFormat);
const packages = [
'@storybook/aurelia',
Expand All @@ -45,17 +40,16 @@ const generator: Generator = async (npmOptions, { storyFormat = StoryFormat.CSF
packages.push('@storybook/addon-docs');
}

const versionedPackages = await getVersionedPackages(npmOptions, ...packages);
const packageJson = getPackageJson();
packageJson.dependencies = packageJson.dependencies || {};
packageJson.devDependencies = packageJson.devDependencies || {};
packageJson.scripts = packageJson.scripts || {};
packageJson.scripts.storybook = 'start-storybook -p 6006';
packageJson.scripts['build-storybook'] = 'build-storybook';
writePackageJson(packageJson);
const versionedPackages = await packageManager.getVersionedPackages(...packages);
const packageJson = packageManager.retrievePackageJson();
addStorybookExcludeGlobToTsConfig();
const babelDependencies = await getBabelDependencies(npmOptions, packageJson);
installDependencies({ ...npmOptions, packageJson }, [...versionedPackages, ...babelDependencies]);
const babelDependencies = await getBabelDependencies(packageManager, packageJson);
packageManager.addDependencies({ ...npmOptions, packageJson }, [
...versionedPackages,
...babelDependencies,
]);

packageManager.addStorybookCommandInScripts();
};

export default generator;
36 changes: 8 additions & 28 deletions lib/cli/src/generators/EMBER/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import {
getVersions,
retrievePackageJson,
writePackageJson,
getBabelDependencies,
installDependencies,
copyTemplate,
} from '../../helpers';
import { getBabelDependencies, copyTemplate } from '../../helpers';
import { Generator } from '../Generator';

const generator: Generator = async (npmOptions, { storyFormat }) => {
const generator: Generator = async (packageManager, npmOptions, { storyFormat }) => {
const [
storybookVersion,
babelPluginEmberModulePolyfillVersion,
babelPluginHtmlBarsInlinePrecompileVersion,
linksVersion,
actionsVersion,
addonsVersion,
] = await getVersions(
npmOptions,
] = await packageManager.getVersions(
'@storybook/ember',
// babel-plugin-ember-modules-api-polyfill is a peerDep of @storybook/ember
'babel-plugin-ember-modules-api-polyfill',
Expand All @@ -30,24 +22,10 @@ const generator: Generator = async (npmOptions, { storyFormat }) => {

copyTemplate(__dirname, storyFormat);

const packageJson = await retrievePackageJson();
const packageJson = packageManager.retrievePackageJson();
const babelDependencies = await getBabelDependencies(packageManager, packageJson);

packageJson.dependencies = packageJson.dependencies || {};
packageJson.devDependencies = packageJson.devDependencies || {};

packageJson.scripts = {
...packageJson.scripts,
...{
storybook: 'start-storybook -p 6006 -s dist',
'build-storybook': 'build-storybook -s dist',
},
};

writePackageJson(packageJson);

const babelDependencies = await getBabelDependencies(npmOptions, packageJson);

installDependencies({ ...npmOptions, packageJson }, [
packageManager.addDependencies({ ...npmOptions, packageJson }, [
`@storybook/ember@${storybookVersion}`,
`@storybook/addon-actions@${actionsVersion}`,
`@storybook/addon-links@${linksVersion}`,
Expand All @@ -56,6 +34,8 @@ const generator: Generator = async (npmOptions, { storyFormat }) => {
`babel-plugin-htmlbars-inline-precompile@${babelPluginHtmlBarsInlinePrecompileVersion}`,
...babelDependencies,
]);

packageManager.addStorybookCommandInScripts();
};

export default generator;
Loading