@nx/storybook
Version:
302 lines (301 loc) • 12.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createNodesV2 = exports.createNodes = exports.createDependencies = void 0;
const internal_1 = require("@nx/devkit/internal");
const devkit_1 = require("@nx/devkit");
const path_1 = require("path");
const fs_1 = require("fs");
const cache_directory_1 = require("nx/src/utils/cache-directory");
const js_1 = require("@nx/js");
const file_hasher_1 = require("nx/src/hasher/file-hasher");
const tsquery_1 = require("@phenomnomnominal/tsquery");
const internal_2 = require("@nx/js/internal");
/**
* @deprecated The 'createDependencies' function is now a no-op. This functionality is included in 'createNodesV2'.
*/
const createDependencies = () => {
return [];
};
exports.createDependencies = createDependencies;
const storybookConfigGlob = '**/.storybook/main.{js,ts,mjs,mts,cjs,cts}';
exports.createNodes = [
storybookConfigGlob,
async (configFilePaths, options, context) => {
const normalizedOptions = normalizeOptions(options);
const optionsHash = (0, file_hasher_1.hashObject)(normalizedOptions);
const cachePath = (0, path_1.join)(cache_directory_1.workspaceDataDirectory, `storybook-${optionsHash}.hash`);
const targetsCache = new internal_1.PluginCache(cachePath);
const packageManager = (0, devkit_1.detectPackageManager)(context.workspaceRoot);
const pmc = (0, devkit_1.getPackageManagerCommand)(packageManager);
const lockFileName = (0, js_1.getLockFileName)(packageManager);
try {
const { entries, preErrors } = await filterStorybookConfigs(configFilePaths, context);
const projectHashes = await (0, internal_1.calculateHashesForCreateNodes)(entries.map((e) => e.projectRoot), normalizedOptions, context, entries.map(() => [lockFileName]));
let results = [];
let nodeErrors = [];
try {
results = await (0, devkit_1.createNodesFromFiles)((configFile, _, ctx, idx) => createNodesInternal(configFile, normalizedOptions, ctx, targetsCache, pmc, entries[idx].projectRoot, projectHashes[idx]), entries.map((e) => e.configFile), normalizedOptions, context);
}
catch (e) {
if (e instanceof devkit_1.AggregateCreateNodesError) {
results = e.partialResults ?? [];
nodeErrors = e.errors;
}
else {
throw e;
}
}
const allErrors = [...preErrors, ...nodeErrors];
if (allErrors.length > 0) {
throw new devkit_1.AggregateCreateNodesError(allErrors, results);
}
return results;
}
finally {
targetsCache.writeToDisk();
}
},
];
exports.createNodesV2 = exports.createNodes;
function getProjectRootFromConfigPath(configFilePath) {
let projectRoot = '';
if (configFilePath.includes('/.storybook')) {
projectRoot = (0, path_1.dirname)(configFilePath).replace('/.storybook', '');
}
else {
projectRoot = (0, path_1.dirname)(configFilePath).replace('.storybook', '');
}
if (projectRoot === '') {
projectRoot = '.';
}
return projectRoot;
}
async function createNodesInternal(configFilePath, options, context, targetsCache, pmc, projectRoot, hash) {
const projectName = buildProjectName(projectRoot, context.workspaceRoot);
if (!targetsCache.has(hash)) {
targetsCache.set(hash, await buildStorybookTargets(configFilePath, projectRoot, options, context, projectName, pmc));
}
const result = {
projects: {
[projectRoot]: {
root: projectRoot,
targets: targetsCache.get(hash),
},
},
};
return result;
}
async function buildStorybookTargets(configFilePath, projectRoot, options, context, projectName, pmc) {
const buildOutputs = getOutputs();
const namedInputs = (0, internal_1.getNamedInputs)(projectRoot, context);
// First attempt to do a very fast lookup for the framework
// If that fails, the framework might be inherited, so do a very heavyweight lookup
const storybookFramework = (await getStorybookFramework(configFilePath, context)) ||
(await getStorybookFullyResolvedFramework(configFilePath, context));
const frameworkIsAngular = storybookFramework === '@storybook/angular';
if (frameworkIsAngular && !projectName) {
throw new Error(`Could not find a name for the project at '${projectRoot}'. Please make sure that the project has a package.json or project.json file with name specified.`);
}
const targets = {};
targets[options.buildStorybookTargetName] = buildTarget(namedInputs, buildOutputs, projectRoot, frameworkIsAngular, projectName, configFilePath);
targets[options.serveStorybookTargetName] = serveTarget(projectRoot, frameworkIsAngular, projectName, configFilePath);
if (isStorybookTestRunnerInstalled()) {
targets[options.testStorybookTargetName] = testTarget(projectRoot);
}
targets[options.staticStorybookTargetName] = serveStaticTarget(options, projectRoot);
(0, internal_2.addBuildAndWatchDepsTargets)(context.workspaceRoot, projectRoot, targets, options, pmc);
return targets;
}
function buildTarget(namedInputs, outputs, projectRoot, frameworkIsAngular, projectName, configFilePath) {
let targetConfig;
if (frameworkIsAngular) {
targetConfig = {
executor: '@storybook/angular:build-storybook',
options: {
configDir: `${(0, path_1.dirname)(configFilePath)}`,
browserTarget: `${projectName}:build-storybook`,
compodoc: false,
outputDir: (0, devkit_1.joinPathFragments)(projectRoot, 'storybook-static'),
},
cache: true,
outputs,
inputs: [
...('production' in namedInputs
? ['production', '^production']
: ['default', '^default']),
{
externalDependencies: [
'storybook',
'@storybook/angular',
isStorybookTestRunnerInstalled()
? '@storybook/test-runner'
: undefined,
].filter(Boolean),
},
],
};
}
else {
targetConfig = {
command: `storybook build`,
options: { cwd: projectRoot },
cache: true,
outputs,
inputs: [
...('production' in namedInputs
? ['production', '^production']
: ['default', '^default']),
{
externalDependencies: [
'storybook',
isStorybookTestRunnerInstalled()
? '@storybook/test-runner'
: undefined,
].filter(Boolean),
},
],
};
}
return targetConfig;
}
function serveTarget(projectRoot, frameworkIsAngular, projectName, configFilePath) {
if (frameworkIsAngular) {
return {
continuous: true,
executor: '@storybook/angular:start-storybook',
options: {
configDir: `${(0, path_1.dirname)(configFilePath)}`,
browserTarget: `${projectName}:build-storybook`,
compodoc: false,
},
};
}
else {
return {
continuous: true,
command: `storybook dev`,
options: { cwd: projectRoot },
};
}
}
function testTarget(projectRoot) {
const targetConfig = {
command: `test-storybook`,
options: { cwd: projectRoot },
inputs: [
{
externalDependencies: ['storybook', '@storybook/test-runner'],
},
],
};
return targetConfig;
}
function serveStaticTarget(options, projectRoot) {
const targetConfig = {
dependsOn: [`${options.buildStorybookTargetName}`],
continuous: true,
executor: '@nx/web:file-server',
options: {
buildTarget: `${options.buildStorybookTargetName}`,
staticFilePath: (0, devkit_1.joinPathFragments)(projectRoot, 'storybook-static'),
},
};
return targetConfig;
}
async function getStorybookFramework(configFilePath, context) {
const resolvedPath = (0, path_1.join)(context.workspaceRoot, configFilePath);
const mainTsJs = (0, fs_1.readFileSync)(resolvedPath, 'utf-8');
const importDeclarations = (0, tsquery_1.query)(mainTsJs, 'ImportDeclaration:has(ImportSpecifier:has([text="StorybookConfig"]))')?.[0];
if (!importDeclarations) {
return parseFrameworkName(mainTsJs);
}
const storybookConfigImportPackage = (0, tsquery_1.query)(importDeclarations, 'StringLiteral')?.[0];
if (storybookConfigImportPackage?.getText() === `'@storybook/core-common'`) {
return parseFrameworkName(mainTsJs);
}
return storybookConfigImportPackage?.getText();
}
function parseFrameworkName(mainTsJs) {
const frameworkPropertyAssignment = (0, tsquery_1.query)(mainTsJs, `PropertyAssignment:has(Identifier:has([text="framework"]))`)?.[0];
if (!frameworkPropertyAssignment) {
return undefined;
}
const propertyAssignments = (0, tsquery_1.query)(frameworkPropertyAssignment, `PropertyAssignment:has(Identifier:has([text="name"]))`);
const namePropertyAssignment = propertyAssignments?.find((expression) => {
return expression.getText().startsWith('name');
});
if (!namePropertyAssignment) {
const storybookConfigImportPackage = (0, tsquery_1.query)(frameworkPropertyAssignment, 'StringLiteral')?.[0];
return storybookConfigImportPackage?.getText();
}
return (0, tsquery_1.query)(namePropertyAssignment, `StringLiteral`)?.[0]?.getText();
}
async function getStorybookFullyResolvedFramework(configFilePath, context) {
const resolvedPath = (0, path_1.join)(context.workspaceRoot, configFilePath);
const { framework } = await (0, internal_1.loadConfigFile)(resolvedPath);
return typeof framework === 'string' ? framework : framework.name;
}
function getOutputs() {
const outputs = [
`{projectRoot}/storybook-static`,
`{options.output-dir}`,
`{options.outputDir}`,
`{options.o}`,
];
return outputs;
}
async function filterStorybookConfigs(configFiles, context) {
const preErrors = [];
const candidates = await Promise.all(configFiles.map(async (configFile) => {
try {
const projectRoot = getProjectRootFromConfigPath(configFile);
const siblingFiles = (0, fs_1.readdirSync)((0, path_1.join)(context.workspaceRoot, projectRoot));
if (!siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json')) {
return null;
}
return { configFile, projectRoot };
}
catch (e) {
preErrors.push([configFile, e]);
return null;
}
}));
return {
entries: candidates.filter((c) => c !== null),
preErrors,
};
}
function normalizeOptions(options) {
return {
buildStorybookTargetName: options.buildStorybookTargetName ?? 'build-storybook',
serveStorybookTargetName: options.serveStorybookTargetName ?? 'storybook',
testStorybookTargetName: options.testStorybookTargetName ?? 'test-storybook',
staticStorybookTargetName: options.staticStorybookTargetName ?? 'static-storybook',
buildDepsTargetName: options.buildDepsTargetName ?? 'build-deps',
watchDepsTargetName: options.watchDepsTargetName ?? 'watch-deps',
};
}
function buildProjectName(projectRoot, workspaceRoot) {
const packageJsonPath = (0, path_1.join)(workspaceRoot, projectRoot, 'package.json');
const projectJsonPath = (0, path_1.join)(workspaceRoot, projectRoot, 'project.json');
let name;
if ((0, fs_1.existsSync)(projectJsonPath)) {
const projectJson = (0, devkit_1.parseJson)((0, fs_1.readFileSync)(projectJsonPath, 'utf-8'));
name = projectJson.name;
}
else if ((0, fs_1.existsSync)(packageJsonPath)) {
const packageJson = (0, devkit_1.parseJson)((0, fs_1.readFileSync)(packageJsonPath, 'utf-8'));
name = packageJson.name;
}
return name;
}
function isStorybookTestRunnerInstalled() {
try {
require.resolve('@storybook/test-runner');
return true;
}
catch (e) {
return false;
}
}