2022-10-17 10:08:05 -04:00

119 lines
3.8 KiB
TypeScript

import path from 'path';
import fs from 'fs';
import { sync as spawnSync } from 'cross-spawn';
import { getStorybookInfo } from '@storybook/core-common';
import { readConfig, writeConfig } from '@storybook/csf-tools';
import { commandLog } from './helpers';
import {
JsPackageManagerFactory,
useNpmWarning,
type PackageManagerName,
} from './js-package-manager';
const logger = console;
const LEGACY_CONFIGS = ['addons', 'config', 'presets'];
const postinstallAddon = async (addonName: string, isOfficialAddon: boolean) => {
let skipMsg = null;
if (!isOfficialAddon) {
skipMsg = 'unofficial addon';
} else if (!fs.existsSync('.storybook')) {
skipMsg = 'no .storybook config';
} else {
skipMsg = 'no codmods found';
LEGACY_CONFIGS.forEach((config) => {
try {
const codemod = require.resolve(
// @ts-expect-error (it is broken)
`${getPackageName(addonName, isOfficialAddon)}/postinstall/${config}.js`
);
commandLog(`Running postinstall script for ${addonName}`)();
let configFile = path.join('.storybook', `${config}.ts`);
if (!fs.existsSync(configFile)) {
configFile = path.join('.storybook', `${config}.js`);
if (!fs.existsSync(configFile)) {
fs.writeFileSync(configFile, '', 'utf8');
}
}
spawnSync('npx', ['jscodeshift', '-t', codemod, configFile], {
stdio: 'inherit',
shell: true,
});
skipMsg = null;
} catch (err) {
// resolve failed, skip
}
});
}
if (skipMsg) {
commandLog(`Skipping postinstall for ${addonName}, ${skipMsg}`)();
}
};
const getVersionSpecifier = (addon: string) => {
const groups = /^(...*)@(.*)$/.exec(addon);
return groups ? [groups[1], groups[2]] : [addon, undefined];
};
/**
* Install the given addon package and add it to main.js
*
* Usage:
* - sb add @storybook/addon-docs
* - sb add @storybook/addon-interactions@7.0.1
*
* If there is no version specifier and it's a storybook addon,
* it will try to use the version specifier matching your current
* Storybook install version.
*/
export async function add(
addon: string,
options: { useNpm: boolean; packageManager: PackageManagerName; skipPostinstall: boolean }
) {
const { useNpm, packageManager: pkgMgr } = options;
if (useNpm) {
useNpmWarning();
}
const packageManager = JsPackageManagerFactory.getPackageManager({ useNpm, force: pkgMgr });
const packageJson = packageManager.retrievePackageJson();
const [addonName, versionSpecifier] = getVersionSpecifier(addon);
const { mainConfig, version: storybookVersion } = getStorybookInfo(packageJson);
if (!mainConfig) {
logger.error('Unable to find storybook main.js config');
return;
}
const main = await readConfig(mainConfig);
const addons = main.getFieldValue(['addons']);
if (addons && !Array.isArray(addons)) {
logger.error('Expected addons array in main.js config');
}
logger.log(`Verifying ${addonName}`);
const latestVersion = packageManager.latestVersion(addonName);
if (!latestVersion) {
logger.error(`Unknown addon ${addonName}`);
}
// add to package.json
const isStorybookAddon = addonName.startsWith('@storybook/');
const version = versionSpecifier || (isStorybookAddon ? storybookVersion : latestVersion);
const addonWithVersion = `${addonName}@${version}`;
logger.log(`Installing ${addonWithVersion}`);
packageManager.addDependencies({ installAsDevDependencies: true }, [addonWithVersion]);
// add to main.js
logger.log(`Adding '${addon}' to main.js addons field.`);
const updatedAddons = [...(addons || []), addonName];
main.setFieldValue(['addons'], updatedAddons);
await writeConfig(main);
if (!options.skipPostinstall) {
await postinstallAddon(addon, isStorybookAddon);
}
}