@nx/storybook
Version:
446 lines (445 loc) • 22.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.onlyShowGuide = onlyShowGuide;
exports.writeFile = writeFile;
exports.removePathResolvesFromNextConfig = removePathResolvesFromNextConfig;
exports.removeViteTsConfigPathsPlugin = removeViteTsConfigPathsPlugin;
exports.addViteConfigFilePathInFrameworkOptions = addViteConfigFilePathInFrameworkOptions;
exports.normalizeViteConfigFilePathWithTree = normalizeViteConfigFilePathWithTree;
exports.removeTypecastFromMainTs = removeTypecastFromMainTs;
exports.removeUiFrameworkFromProjectJson = removeUiFrameworkFromProjectJson;
exports.changeCoreCommonImportToFramework = changeCoreCommonImportToFramework;
exports.getAllStorybookInfo = getAllStorybookInfo;
exports.prepareFiles = prepareFiles;
exports.handleMigrationResult = handleMigrationResult;
exports.checkStorybookInstalled = checkStorybookInstalled;
exports.checkWebComponentsInstalled = checkWebComponentsInstalled;
exports.afterMigration = afterMigration;
exports.logResult = logResult;
const devkit_1 = require("@nx/devkit");
const executor_options_utils_1 = require("@nx/devkit/src/generators/executor-options-utils");
const tsquery_1 = require("@phenomnomnominal/tsquery");
const fs = require("fs");
const fileutils_1 = require("nx/src/utils/fileutils");
const fs_1 = require("fs");
const path_1 = require("path");
function onlyShowGuide(storybookProjects) {
const pm = (0, devkit_1.getPackageManagerCommand)();
devkit_1.output.log({
title: 'Storybook 7 Migration Guide',
bodyLines: [
`You can run the following commands manually to upgrade your Storybook projects to Storybook 7:`,
``,
`1. Call the Storybook upgrade script:`,
`${pm.exec} storybook@latest upgrade`,
``,
`2. Call the Nx generator to prepare your files for migration:`,
`nx g @nx/storybook:migrate-7 --onlyPrepare`,
``,
`3. Call the Storybook automigrate scripts:`,
`Run the following commands for each Storybook project:`,
...Object.entries(storybookProjects).map(([_projectName, storybookProjectInfo]) => {
return `${pm.exec} storybook@latest automigrate --config-dir ${storybookProjectInfo.configDir} --renderer ${storybookProjectInfo.uiFramework}`;
}),
``,
`4. Call the Nx generator to finish the migration:`,
`nx g @nx/storybook:migrate-7 --afterMigration`,
],
});
}
function writeFile(file) {
if (file?.path && file?.content) {
fs.writeFileSync(file.path, file.content);
}
}
function removePathResolvesFromNextConfig(tree, mainJsTsPath) {
let mainJsTs = tree.read(mainJsTsPath, 'utf-8');
const hasNextConfig = tsquery_1.tsquery.query(mainJsTs, `PropertyAssignment:has(Identifier:has([name="nextConfigPath"]))`);
const nextConfigPathAssignment = hasNextConfig?.find((propertyAssignment) => {
return propertyAssignment.getText().startsWith('nextConfigPath');
});
if (!nextConfigPathAssignment) {
// No nextConfigPath found, nothing to do
return;
}
const pathResolve = tsquery_1.tsquery.query(nextConfigPathAssignment, `CallExpression:has(PropertyAccessExpression:has([name="path"]):has([name="resolve"]))`)?.[0];
if (pathResolve) {
const getStringLiteral = tsquery_1.tsquery.query(pathResolve, 'StringLiteral')?.[0];
if (getStringLiteral) {
mainJsTs = (0, devkit_1.applyChangesToString)(mainJsTs, [
{
type: devkit_1.ChangeType.Delete,
start: pathResolve.getStart(),
length: pathResolve.getText().length,
},
{
type: devkit_1.ChangeType.Insert,
index: pathResolve.getStart(),
text: getStringLiteral.getText(),
},
]);
return {
path: mainJsTsPath,
content: mainJsTs,
};
}
}
}
function removeViteTsConfigPathsPlugin(tree, mainJsTsPath) {
let mainJsTs = tree.read(mainJsTsPath, 'utf-8');
const { vitePluginVariableName, importExpression } = getViteTsConfigPathsNameAndImport(mainJsTs);
const viteTsConfigPathsPluginParent = tsquery_1.tsquery.query(mainJsTs, `CallExpression:has(Identifier[name="${vitePluginVariableName}"])`);
const viteTsConfigPathsPlugin = viteTsConfigPathsPluginParent?.find((callExpression) => {
return callExpression.getText().startsWith(vitePluginVariableName);
});
if (viteTsConfigPathsPlugin && importExpression) {
mainJsTs = (0, devkit_1.applyChangesToString)(mainJsTs, [
{
type: devkit_1.ChangeType.Delete,
start: viteTsConfigPathsPlugin.getStart(),
length: mainJsTs[viteTsConfigPathsPlugin.getEnd()] === ','
? viteTsConfigPathsPlugin.getText().length + 1
: viteTsConfigPathsPlugin.getText().length,
},
{
type: devkit_1.ChangeType.Delete,
start: importExpression.getStart(),
length: importExpression.getText().length,
},
]);
tree.write(mainJsTsPath, mainJsTs);
removePluginsArrayIfEmpty(tree, mainJsTsPath);
}
}
function getViteTsConfigPathsNameAndImport(mainJsTs) {
const requireVariableStatement = tsquery_1.tsquery.query(mainJsTs, `VariableStatement:has(CallExpression:has(Identifier[name="require"]))`);
let vitePluginVariableName;
let importExpression;
if (requireVariableStatement?.length) {
importExpression = requireVariableStatement.find((statement) => {
const requireCallExpression = tsquery_1.tsquery.query(statement, 'CallExpression:has(Identifier[name="require"])');
return requireCallExpression?.[0]
?.getText()
?.includes('vite-tsconfig-paths');
});
if (importExpression) {
vitePluginVariableName = tsquery_1.tsquery
.query(importExpression, 'Identifier')?.[0]
?.getText();
}
}
else {
const importDeclarations = tsquery_1.tsquery.query(mainJsTs, 'ImportDeclaration');
importExpression = importDeclarations?.find((statement) => {
const stringLiteral = tsquery_1.tsquery.query(statement, 'StringLiteral');
return stringLiteral?.[0]?.getText()?.includes('vite-tsconfig-paths');
});
if (importExpression) {
vitePluginVariableName = tsquery_1.tsquery
.query(importExpression, 'Identifier')?.[0]
?.getText();
}
}
return {
vitePluginVariableName,
importExpression,
};
}
function removePluginsArrayIfEmpty(tree, mainJsTsPath) {
let mainJsTs = tree.read(mainJsTsPath, 'utf-8');
const viteFinalMethodDeclaration = tsquery_1.tsquery.query(mainJsTs, `MethodDeclaration:has([name="viteFinal"])`)?.[0];
if (!viteFinalMethodDeclaration) {
return;
}
const pluginsPropertyAssignment = tsquery_1.tsquery.query(viteFinalMethodDeclaration, `PropertyAssignment:has(Identifier:has([name="plugins"]))`)?.[0];
if (!pluginsPropertyAssignment) {
return;
}
const pluginsArrayLiteralExpression = tsquery_1.tsquery.query(pluginsPropertyAssignment, `ArrayLiteralExpression`)?.[0];
if (pluginsArrayLiteralExpression?.getText()?.replace(/\s/g, '') === '[]') {
mainJsTs = (0, devkit_1.applyChangesToString)(mainJsTs, [
{
type: devkit_1.ChangeType.Delete,
start: pluginsPropertyAssignment.getStart(),
length: mainJsTs[pluginsPropertyAssignment.getEnd()] === ','
? pluginsPropertyAssignment.getText().length + 1
: pluginsPropertyAssignment.getText().length,
},
]);
tree.write(mainJsTsPath, mainJsTs);
}
}
function addViteConfigFilePathInFrameworkOptions(tree, mainJsTsPath, viteConfigPath) {
let mainJsTs = tree.read(mainJsTsPath, 'utf-8');
const viteFramework = tsquery_1.tsquery.query(mainJsTs, `PropertyAssignment:has(Identifier:has([name="framework"])):has(StringLiteral:has([text="@storybook/react-vite"]))`)?.[0] ??
tsquery_1.tsquery.query(mainJsTs, `PropertyAssignment:has(Identifier:has([name="framework"])):has(StringLiteral:has([text="@storybook/web-components-vite"]))`)?.[0] ??
tsquery_1.tsquery.query(mainJsTs, `PropertyAssignment:has(Identifier:has([name="framework"])):has(StringLiteral:has([text="@storybook/svelte-vite"]))`)?.[0] ??
tsquery_1.tsquery.query(mainJsTs, `PropertyAssignment:has(Identifier:has([name="framework"])):has(StringLiteral:has([text="@storybook/vue-vite"]))`)?.[0] ??
tsquery_1.tsquery.query(mainJsTs, `PropertyAssignment:has(Identifier:has([name="framework"])):has(StringLiteral:has([text="@storybook/vue3-vite"]))`)?.[0];
if (viteFramework) {
const optionsPropertyAssignments = tsquery_1.tsquery.query(viteFramework, `PropertyAssignment:has(Identifier:has([name="options"]))`);
const frameworkOptionsPropertyAssignment = optionsPropertyAssignments?.find((expression) => {
return expression.getText().startsWith('options');
});
if (frameworkOptionsPropertyAssignment) {
const objectLiteralExpression = tsquery_1.tsquery.query(frameworkOptionsPropertyAssignment, `ObjectLiteralExpression`)?.[0];
mainJsTs = (0, devkit_1.applyChangesToString)(mainJsTs, [
{
type: devkit_1.ChangeType.Insert,
index: objectLiteralExpression.getStart() + 1,
text: `
builder: {
viteConfigPath: '${viteConfigPath}',
},
`,
},
]);
tree.write(mainJsTsPath, mainJsTs);
}
else {
const objectLiteralExpression = tsquery_1.tsquery.query(viteFramework, `ObjectLiteralExpression`)?.[0];
if (!objectLiteralExpression) {
return;
}
mainJsTs = (0, devkit_1.applyChangesToString)(mainJsTs, [
{
type: devkit_1.ChangeType.Insert,
index: objectLiteralExpression.getStart() + 1,
text: ` options: {
builder: {
viteConfigPath: '${viteConfigPath}',
},
},
`,
},
]);
tree.write(mainJsTsPath, mainJsTs);
}
}
}
function normalizeViteConfigFilePathWithTree(tree, projectRoot, configFile) {
return configFile && tree.exists(configFile)
? configFile
: tree.exists((0, devkit_1.joinPathFragments)(`${projectRoot}/vite.config.ts`))
? (0, devkit_1.joinPathFragments)(`${projectRoot}/vite.config.ts`)
: tree.exists((0, devkit_1.joinPathFragments)(`${projectRoot}/vite.config.js`))
? (0, devkit_1.joinPathFragments)(`${projectRoot}/vite.config.js`)
: undefined;
}
function removeTypecastFromMainTs(tree, mainTsPath) {
let mainTs = tree.read(mainTsPath, 'utf-8');
mainTs = mainTs.replace(/as StorybookConfig/g, '');
return {
path: mainTsPath,
content: mainTs,
};
}
function removeUiFrameworkFromProjectJson(tree) {
(0, executor_options_utils_1.forEachExecutorOptions)(tree, '@nx/storybook:build', (options, projectName, targetName) => {
if (projectName && options?.['uiFramework']) {
const projectConfiguration = (0, devkit_1.readProjectConfiguration)(tree, projectName);
delete projectConfiguration.targets[targetName].options['uiFramework'];
(0, devkit_1.updateProjectConfiguration)(tree, projectName, projectConfiguration);
}
});
(0, executor_options_utils_1.forEachExecutorOptions)(tree, '@nx/storybook:storybook', (options, projectName, targetName) => {
if (projectName && options?.['uiFramework']) {
const projectConfiguration = (0, devkit_1.readProjectConfiguration)(tree, projectName);
delete projectConfiguration.targets[targetName].options['uiFramework'];
(0, devkit_1.updateProjectConfiguration)(tree, projectName, projectConfiguration);
}
});
}
function changeCoreCommonImportToFramework(tree, mainTsPath) {
let mainTs = tree.read(mainTsPath, 'utf-8');
const importDeclarations = tsquery_1.tsquery.query(mainTs, 'ImportDeclaration:has(ImportSpecifier:has([text="StorybookConfig"]))')?.[0];
if (!importDeclarations) {
return;
}
const storybookConfigImportPackage = tsquery_1.tsquery.query(importDeclarations, 'StringLiteral')?.[0];
if (storybookConfigImportPackage?.getText() === `'@storybook/core-common'`) {
const frameworkPropertyAssignment = tsquery_1.tsquery.query(mainTs, `PropertyAssignment:has(Identifier:has([text="framework"]))`)?.[0];
if (!frameworkPropertyAssignment) {
return;
}
const propertyAssignments = tsquery_1.tsquery.query(frameworkPropertyAssignment, `PropertyAssignment:has(Identifier:has([text="name"]))`);
const namePropertyAssignment = propertyAssignments?.find((expression) => {
return expression.getText().startsWith('name');
});
if (!namePropertyAssignment) {
return;
}
const frameworkName = tsquery_1.tsquery.query(namePropertyAssignment, `StringLiteral`)?.[0];
if (frameworkName) {
mainTs = (0, devkit_1.applyChangesToString)(mainTs, [
{
type: devkit_1.ChangeType.Delete,
start: storybookConfigImportPackage.getStart(),
length: storybookConfigImportPackage.getWidth(),
},
{
type: devkit_1.ChangeType.Insert,
index: storybookConfigImportPackage.getStart(),
text: frameworkName.getText(),
},
]);
tree.write(mainTsPath, mainTs);
}
}
}
function getAllStorybookInfo(tree) {
const allStorybookDirs = {};
(0, executor_options_utils_1.forEachExecutorOptions)(tree, '@nx/storybook:build', (options, projectName) => {
if (projectName && options?.['configDir']) {
const projectConfiguration = (0, devkit_1.readProjectConfiguration)(tree, projectName);
allStorybookDirs[projectName] = {
configDir: options?.['configDir'],
uiFramework: options?.['uiFramework'],
viteConfigFilePath: normalizeViteConfigFilePathWithTree(tree, projectConfiguration.root, projectConfiguration.targets?.build?.options?.configFile),
};
}
});
(0, executor_options_utils_1.forEachExecutorOptions)(tree, '@storybook/angular:build-storybook', (options, projectName) => {
if (projectName && options?.['configDir']) {
allStorybookDirs[projectName] = {
configDir: options?.['configDir'],
uiFramework: '@storybook/angular',
};
}
});
return allStorybookDirs;
}
function prepareFiles(tree, allStorybookProjects) {
devkit_1.output.log({
title: `Preparing Storybook files.`,
bodyLines: [
`Nx will make some adjustments to the Storybook configuration files of your workspace`,
`so that the Storybook automigration scripts can run successfully.`,
`The adjustments are:`,
` - Remove the "as StorybookConfig" typecast from the main.ts files, if any`,
` - Remove the "path.resolve" calls from the Next.js Storybook configuration, if any`,
],
color: 'blue',
});
Object.entries(allStorybookProjects).forEach(([projectName, storybookProjectInfo]) => {
const mainJsTsPath = tree.exists(`${storybookProjectInfo.configDir}/main.js`)
? `${storybookProjectInfo.configDir}/main.js`
: tree.exists(`${storybookProjectInfo.configDir}/main.ts`)
? `${storybookProjectInfo.configDir}/main.ts`
: undefined;
if (!mainJsTsPath) {
devkit_1.output.error({
title: `Failed to prepare Storybook files for ${projectName}.`,
bodyLines: [
`Could not find main.js or main.ts in ${storybookProjectInfo.configDir}`,
`Skipping project ${projectName}.`,
],
});
}
if (mainJsTsPath.endsWith('.ts')) {
writeFile(removeTypecastFromMainTs(tree, mainJsTsPath));
}
writeFile(removePathResolvesFromNextConfig(tree, mainJsTsPath));
});
devkit_1.output.log({
title: `Files prepared successfully!`,
bodyLines: [`Nx prepared your files successfully.`],
color: 'green',
});
}
function handleMigrationResult(migrateResult, allStorybookProjects) {
if ((0, fileutils_1.fileExists)((0, path_1.join)(devkit_1.workspaceRoot, 'migration-storybook.log')) &&
Object.keys(migrateResult.successfulProjects)?.length) {
const sbLogFile = (0, fs_1.readFileSync)((0, path_1.join)(devkit_1.workspaceRoot, 'migration-storybook.log'), 'utf-8');
Object.keys(migrateResult.successfulProjects).forEach((projectName) => {
if (sbLogFile.includes(`The migration failed to update your ${allStorybookProjects[projectName].configDir}`)) {
migrateResult.failedProjects[projectName] =
migrateResult.successfulProjects[projectName];
delete migrateResult.successfulProjects[projectName];
}
});
}
if (Object.keys(allStorybookProjects)?.length ===
Object.keys(migrateResult.successfulProjects)?.length ||
Object.keys(migrateResult.failedProjects)?.length === 0) {
devkit_1.output.log({
title: `Storybook configuration migrated.`,
bodyLines: [
`☑️ The automigrate command was successful.`,
`All your projects were migrated successfully.`,
],
color: 'green',
});
}
else {
if (Object.keys(migrateResult.failedProjects).length) {
if (Object.keys(migrateResult.failedProjects).length) {
devkit_1.output.log({
title: `Storybook configuration migrated.`,
bodyLines: [
`☑️ The automigrate command was successful.`,
`The following projects were migrated successfully:`,
...Object.keys(migrateResult.successfulProjects).map((project) => ` - ${project}`),
],
color: 'green',
});
}
devkit_1.output.log({
title: `Failed migrations.`,
bodyLines: [
`There were some projects that were not migrated successfully.`,
`⚠️ The following projects were not migrated successfully:`,
...Object.keys(migrateResult.failedProjects).map((project) => ` - ${project}`),
`You can run the following commands to migrate them manually:`,
...Object.entries(migrateResult.failedProjects).map(([_project, command]) => `- ${command}`),
],
color: 'red',
});
}
}
return migrateResult;
}
function checkStorybookInstalled(packageJson) {
return ((packageJson.dependencies['@storybook/core-server'] ||
packageJson.devDependencies['@storybook/core-server']) &&
(packageJson.dependencies['@nx/storybook'] ||
packageJson.devDependencies['@nx/storybook']));
}
function checkWebComponentsInstalled(packageJson) {
return (packageJson.dependencies['@storybook/web-components'] ||
packageJson.devDependencies['@storybook/web-components-vite'] ||
packageJson.dependencies['@storybook/web-components-vite'] ||
packageJson.devDependencies['@storybook/web-components-webpack5'] ||
packageJson.dependencies['@storybook/web-components-webpack5']);
}
function afterMigration(tree, allStorybookProjects) {
Object.entries(allStorybookProjects).forEach(async ([_projectName, storybookProjectInfo]) => {
const mainJsTsPath = tree.exists(`${storybookProjectInfo.configDir}/main.js`)
? `${storybookProjectInfo.configDir}/main.js`
: tree.exists(`${storybookProjectInfo.configDir}/main.ts`)
? `${storybookProjectInfo.configDir}/main.ts`
: undefined;
removeViteTsConfigPathsPlugin(tree, mainJsTsPath);
if (storybookProjectInfo.viteConfigFilePath) {
addViteConfigFilePathInFrameworkOptions(tree, mainJsTsPath, storybookProjectInfo.viteConfigFilePath);
}
changeCoreCommonImportToFramework(tree, mainJsTsPath);
});
}
function logResult(tree, migrationSummary) {
devkit_1.output.log({
title: `Migration complete!`,
bodyLines: [
`🎉 Your Storybook configuration has been migrated to Storybook 7.0.0!`,
`📖 You can see a summary of the tasks that were performed in the storybook-migration-summary.md file in the root of your workspace.`,
],
color: 'green',
});
(0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, 'files'), '.', {
tmpl: '',
successfulProjects: Object.entries(migrationSummary?.successfulProjects)?.map(([_projectName, command]) => command),
failedProjects: Object.entries(migrationSummary?.failedProjects)?.map(([_projectName, command]) => command),
hasFailedProjects: Object.keys(migrationSummary?.failedProjects)?.length > 0,
hasSuccessfulProjects: Object.keys(migrationSummary?.successfulProjects)?.length > 0,
});
}