@nx/storybook 23.2.0-beta.0 → 23.2.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -76,9 +76,9 @@ async function configurationGeneratorInternal(tree, rawSchema) {
76
76
  (0, util_functions_1.configureTsSolutionConfig)(tree, schema);
77
77
  (0, util_functions_1.updateLintConfig)(tree, schema);
78
78
  (0, util_functions_1.addBuildStorybookToCacheableOperations)(tree);
79
- (0, util_functions_1.addStorybookToNamedInputs)(tree);
79
+ (0, util_functions_1.addStorybookToNamedInputs)(tree, schema);
80
80
  if (!hasPlugin) {
81
- (0, util_functions_1.addStorybookToTargetDefaults)(tree);
81
+ (0, util_functions_1.addStorybookToTargetDefaults)(tree, schema);
82
82
  }
83
83
  let devDeps = {};
84
84
  if (!hasPlugin || schema.addExplicitTargets) {
@@ -96,6 +96,9 @@ async function configurationGeneratorInternal(tree, rawSchema) {
96
96
  else {
97
97
  devDeps['storybook'] = (0, utilities_1.getStorybookVersionToInstall)(tree);
98
98
  }
99
+ if (schema.interactionTests) {
100
+ devDeps['@storybook/test-runner'] = (0, versions_1.versions)(tree).testRunnerVersion;
101
+ }
99
102
  if (schema.tsConfiguration) {
100
103
  devDeps['ts-node'] = versions_1.tsNodeVersion;
101
104
  }
@@ -128,7 +131,6 @@ function normalizeSchema(tree, schema) {
128
131
  nxJson.useInferencePlugins !== false;
129
132
  const defaults = {
130
133
  interactionTests: true,
131
- linter: 'eslint',
132
134
  js: false,
133
135
  tsConfiguration: true,
134
136
  addPlugin,
@@ -17,8 +17,8 @@ export declare function configureTsSolutionConfig(tree: Tree, schema: StorybookC
17
17
  */
18
18
  export declare function updateLintConfig(tree: Tree, schema: StorybookConfigureSchema): void;
19
19
  export declare function normalizeSchema(schema: StorybookConfigureSchema): StorybookConfigureSchema;
20
- export declare function addStorybookToNamedInputs(tree: Tree): void;
21
- export declare function addStorybookToTargetDefaults(tree: Tree, setCache?: boolean): void;
20
+ export declare function addStorybookToNamedInputs(tree: Tree, schema: StorybookConfigureSchema): void;
21
+ export declare function addStorybookToTargetDefaults(tree: Tree, schema: StorybookConfigureSchema, setCache?: boolean): void;
22
22
  export declare function createProjectStorybookDir(tree: Tree, projectName: string, uiFramework: UiFramework, js: boolean, tsConfiguration: boolean, root: string, projectType: string, projectIsRootProjectInStandaloneWorkspace: boolean, interactionTests: boolean, mainDir?: string, isNextJs?: boolean, usesSwc?: boolean, usesVite?: boolean, viteConfigFilePath?: string, hasPlugin?: boolean, viteConfigFileName?: string, usesReactNative?: boolean): void;
23
23
  export declare function getTsConfigPath(tree: Tree, projectName: string, path?: string): string;
24
24
  export declare function addBuildStorybookToCacheableOperations(tree: Tree): void;
@@ -74,7 +74,7 @@ function addAngularStorybookTarget(tree, projectName, interactionTests) {
74
74
  options: {
75
75
  port: 4400,
76
76
  configDir: `${projectConfig.root}/.storybook`,
77
- browserTarget: `${projectName}:${ngBuildTarget ? 'build' : 'build-storybook'}`,
77
+ browserTarget: `${projectName}:${ngBuildTarget ?? 'build-storybook'}`,
78
78
  compodoc: false,
79
79
  },
80
80
  configurations: {
@@ -89,7 +89,7 @@ function addAngularStorybookTarget(tree, projectName, interactionTests) {
89
89
  options: {
90
90
  outputDir: (0, devkit_1.joinPathFragments)('dist/storybook', projectName),
91
91
  configDir: `${projectConfig.root}/.storybook`,
92
- browserTarget: `${projectName}:${ngBuildTarget ? 'build' : 'build-storybook'}`,
92
+ browserTarget: `${projectName}:${ngBuildTarget ?? 'build-storybook'}`,
93
93
  compodoc: false,
94
94
  },
95
95
  configurations: {
@@ -345,7 +345,6 @@ function updateLintConfig(tree, schema) {
345
345
  function normalizeSchema(schema) {
346
346
  const defaults = {
347
347
  configureCypress: true,
348
- linter: 'eslint',
349
348
  js: false,
350
349
  };
351
350
  return {
@@ -353,7 +352,7 @@ function normalizeSchema(schema) {
353
352
  ...schema,
354
353
  };
355
354
  }
356
- function addStorybookToNamedInputs(tree) {
355
+ function addStorybookToNamedInputs(tree, schema) {
357
356
  const nxJson = (0, devkit_1.readNxJson)(tree);
358
357
  if (nxJson.namedInputs) {
359
358
  const hasProductionFileset = !!nxJson.namedInputs?.production;
@@ -364,14 +363,15 @@ function addStorybookToNamedInputs(tree) {
364
363
  if (!nxJson.namedInputs.production.includes('!{projectRoot}/.storybook/**/*')) {
365
364
  nxJson.namedInputs.production.push('!{projectRoot}/.storybook/**/*');
366
365
  }
367
- if (!nxJson.namedInputs.production.includes('!{projectRoot}/tsconfig.storybook.json')) {
366
+ if (schema.uiFramework !== '@storybook/angular' &&
367
+ !nxJson.namedInputs.production.includes('!{projectRoot}/tsconfig.storybook.json')) {
368
368
  nxJson.namedInputs.production.push('!{projectRoot}/tsconfig.storybook.json');
369
369
  }
370
370
  }
371
371
  (0, devkit_1.updateNxJson)(tree, nxJson);
372
372
  }
373
373
  }
374
- function addStorybookToTargetDefaults(tree, setCache = true) {
374
+ function addStorybookToTargetDefaults(tree, schema, setCache = true) {
375
375
  const nxJson = (0, devkit_1.readNxJson)(tree) ?? {};
376
376
  const existing = (0, internal_2.findTargetDefault)(nxJson.targetDefaults, {
377
377
  target: 'build-storybook',
@@ -391,7 +391,8 @@ function addStorybookToTargetDefaults(tree, setCache = true) {
391
391
  const negatedIndex = inputs.indexOf('!{projectRoot}/.storybook/**/*');
392
392
  if (negatedIndex !== -1)
393
393
  inputs.splice(negatedIndex, 1);
394
- if (!inputs.includes('{projectRoot}/tsconfig.storybook.json')) {
394
+ if (schema.uiFramework !== '@storybook/angular' &&
395
+ !inputs.includes('{projectRoot}/tsconfig.storybook.json')) {
395
396
  inputs.push('{projectRoot}/tsconfig.storybook.json');
396
397
  }
397
398
  (0, internal_2.upsertTargetDefault)(tree, nxJson, {
@@ -1,10 +1,10 @@
1
- import { Linter, LinterType } from '@nx/eslint';
1
+ import { LinterType } from '@nx/js';
2
2
  import { UiFramework } from '../../utils/models';
3
3
 
4
4
  export interface StorybookConfigureSchema {
5
5
  project: string;
6
6
  uiFramework?: UiFramework;
7
- linter?: Linter | LinterType;
7
+ linter?: LinterType;
8
8
  js?: boolean;
9
9
  interactionTests?: boolean;
10
10
  tsConfiguration?: boolean;
@@ -28,8 +28,7 @@
28
28
  "linter": {
29
29
  "description": "The tool to use for running lint checks.",
30
30
  "type": "string",
31
- "enum": ["eslint", "none"],
32
- "default": "eslint"
31
+ "enum": ["eslint", "oxlint", "none"]
33
32
  },
34
33
  "js": {
35
34
  "type": "boolean",
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.migrate10Generator = migrate10Generator;
4
4
  const devkit_1 = require("@nx/devkit");
5
- const output_1 = require("nx/src/utils/output");
6
5
  const calling_storybook_cli_1 = require("./calling-storybook-cli");
7
6
  const path_1 = require("path");
8
7
  const fs_1 = require("fs");
@@ -11,7 +10,7 @@ async function migrate10Generator(tree, schema) {
11
10
  (0, assert_supported_storybook_version_1.assertSupportedStorybookVersion)(tree);
12
11
  const packageJson = (0, devkit_1.readJson)(tree, 'package.json');
13
12
  if (!(0, calling_storybook_cli_1.checkStorybookInstalled)(packageJson)) {
14
- output_1.output.error({
13
+ devkit_1.output.error({
15
14
  title: 'No Storybook packages installed',
16
15
  bodyLines: [
17
16
  `🚨 Nx did not find any Storybook packages installed in your workspace.`,
@@ -6,9 +6,9 @@ exports.handleMigrationResult = handleMigrationResult;
6
6
  exports.checkStorybookInstalled = checkStorybookInstalled;
7
7
  exports.logResult = logResult;
8
8
  const devkit_1 = require("@nx/devkit");
9
- const fileutils_1 = require("nx/src/utils/fileutils");
10
9
  const fs_1 = require("fs");
11
10
  const path_1 = require("path");
11
+ const internal_1 = require("@nx/devkit/internal");
12
12
  function onlyShowGuide(storybookProjects) {
13
13
  const packageManager = (0, devkit_1.detectPackageManager)();
14
14
  const pm = (0, devkit_1.getPackageManagerCommand)(packageManager);
@@ -65,7 +65,7 @@ function getAllStorybookInfo(tree) {
65
65
  return allStorybookDirs;
66
66
  }
67
67
  function handleMigrationResult(migrateResult, allStorybookProjects) {
68
- if ((0, fileutils_1.fileExists)((0, path_1.join)(devkit_1.workspaceRoot, 'migration-storybook.log')) &&
68
+ if ((0, internal_1.fileExists)((0, path_1.join)(devkit_1.workspaceRoot, 'migration-storybook.log')) &&
69
69
  Object.keys(migrateResult.successfulProjects)?.length) {
70
70
  const sbLogFile = (0, fs_1.readFileSync)((0, path_1.join)(devkit_1.workspaceRoot, 'migration-storybook.log'), 'utf-8');
71
71
  Object.keys(migrateResult.successfulProjects).forEach((projectName) => {
@@ -2,13 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.migrate8Generator = migrate8Generator;
4
4
  const devkit_1 = require("@nx/devkit");
5
- const output_1 = require("nx/src/utils/output");
6
5
  const calling_storybook_cli_1 = require("./calling-storybook-cli");
7
6
  const helper_functions_1 = require("./helper-functions");
8
7
  async function migrate8Generator(tree, schema) {
9
8
  const packageJson = (0, devkit_1.readJson)(tree, 'package.json');
10
9
  if (!(0, helper_functions_1.checkStorybookInstalled)(packageJson)) {
11
- output_1.output.error({
10
+ devkit_1.output.error({
12
11
  title: 'No Storybook packages installed',
13
12
  bodyLines: [
14
13
  `🚨 Nx did not find any Storybook packages installed in your workspace.`,
@@ -6,9 +6,9 @@ exports.handleMigrationResult = handleMigrationResult;
6
6
  exports.checkStorybookInstalled = checkStorybookInstalled;
7
7
  exports.logResult = logResult;
8
8
  const devkit_1 = require("@nx/devkit");
9
- const fileutils_1 = require("nx/src/utils/fileutils");
10
9
  const fs_1 = require("fs");
11
10
  const path_1 = require("path");
11
+ const internal_1 = require("@nx/devkit/internal");
12
12
  function onlyShowGuide(storybookProjects) {
13
13
  const packageManager = (0, devkit_1.detectPackageManager)();
14
14
  const pm = (0, devkit_1.getPackageManagerCommand)(packageManager);
@@ -66,7 +66,7 @@ function getAllStorybookInfo(tree) {
66
66
  return allStorybookDirs;
67
67
  }
68
68
  function handleMigrationResult(migrateResult, allStorybookProjects) {
69
- if ((0, fileutils_1.fileExists)((0, path_1.join)(devkit_1.workspaceRoot, 'migration-storybook.log')) &&
69
+ if ((0, internal_1.fileExists)((0, path_1.join)(devkit_1.workspaceRoot, 'migration-storybook.log')) &&
70
70
  Object.keys(migrateResult.successfulProjects)?.length) {
71
71
  const sbLogFile = (0, fs_1.readFileSync)((0, path_1.join)(devkit_1.workspaceRoot, 'migration-storybook.log'), 'utf-8');
72
72
  Object.keys(migrateResult.successfulProjects).forEach((projectName) => {
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.migrate9Generator = migrate9Generator;
4
4
  const devkit_1 = require("@nx/devkit");
5
- const output_1 = require("nx/src/utils/output");
6
5
  const calling_storybook_cli_1 = require("./calling-storybook-cli");
7
6
  const helper_functions_1 = require("./helper-functions");
8
7
  const assert_supported_storybook_version_1 = require("../../utils/assert-supported-storybook-version");
@@ -11,7 +10,7 @@ async function migrate9Generator(tree, schema) {
11
10
  schema.versionTag = schema.versionTag ?? 'latest';
12
11
  const packageJson = (0, devkit_1.readJson)(tree, 'package.json');
13
12
  if (!(0, helper_functions_1.checkStorybookInstalled)(packageJson)) {
14
- output_1.output.error({
13
+ devkit_1.output.error({
15
14
  title: 'No Storybook packages installed',
16
15
  bodyLines: [
17
16
  `🚨 Nx did not find any Storybook packages installed in your workspace.`,
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = changeStorybookTargets;
4
4
  const tslib_1 = require("tslib");
5
- const output_1 = require("nx/src/utils/output");
5
+ const devkit_1 = require("@nx/devkit");
6
6
  const migrate_9_1 = tslib_1.__importDefault(require("../../generators/migrate-9/migrate-9"));
7
7
  const utilities_1 = require("../../utils/utilities");
8
8
  async function changeStorybookTargets(tree) {
@@ -18,7 +18,7 @@ async function changeStorybookTargets(tree) {
18
18
  */
19
19
  return;
20
20
  }
21
- output_1.output.log({
21
+ devkit_1.output.log({
22
22
  title: 'Migrating Storybook to v9',
23
23
  bodyLines: [
24
24
  `🚀 This migration will update your Storybook configuration to v9.`,
@@ -2,10 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = migrateToStorybook10;
4
4
  const tslib_1 = require("tslib");
5
- const output_1 = require("nx/src/utils/output");
5
+ const devkit_1 = require("@nx/devkit");
6
6
  const migrate_10_1 = tslib_1.__importDefault(require("../../generators/migrate-10/migrate-10"));
7
7
  async function migrateToStorybook10(tree) {
8
- output_1.output.log({
8
+ devkit_1.output.log({
9
9
  title: 'Migrating Storybook to v10',
10
10
  bodyLines: [
11
11
  `🚀 This migration will update your Storybook configuration to v10.`,
@@ -5,11 +5,10 @@ const internal_1 = require("@nx/devkit/internal");
5
5
  const devkit_1 = require("@nx/devkit");
6
6
  const path_1 = require("path");
7
7
  const fs_1 = require("fs");
8
- const cache_directory_1 = require("nx/src/utils/cache-directory");
9
8
  const js_1 = require("@nx/js");
10
- const file_hasher_1 = require("nx/src/hasher/file-hasher");
11
9
  const tsquery_1 = require("@phenomnomnominal/tsquery");
12
10
  const internal_2 = require("@nx/js/internal");
11
+ const utilities_1 = require("../utils/utilities");
13
12
  /**
14
13
  * @deprecated The 'createDependencies' function is now a no-op. This functionality is included in 'createNodesV2'.
15
14
  */
@@ -18,12 +17,29 @@ const createDependencies = () => {
18
17
  };
19
18
  exports.createDependencies = createDependencies;
20
19
  const storybookConfigGlob = '**/.storybook/main.{js,ts,mjs,mts,cjs,cts}';
20
+ // Vite-builder frameworks we infer the addon target for. The addon itself only
21
+ // checks that the resolved builder is Vite, so this list is conservative rather
22
+ // than exhaustive. `@storybook/sveltekit` uses the Vite builder despite the name.
23
+ const VITE_BUILDER_FRAMEWORKS = new Set([
24
+ '@storybook/angular-vite',
25
+ '@storybook/react-vite',
26
+ '@storybook/react-native-web-vite',
27
+ '@storybook/tanstack-react',
28
+ '@storybook/vue-vite',
29
+ '@storybook/vue3-vite',
30
+ '@storybook/svelte-vite',
31
+ '@storybook/sveltekit',
32
+ '@storybook/web-components-vite',
33
+ '@storybook/html-vite',
34
+ '@storybook/preact-vite',
35
+ '@storybook/nextjs-vite',
36
+ ]);
21
37
  exports.createNodes = [
22
38
  storybookConfigGlob,
23
39
  async (configFilePaths, options, context) => {
24
40
  const normalizedOptions = normalizeOptions(options);
25
- const optionsHash = (0, file_hasher_1.hashObject)(normalizedOptions);
26
- const cachePath = (0, path_1.join)(cache_directory_1.workspaceDataDirectory, `storybook-${optionsHash}.hash`);
41
+ const optionsHash = (0, internal_1.hashObject)(normalizedOptions);
42
+ const cachePath = (0, path_1.join)(internal_1.workspaceDataDirectory, `storybook-${optionsHash}.hash`);
27
43
  const targetsCache = new internal_1.PluginCache(cachePath);
28
44
  const packageManager = (0, devkit_1.detectPackageManager)(context.workspaceRoot);
29
45
  const pmc = (0, devkit_1.getPackageManagerCommand)(packageManager);
@@ -71,9 +87,9 @@ function getProjectRootFromConfigPath(configFilePath) {
71
87
  return projectRoot;
72
88
  }
73
89
  async function createNodesInternal(configFilePath, options, context, targetsCache, pmc, projectRoot, hash) {
74
- const projectName = buildProjectName(projectRoot, context.workspaceRoot);
90
+ const { projectName, angularBuildTarget } = getProjectMetadata(projectRoot, context.workspaceRoot);
75
91
  if (!targetsCache.has(hash)) {
76
- targetsCache.set(hash, await buildStorybookTargets(configFilePath, projectRoot, options, context, projectName, pmc));
92
+ targetsCache.set(hash, await buildStorybookTargets(configFilePath, projectRoot, options, context, projectName, angularBuildTarget, pmc));
77
93
  }
78
94
  const result = {
79
95
  projects: {
@@ -85,35 +101,51 @@ async function createNodesInternal(configFilePath, options, context, targetsCach
85
101
  };
86
102
  return result;
87
103
  }
88
- async function buildStorybookTargets(configFilePath, projectRoot, options, context, projectName, pmc) {
104
+ async function buildStorybookTargets(configFilePath, projectRoot, options, context, projectName, angularBuildTarget, pmc) {
89
105
  const buildOutputs = getOutputs();
90
106
  const namedInputs = (0, internal_1.getNamedInputs)(projectRoot, context);
91
107
  // First attempt to do a very fast lookup for the framework
92
108
  // If that fails, the framework might be inherited, so do a very heavyweight lookup
93
- const storybookFramework = (await getStorybookFramework(configFilePath, context)) ||
94
- (await getStorybookFullyResolvedFramework(configFilePath, context));
109
+ // The slow path returns whatever the config holds, which for the common
110
+ // `getAbsolutePath('@storybook/react-vite')` shape is a resolved path.
111
+ const storybookFramework = normalizePackageName((await getStorybookFramework(configFilePath, context)) ||
112
+ (await getStorybookFullyResolvedFramework(configFilePath, context)));
95
113
  const frameworkIsAngular = storybookFramework === '@storybook/angular';
96
114
  if (frameworkIsAngular && !projectName) {
97
115
  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.`);
98
116
  }
99
117
  const targets = {};
100
- targets[options.buildStorybookTargetName] = buildTarget(namedInputs, buildOutputs, projectRoot, frameworkIsAngular, projectName, configFilePath);
101
- targets[options.serveStorybookTargetName] = serveTarget(projectRoot, frameworkIsAngular, projectName, configFilePath);
102
- if (isStorybookTestRunnerInstalled()) {
118
+ // Resolution alone is not enough: the addon lives in the root package.json, so
119
+ // it resolves for every project. Registration in this project's own config is
120
+ // what makes it ours. Resolve first - it is a stat walk, while reading the
121
+ // registration executes the user's config.
122
+ const usesVitestAddon = VITE_BUILDER_FRAMEWORKS.has(storybookFramework) &&
123
+ isInstalled('@storybook/addon-vitest', context.workspaceRoot, projectRoot) &&
124
+ (await registersVitestAddon(configFilePath, context));
125
+ const hasTestRunner = isInstalled('@storybook/test-runner', context.workspaceRoot, projectRoot);
126
+ // Falls back to the storybook build target, whose name is configurable, so
127
+ // resolve it from the options rather than assuming the default.
128
+ const browserTarget = `${projectName}:${angularBuildTarget ?? options.buildStorybookTargetName}`;
129
+ targets[options.buildStorybookTargetName] = buildTarget(namedInputs, buildOutputs, projectRoot, frameworkIsAngular, browserTarget, configFilePath, hasTestRunner);
130
+ targets[options.serveStorybookTargetName] = serveTarget(projectRoot, frameworkIsAngular, browserTarget, configFilePath);
131
+ if (usesVitestAddon) {
132
+ targets[options.testStorybookTargetName] = vitestTestTarget(projectRoot);
133
+ }
134
+ else if (hasTestRunner) {
103
135
  targets[options.testStorybookTargetName] = testTarget(projectRoot);
104
136
  }
105
137
  targets[options.staticStorybookTargetName] = serveStaticTarget(options, projectRoot);
106
138
  (0, internal_2.addBuildAndWatchDepsTargets)(context.workspaceRoot, projectRoot, targets, options, pmc);
107
139
  return targets;
108
140
  }
109
- function buildTarget(namedInputs, outputs, projectRoot, frameworkIsAngular, projectName, configFilePath) {
141
+ function buildTarget(namedInputs, outputs, projectRoot, frameworkIsAngular, browserTarget, configFilePath, hasTestRunner) {
110
142
  let targetConfig;
111
143
  if (frameworkIsAngular) {
112
144
  targetConfig = {
113
145
  executor: '@storybook/angular:build-storybook',
114
146
  options: {
115
147
  configDir: `${(0, path_1.dirname)(configFilePath)}`,
116
- browserTarget: `${projectName}:build-storybook`,
148
+ browserTarget,
117
149
  compodoc: false,
118
150
  outputDir: (0, devkit_1.joinPathFragments)(projectRoot, 'storybook-static'),
119
151
  },
@@ -127,9 +159,7 @@ function buildTarget(namedInputs, outputs, projectRoot, frameworkIsAngular, proj
127
159
  externalDependencies: [
128
160
  'storybook',
129
161
  '@storybook/angular',
130
- isStorybookTestRunnerInstalled()
131
- ? '@storybook/test-runner'
132
- : undefined,
162
+ hasTestRunner ? '@storybook/test-runner' : undefined,
133
163
  ].filter(Boolean),
134
164
  },
135
165
  ],
@@ -148,9 +178,7 @@ function buildTarget(namedInputs, outputs, projectRoot, frameworkIsAngular, proj
148
178
  {
149
179
  externalDependencies: [
150
180
  'storybook',
151
- isStorybookTestRunnerInstalled()
152
- ? '@storybook/test-runner'
153
- : undefined,
181
+ hasTestRunner ? '@storybook/test-runner' : undefined,
154
182
  ].filter(Boolean),
155
183
  },
156
184
  ],
@@ -158,15 +186,18 @@ function buildTarget(namedInputs, outputs, projectRoot, frameworkIsAngular, proj
158
186
  }
159
187
  return targetConfig;
160
188
  }
161
- function serveTarget(projectRoot, frameworkIsAngular, projectName, configFilePath) {
189
+ function serveTarget(projectRoot, frameworkIsAngular, browserTarget, configFilePath) {
162
190
  if (frameworkIsAngular) {
163
191
  return {
164
192
  continuous: true,
165
193
  executor: '@storybook/angular:start-storybook',
166
194
  options: {
167
195
  configDir: `${(0, path_1.dirname)(configFilePath)}`,
168
- browserTarget: `${projectName}:build-storybook`,
196
+ browserTarget,
169
197
  compodoc: false,
198
+ // The builder defaults to 9009, but test-storybook looks for 6006. Match
199
+ // the port the non-Angular `storybook dev` target serves on.
200
+ port: 6006,
170
201
  },
171
202
  };
172
203
  }
@@ -190,6 +221,24 @@ function testTarget(projectRoot) {
190
221
  };
191
222
  return targetConfig;
192
223
  }
224
+ function vitestTestTarget(projectRoot) {
225
+ const targetConfig = {
226
+ // `--passWithNoTests` so the target is green before any stories exist. It does
227
+ // not mask a missing `storybook` project: that is a startup error either way.
228
+ command: `vitest run --project=storybook --passWithNoTests`,
229
+ options: { cwd: projectRoot },
230
+ inputs: [
231
+ {
232
+ externalDependencies: [
233
+ 'storybook',
234
+ '@storybook/addon-vitest',
235
+ 'vitest',
236
+ ],
237
+ },
238
+ ],
239
+ };
240
+ return targetConfig;
241
+ }
193
242
  function serveStaticTarget(options, projectRoot) {
194
243
  const targetConfig = {
195
244
  dependsOn: [`${options.buildStorybookTargetName}`],
@@ -210,10 +259,11 @@ async function getStorybookFramework(configFilePath, context) {
210
259
  return parseFrameworkName(mainTsJs);
211
260
  }
212
261
  const storybookConfigImportPackage = (0, tsquery_1.query)(importDeclarations, 'StringLiteral')?.[0];
213
- if (storybookConfigImportPackage?.getText() === `'@storybook/core-common'`) {
262
+ const frameworkPackage = stringLiteralValue(storybookConfigImportPackage);
263
+ if (frameworkPackage === '@storybook/core-common') {
214
264
  return parseFrameworkName(mainTsJs);
215
265
  }
216
- return storybookConfigImportPackage?.getText();
266
+ return frameworkPackage;
217
267
  }
218
268
  function parseFrameworkName(mainTsJs) {
219
269
  const frameworkPropertyAssignment = (0, tsquery_1.query)(mainTsJs, `PropertyAssignment:has(Identifier:has([text="framework"]))`)?.[0];
@@ -226,9 +276,13 @@ function parseFrameworkName(mainTsJs) {
226
276
  });
227
277
  if (!namePropertyAssignment) {
228
278
  const storybookConfigImportPackage = (0, tsquery_1.query)(frameworkPropertyAssignment, 'StringLiteral')?.[0];
229
- return storybookConfigImportPackage?.getText();
279
+ return stringLiteralValue(storybookConfigImportPackage);
230
280
  }
231
- return (0, tsquery_1.query)(namePropertyAssignment, `StringLiteral`)?.[0]?.getText();
281
+ return stringLiteralValue((0, tsquery_1.query)(namePropertyAssignment, `StringLiteral`)?.[0]);
282
+ }
283
+ function stringLiteralValue(node) {
284
+ const value = node?.getText();
285
+ return value?.slice(1, -1);
232
286
  }
233
287
  async function getStorybookFullyResolvedFramework(configFilePath, context) {
234
288
  const resolvedPath = (0, path_1.join)(context.workspaceRoot, configFilePath);
@@ -276,26 +330,73 @@ function normalizeOptions(options) {
276
330
  watchDepsTargetName: options.watchDepsTargetName ?? 'watch-deps',
277
331
  };
278
332
  }
279
- function buildProjectName(projectRoot, workspaceRoot) {
333
+ function getProjectMetadata(projectRoot, workspaceRoot) {
280
334
  const packageJsonPath = (0, path_1.join)(workspaceRoot, projectRoot, 'package.json');
281
335
  const projectJsonPath = (0, path_1.join)(workspaceRoot, projectRoot, 'project.json');
282
- let name;
336
+ let projectJsonName;
337
+ let projectJsonTargets = {};
338
+ let packageJsonName;
339
+ let packageJsonTargets = {};
283
340
  if ((0, fs_1.existsSync)(projectJsonPath)) {
284
341
  const projectJson = (0, devkit_1.parseJson)((0, fs_1.readFileSync)(projectJsonPath, 'utf-8'));
285
- name = projectJson.name;
342
+ projectJsonName = projectJson.name;
343
+ projectJsonTargets = projectJson.targets ?? {};
286
344
  }
287
- else if ((0, fs_1.existsSync)(packageJsonPath)) {
345
+ if ((0, fs_1.existsSync)(packageJsonPath)) {
288
346
  const packageJson = (0, devkit_1.parseJson)((0, fs_1.readFileSync)(packageJsonPath, 'utf-8'));
289
- name = packageJson.name;
347
+ packageJsonName = packageJson.name;
348
+ packageJsonTargets = packageJson.nx?.targets ?? {};
290
349
  }
291
- return name;
350
+ // Mirror how Nx itself names a project: project.json, then package.json, then
351
+ // the directory. Both files can carry targets, and project.json wins.
352
+ const targets = { ...packageJsonTargets, ...projectJsonTargets };
353
+ return {
354
+ projectName: projectJsonName ?? packageJsonName ?? nameFromRoot(projectRoot),
355
+ angularBuildTarget: (0, utilities_1.findStorybookAndBuildTargetsAndCompiler)(targets).ngBuildTarget,
356
+ };
357
+ }
358
+ // Same derivation as Nx's `toProjectName`. The workspace root has no directory
359
+ // segment to name it after, so leave it undefined and let the caller complain.
360
+ function nameFromRoot(projectRoot) {
361
+ const segment = projectRoot.split(/[\/\\]/g).pop();
362
+ return !segment || segment === '.' ? undefined : segment.toLowerCase();
363
+ }
364
+ async function registersVitestAddon(configFilePath, context) {
365
+ try {
366
+ const { addons } = await (0, internal_1.loadConfigFile)((0, path_1.join)(context.workspaceRoot, configFilePath));
367
+ return addons?.some(isVitestAddon) ?? false;
368
+ }
369
+ catch {
370
+ return false;
371
+ }
372
+ }
373
+ function isVitestAddon(addon) {
374
+ const addonName = typeof addon === 'string'
375
+ ? addon
376
+ : typeof addon === 'object' && addon !== null && 'name' in addon
377
+ ? addon.name
378
+ : undefined;
379
+ return (typeof addonName === 'string' &&
380
+ normalizePackageName(addonName) === '@storybook/addon-vitest');
381
+ }
382
+ function normalizePackageName(packageName) {
383
+ return packageName
384
+ ?.replace(/\\/g, '/')
385
+ .split('/')
386
+ .filter(Boolean)
387
+ .slice(-2)
388
+ .join('/');
292
389
  }
293
- function isStorybookTestRunnerInstalled() {
390
+ // Resolved from the project first so a runner declared in the project's own
391
+ // package.json counts, not just one hoisted to the workspace root.
392
+ function isInstalled(packageName, workspaceRoot, projectRoot) {
294
393
  try {
295
- require.resolve('@storybook/test-runner');
394
+ require.resolve(packageName, {
395
+ paths: [(0, path_1.join)(workspaceRoot, projectRoot), workspaceRoot],
396
+ });
296
397
  return true;
297
398
  }
298
- catch (e) {
399
+ catch {
299
400
  return false;
300
401
  }
301
402
  }
@@ -67,24 +67,21 @@ function findStorybookAndBuildTargetsAndCompiler(targets) {
67
67
  '@angular-devkit/build-angular:application',
68
68
  '@angular-devkit/build-angular:browser',
69
69
  '@angular-devkit/build-angular:browser-esbuild',
70
+ '@angular/build:application',
70
71
  ];
71
72
  for (const target in targets) {
72
73
  if (arrayOfBuilders.includes(targets[target].executor)) {
73
74
  if (targets[target].executor === '@angular-devkit/build-angular:browser' ||
74
75
  targets[target].executor ===
75
76
  '@angular-devkit/build-angular:browser-esbuild' ||
76
- targets[target].executor === '@angular-devkit/build-angular:application') {
77
+ targets[target].executor ===
78
+ '@angular-devkit/build-angular:application' ||
79
+ targets[target].executor === '@angular/build:application') {
77
80
  /**
78
- * Not looking for '@nx/angular:ng-packagr-lite' or any other
79
- * @nx/angular:* executors.
80
- * Only looking for '@angular-devkit/build-angular:browser'
81
- * because the '@nx/angular:ng-packagr-lite' executor
82
- * (and maybe the other custom executors)
83
- * does not support styles and extra options, so the user
84
- * will be forced to switch to build-storybook to add extra options.
85
- *
86
- * So we might as well use the build-storybook by default to
87
- * avoid any errors.
81
+ * Only the official Angular application/browser builders. The
82
+ * '@nx/angular:*' executors (ng-packagr-lite and friends) do not
83
+ * support styles and extra options, so a project on one of those is
84
+ * better served by build-storybook, which can carry those options.
88
85
  */
89
86
  returnObject.ngBuildTarget = target;
90
87
  }
@@ -8,8 +8,10 @@ export declare const storybookVersion = "^10.5.0";
8
8
  export declare const reactVersion = "^18.2.0";
9
9
  export declare const viteVersion = "^6.0.0";
10
10
  export declare const coreJsVersion = "^3.36.1";
11
+ export declare const testRunnerVersion = "^0.24.0";
11
12
  type StorybookVersions = {
12
13
  storybookVersion: string;
14
+ testRunnerVersion: string;
13
15
  };
14
16
  export declare function versions(tree: Tree): StorybookVersions;
15
17
  export declare function storybookMajorVersion(tree?: Tree): number | undefined;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.coreJsVersion = exports.viteVersion = exports.reactVersion = exports.storybookVersion = exports.minSupportedStorybookVersion = exports.tsLibVersion = exports.tsNodeVersion = exports.litVersion = exports.nxVersion = void 0;
3
+ exports.testRunnerVersion = exports.coreJsVersion = exports.viteVersion = exports.reactVersion = exports.storybookVersion = exports.minSupportedStorybookVersion = exports.tsLibVersion = exports.tsNodeVersion = exports.litVersion = exports.nxVersion = void 0;
4
4
  exports.versions = versions;
5
5
  exports.storybookMajorVersion = storybookMajorVersion;
6
6
  exports.getInstalledStorybookVersion = getInstalledStorybookVersion;
@@ -17,20 +17,41 @@ exports.storybookVersion = '^10.5.0';
17
17
  exports.reactVersion = '^18.2.0';
18
18
  exports.viteVersion = '^6.0.0';
19
19
  exports.coreJsVersion = '^3.36.1';
20
+ exports.testRunnerVersion = '^0.24.0';
20
21
  const latestVersions = {
21
22
  storybookVersion: exports.storybookVersion,
23
+ testRunnerVersion: exports.testRunnerVersion,
22
24
  };
23
25
  const versionMap = {
24
- 8: { storybookVersion: '^8.6.11' },
25
- 9: { storybookVersion: '^9.0.5' },
26
- 10: { storybookVersion: '^10.5.0' },
26
+ 8: { storybookVersion: '^8.6.11', testRunnerVersion: '^0.21.0' },
27
+ 9: { storybookVersion: '^9.0.5', testRunnerVersion: '^0.23.0' },
28
+ 10: { storybookVersion: '^10.5.0', testRunnerVersion: exports.testRunnerVersion },
27
29
  };
28
30
  function versions(tree) {
29
31
  const installedStorybookMajor = storybookMajorVersion(tree);
30
32
  if (installedStorybookMajor === undefined) {
31
33
  return latestVersions;
32
34
  }
33
- return (versionMap[installedStorybookMajor] ?? latestVersions);
35
+ const compat = versionMap[installedStorybookMajor] ?? latestVersions;
36
+ // 0.20 is the first runner line to declare a storybook peer, and it peers ^8.2.
37
+ // Ranges that cannot reach 8.2 need 0.17, the earliest line built against 8.
38
+ if (installedStorybookMajor === 8 &&
39
+ !storybookVersionIntersects(tree, '>=8.2.0')) {
40
+ return { ...compat, testRunnerVersion: '^0.17.0' };
41
+ }
42
+ return compat;
43
+ }
44
+ function storybookVersionIntersects(tree, range) {
45
+ const installedVersion = getInstalledStorybookVersion(tree);
46
+ if (!installedVersion) {
47
+ return false;
48
+ }
49
+ try {
50
+ return (0, semver_1.intersects)(installedVersion, range);
51
+ }
52
+ catch {
53
+ return false;
54
+ }
34
55
  }
35
56
  function storybookMajorVersion(tree) {
36
57
  const installedVersion = getInstalledStorybookVersion(tree);
package/migrations.json CHANGED
@@ -435,6 +435,19 @@
435
435
  "alwaysAddToPackageJson": false
436
436
  }
437
437
  }
438
+ },
439
+ "23.2.0-test-runner": {
440
+ "version": "23.2.0-beta.8",
441
+ "requires": {
442
+ "storybook": ">=10.0.0 <11.0.0",
443
+ "@storybook/test-runner": "<0.24.0"
444
+ },
445
+ "packages": {
446
+ "@storybook/test-runner": {
447
+ "version": "^0.24.0",
448
+ "alwaysAddToPackageJson": false
449
+ }
450
+ }
438
451
  }
439
452
  }
440
453
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/storybook",
3
- "version": "23.2.0-beta.0",
3
+ "version": "23.2.0-beta.10",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "files": [
@@ -92,18 +92,18 @@
92
92
  "@phenomnomnominal/tsquery": "~6.2.0",
93
93
  "semver": "^7.6.3",
94
94
  "tslib": "^2.3.0",
95
- "@nx/devkit": "23.2.0-beta.0",
96
- "@nx/cypress": "23.2.0-beta.0",
97
- "@nx/js": "23.2.0-beta.0",
98
- "@nx/eslint": "23.2.0-beta.0"
95
+ "@nx/devkit": "23.2.0-beta.10",
96
+ "@nx/cypress": "23.2.0-beta.10",
97
+ "@nx/js": "23.2.0-beta.10",
98
+ "@nx/eslint": "23.2.0-beta.10"
99
99
  },
100
100
  "devDependencies": {
101
101
  "storybook": "9.0.6",
102
- "nx": "23.2.0-beta.0"
102
+ "nx": "23.2.0-beta.10"
103
103
  },
104
104
  "peerDependencies": {
105
105
  "storybook": ">=8.0.0 <11.0.0",
106
- "@nx/web": "23.2.0-beta.0"
106
+ "@nx/web": "23.2.0-beta.10"
107
107
  },
108
108
  "peerDependenciesMeta": {
109
109
  "@nx/web": {