@nx/storybook
Version:
269 lines (268 loc) • 11.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createNodes = exports.createNodesV2 = exports.createDependencies = void 0;
const devkit_1 = require("@nx/devkit");
const path_1 = require("path");
const get_named_inputs_1 = require("@nx/devkit/src/utils/get-named-inputs");
const fs_1 = require("fs");
const calculate_hash_for_create_nodes_1 = require("@nx/devkit/src/utils/calculate-hash-for-create-nodes");
const cache_directory_1 = require("nx/src/utils/cache-directory");
const js_1 = require("@nx/js");
const config_utils_1 = require("@nx/devkit/src/utils/config-utils");
const file_hasher_1 = require("nx/src/hasher/file-hasher");
const tsquery_1 = require("@phenomnomnominal/tsquery");
function readTargetsCache(cachePath) {
return (0, fs_1.existsSync)(cachePath) ? (0, devkit_1.readJsonFile)(cachePath) : {};
}
function writeTargetsToCache(cachePath, results) {
(0, devkit_1.writeJsonFile)(cachePath, results);
}
/**
* @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.createNodesV2 = [
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 = readTargetsCache(cachePath);
try {
return await (0, devkit_1.createNodesFromFiles)((configFile, _, context) => createNodesInternal(configFile, normalizedOptions, context, targetsCache), configFilePaths, normalizedOptions, context);
}
finally {
writeTargetsToCache(cachePath, targetsCache);
}
},
];
exports.createNodes = [
storybookConfigGlob,
(configFilePath, options, context) => {
devkit_1.logger.warn('`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.');
return createNodesInternal(configFilePath, normalizeOptions(options), context, {});
},
];
async function createNodesInternal(configFilePath, options, context, targetsCache) {
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 = '.';
}
// Do not create a project if package.json and project.json isn't there.
const siblingFiles = (0, fs_1.readdirSync)((0, path_1.join)(context.workspaceRoot, projectRoot));
if (!siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json')) {
return {};
}
const hash = await (0, calculate_hash_for_create_nodes_1.calculateHashForCreateNodes)(projectRoot, options, context, [(0, js_1.getLockFileName)((0, devkit_1.detectPackageManager)(context.workspaceRoot))]);
const projectName = buildProjectName(projectRoot, context.workspaceRoot);
targetsCache[hash] ??= await buildStorybookTargets(configFilePath, projectRoot, options, context, projectName);
const result = {
projects: {
[projectRoot]: {
root: projectRoot,
targets: targetsCache[hash],
},
},
};
return result;
}
async function buildStorybookTargets(configFilePath, projectRoot, options, context, projectName) {
const buildOutputs = getOutputs();
const namedInputs = (0, get_named_inputs_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);
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 = tsquery_1.tsquery.query(mainTsJs, 'ImportDeclaration:has(ImportSpecifier:has([text="StorybookConfig"]))')?.[0];
if (!importDeclarations) {
return parseFrameworkName(mainTsJs);
}
const storybookConfigImportPackage = tsquery_1.tsquery.query(importDeclarations, 'StringLiteral')?.[0];
if (storybookConfigImportPackage?.getText() === `'@storybook/core-common'`) {
return parseFrameworkName(mainTsJs);
}
return storybookConfigImportPackage?.getText();
}
function parseFrameworkName(mainTsJs) {
const frameworkPropertyAssignment = tsquery_1.tsquery.query(mainTsJs, `PropertyAssignment:has(Identifier:has([text="framework"]))`)?.[0];
if (!frameworkPropertyAssignment) {
return undefined;
}
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) {
const storybookConfigImportPackage = tsquery_1.tsquery.query(frameworkPropertyAssignment, 'StringLiteral')?.[0];
return storybookConfigImportPackage?.getText();
}
return tsquery_1.tsquery.query(namePropertyAssignment, `StringLiteral`)?.[0]?.getText();
}
async function getStorybookFullyResolvedFramework(configFilePath, context) {
const resolvedPath = (0, path_1.join)(context.workspaceRoot, configFilePath);
const { framework } = await (0, config_utils_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;
}
function normalizeOptions(options) {
return {
buildStorybookTargetName: options.buildStorybookTargetName ?? 'build-storybook',
serveStorybookTargetName: options.serveStorybookTargetName ?? 'storybook',
testStorybookTargetName: options.testStorybookTargetName ?? 'test-storybook',
staticStorybookTargetName: options.staticStorybookTargetName ?? 'static-storybook',
};
}
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;
}
}