Skip to content

Commit

Permalink
Check for missing dependencies (#1)
Browse files Browse the repository at this point in the history
* Rename addon-styling to addon-styling-webpack
* Check for missing dependencies
* Fix installs for theme decorators
  • Loading branch information
Shaun Evening authored Aug 25, 2023
1 parent 7b154f1 commit 17ffdcd
Show file tree
Hide file tree
Showing 24 changed files with 348 additions and 172 deletions.
96 changes: 96 additions & 0 deletions src/@storybook/addon-styling-webpack/configure/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import dedent from 'dedent';

import { DEFAULT_CONFIGURATION_MAP, ConfigurationMap, ConfigurationKey } from '../types';

import { generateCssRules } from './css-rules';
import { generateSassRules } from './sass-rules';
import { generateLessRules } from './less-rules';
import { JsPackageManager } from '@storybook/cli';

const setConfigWithDefaults = (configMap: Partial<ConfigurationMap>): ConfigurationMap => ({
...DEFAULT_CONFIGURATION_MAP,
...configMap,
});

export const buildImports = (configMap: Partial<ConfigurationMap>): string => {
const { vanillaExtract } = setConfigWithDefaults(configMap);

return vanillaExtract
? dedent`
import { VanillaExtractPlugin } from "@vanilla-extract/webpack-plugin";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
`
: '';
};

const buildPluginsArray = ({ vanillaExtract }: ConfigurationMap): string =>
vanillaExtract
? dedent`
plugins: [new VanillaExtractPlugin(), new MiniCssExtractPlugin()],`
: '';

export const buildAddonConfig = (configMap: Partial<ConfigurationMap>): string => {
const config = setConfigWithDefaults(configMap);

return dedent`({
name: "@storybook/addon-styling-webpack",
options: {${buildPluginsArray(config)}
rules: [${generateCssRules(config)}${generateSassRules(config)}${generateLessRules(config)}],
}
})`;
};

const BASE_DEPENDENCIES = {
'style-loader': '',
'css-loader': '',
};

const REQUIRED_DEPENDENCIES: Record<ConfigurationKey, Record<string, string>> = {
cssModules: {},
sass: {
'sass-loader': '',
'resolve-url-loader': '',
},
less: {
'less-loader': '',
},
postcss: {
'postcss-loader': '',
postcss: '',
},
vanillaExtract: {
'@vanilla-extract/webpack-plugin': '',
'mini-css-extract-plugin': '',
},
};

export const checkForMissingDependencies = async (
packageManager: JsPackageManager,
configMap: Partial<ConfigurationMap>,
extraDependencies: Record<string, string> = {},
): Promise<Record<string, string>> => {
const configured = Object.entries(configMap)
.filter(([key, isUsed]) => isUsed)
.map(([key]) => key) as ConfigurationKey[];

const dependenciesToCheck = configured.reduce((deps, key) => ({ ...deps, ...REQUIRED_DEPENDENCIES[key] }), {
...BASE_DEPENDENCIES,
...extraDependencies,
});

const userDependencies = await packageManager.getAllDependencies();

const missingDependencies = Object.entries(dependenciesToCheck).reduce(
(missing, [dependency, version]) => {
if (!userDependencies[dependency]) {
return { ...missing, [dependency]: version };
}

return missing;
},
{} as Record<string, string>,
);

return missingDependencies;
};
58 changes: 58 additions & 0 deletions src/@storybook/addon-styling-webpack/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { logger, colors } from '@storybook/node-logger';
import dedent from 'dedent';
import prompt from 'prompts';

import { ConfigSummary, printError, printWarning } from '../../utils/output.utils';

const printUnsupportedBuilderError = () => {
printError(
'Unsupported builder',
dedent`"${colors.green.bold('@storybook/addon-styling-webpack')}" is for webpack projects only.
Please remove it from your project to avoid unneeded dependencies.`,
);
};

export const Errors = {
unsupportedBuilder: printUnsupportedBuilderError,
};

export const askToInstallMissingDependencies = async (
missingDependencies: Record<string, string>,
): Promise<boolean> => {
printWarning(
'💬 Missing dependencies',
`I noticed that you're missing some dependencies for this configuration.
Would you like me to install the following dependencies for you?
${Object.keys(missingDependencies)
.map((dep) => ` - ${colors.blue(dep)}`)
.join('\n')}`,
);

const { installDependencies } = await prompt(
{
type: 'confirm',
name: 'installDependencies',
message: 'Install missing dependencies?',
initial: true,
},
{ onCancel: () => process.exit(0) },
);
logger.line(1);

return installDependencies;
};

export const buildSummary = (summary: ConfigSummary) =>
`${
summary.strategy === 'custom'
? "I configured Storybook's Webpack as you asked!"
: `"${colors.blue.bold(summary.strategy)}" has been configured and will now work in your stories!`
}
${colors.purple.bold('What I did:')}
${summary.changed.map((change) => ` - ${change}`).join('\n')}
${colors.purple.bold('Next steps:')}
${summary.nextSteps.map((step) => ` - ${step}`).join('\n')}`;
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Errors, buildSummary } from './helpers';
export interface Options {}

const autoConfigure = async ({}: Options = {}) => {
printWelcome('@storybook/addon-styling');
printWelcome('@storybook/addon-styling-webpack');

const isGitDirty = (await isGitClean()) === false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('CODEMOD: fallback configuration', () => {
const config: StorybookConfig = {
stories: [\\"../stories/**/*.stories.@(js|jsx|ts|tsx)\\"],
addons: [\\"@storybook/addon-essentials\\", ({
name: \\"@storybook/addon-styling\\",
name: \\"@storybook/addon-styling-webpack\\",
options: {
rules: [{
Expand Down Expand Up @@ -88,7 +88,7 @@ describe('CODEMOD: fallback configuration', () => {
const config: StorybookConfig = {
stories: [\\"../stories/**/*.stories.@(js|jsx|ts|tsx)\\"],
addons: [\\"@storybook/addon-essentials\\", ({
name: \\"@storybook/addon-styling\\",
name: \\"@storybook/addon-styling-webpack\\",
options: {
rules: [{
Expand Down Expand Up @@ -175,7 +175,7 @@ describe('CODEMOD: fallback configuration', () => {
const config: StorybookConfig = {
stories: [\\"../stories/**/*.stories.@(js|jsx|ts|tsx)\\"],
addons: [\\"@storybook/addon-essentials\\", ({
name: \\"@storybook/addon-styling\\",
name: \\"@storybook/addon-styling-webpack\\",
options: {plugins: [new VanillaExtractPlugin(), new MiniCssExtractPlugin()],
rules: [{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { colors } from '@storybook/node-logger';
import prompts from 'prompts';
import dedent from 'dedent';

import { buildAddonConfig, buildImports } from '../../configure';
import { buildAddonConfig, buildImports, checkForMissingDependencies } from '../../configure';
import {
AddonStylingConfigurationStrategy,
CONFIGURATION_KEY_TO_NAME,
Expand All @@ -13,11 +13,18 @@ import {
} from '../../types';
import { addImports, createNode } from 'src/utils/ast.utils';
import { printWarning } from 'src/utils/output.utils';
import { ChangeSummary } from 'src/utils/strategy.utils';
import { askToInstallMissingDependencies } from '../../helpers';

export const customStrategy: AddonStylingConfigurationStrategy = {
name: CONFIGURATION_STRATEGY_KEYS.CUSTOM,
predicate: (_) => true,
main: async (mainConfig, meta) => {
const summary: ChangeSummary = {
changed: [],
nextSteps: [],
};

printWarning(
'💬 I need your help',
dedent`I didn't recognize any styling tools in your project that I know how to configure.
Expand Down Expand Up @@ -73,9 +80,31 @@ export const customStrategy: AddonStylingConfigurationStrategy = {
)}`,
);

return {
changed: [...configuredTools],
nextSteps: [],
};
summary.changed.push(...configuredTools);

const missingDependencies = await checkForMissingDependencies(meta.packageManager, configMap);

if (Object.keys(missingDependencies).length > 0) {
const installDependencies = await askToInstallMissingDependencies(missingDependencies);

if (installDependencies) {
await meta.packageManager.addDependencies(
{ installAsDevDependencies: true },
Object.entries(missingDependencies).map(([name, version]) =>
version === '' ? name : `${name}@${version}`,
),
);

summary.changed.push(`Installed dependencies for configuration`);
} else {
summary.nextSteps.push(
`Install ${colors.blue.bold(
Object.keys(missingDependencies).join(', '),
)} to complete the configuration`,
);
}
}

return summary;
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('CODEMOD: tailwind configuration', () => {
const config: StorybookConfig = {
stories: [\\"../stories/**/*.stories.@(js|jsx|ts|tsx)\\"],
addons: [\\"@storybook/addon-essentials\\", ({
name: \\"@storybook/addon-styling\\",
name: \\"@storybook/addon-styling-webpack\\",
options: {
rules: [{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { logger, colors } from '@storybook/node-logger';
import prompt from 'prompts';

import { addImports, createNode } from '../../../../utils/ast.utils';

import { AddonStylingConfigurationStrategy, CONFIGURATION_STRATEGY_KEYS } from '../../types';
import { buildAddonConfig, checkForMissingDependencies } from '../../configure';
import { ChangeSummary } from 'src/utils/strategy.utils';
import { printWarning } from 'src/utils/output.utils';
import { askToInstallMissingDependencies } from '../../helpers';

const projectHasTailwind = (deps: Record<string, string>) => Boolean(deps['tailwindcss']) && Boolean(deps['postcss']);

export const tailwindStrategy: AddonStylingConfigurationStrategy = {
name: CONFIGURATION_STRATEGY_KEYS.TAILWIND,
predicate: projectHasTailwind,
main: async (mainConfig, meta) => {
const summary: ChangeSummary = {
changed: [],
nextSteps: [],
};

const configMap = {
postcss: true,
sass: false,
less: false,
vanillaExtract: false,
cssModules: false,
};

const [addonConfigNode] = createNode(buildAddonConfig(configMap));

mainConfig.appendNodeToArray(['addons'], addonConfigNode.expression);

summary.changed.push(`Configured ${colors.blue.bold('PostCSS')} for ${colors.blue.bold('Webpack')}`);

const missingDependencies = await checkForMissingDependencies(meta.packageManager, configMap);

if (Object.keys(missingDependencies).length > 0) {
const installDependencies = await askToInstallMissingDependencies(missingDependencies);

if (installDependencies) {
await meta.packageManager.addDependencies(
{ installAsDevDependencies: true },
Object.entries(missingDependencies).map(([name, version]) =>
version === '' ? name : `${name}@${version}`,
),
);

summary.changed.push(`Installed dependencies for configuration`);
} else {
summary.nextSteps.push(
`Install ${colors.blue.bold(
Object.keys(missingDependencies).join(', '),
)} to complete the configuration`,
);
}
}

return summary;
},
preview: async (previewConfig, meta) => {
const { importPath } = await prompt(
{
type: 'text',
name: 'importPath',
message: 'Where is your global CSS file? (relative to the .storybook folder)',
initial: '../src/tailwind.css',
},
{ onCancel: () => process.exit(1) },
);

const globalCssImport = createNode(`import '${importPath}';`);

addImports(previewConfig._ast, globalCssImport);

return {
changed: [`Imported your global CSS into ${colors.blue.bold(previewConfig.fileName)}`],
nextSteps: [],
};
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('CODEMOD: vanilla-extract configuration', () => {
const config: StorybookConfig = {
stories: [\\"../stories/**/*.stories.@(js|jsx|ts|tsx)\\"],
addons: [\\"@storybook/addon-essentials\\", ({
name: \\"@storybook/addon-styling\\",
name: \\"@storybook/addon-styling-webpack\\",
options: {plugins: [new VanillaExtractPlugin(), new MiniCssExtractPlugin()],
rules: [{
test: /\\\\.css$/,
Expand Down
Loading

0 comments on commit 17ffdcd

Please sign in to comment.