Commit 620c5861009 for woocommerce

commit 620c58610096d937fc3a4668c41a8922dab07336
Author: Brandon Kraft <public@brandonkraft.com>
Date:   Wed Jul 8 15:52:06 2026 -0500

    Fix the Storybook nightly build and report failures to Slack (#65651)

    * Fix the Storybook nightly build and report failures to Slack

    The storybook-pages.yml nightly has been failing since several source
    changes broke the build without any CI or alerting to surface it:

    - PR 65254 removed the build cascade, so the Blocks Storybook dist and
      the package/admin assets the aggregate build copies were never built.
      Restore a scoped dependency cascade in the tools/storybook build
      script.
    - PR 65254 moved the admin webpack output from client/admin/build to
      plugins/woocommerce/assets/client/admin. Update the app CSS copy path
      in the Storybook webpack config.
    - PR 63543 added a type-only tsconfig path mapping for
      dinero.js/currencies pointing at a .d.ts file, which the Blocks
      Storybook config converted into a webpack alias webpack cannot parse.
      Skip declaration-file targets when generating aliases.
    - PR 65500 removed packages/js/product-editor but left its stories
      glob, preview CSS links, and sidebar entry behind. Remove them.

    Also add a Slack failure report step to the workflow, using the
    existing slack-test-report util and nightly-checks channel routing, so
    the next silent breakage is not silent.

    * Adopt bundle-from-source approach for Storybook per review

    Replaces the restored build cascade with the approach from
    fix/wooplug-6798-storybook-stale-admin-css-path:

    - Activate the wc-source export condition in the Storybook webpack
      configs so the monorepo JS packages bundle from TypeScript source,
      mirroring the admin and blocks builds from PR 65254, instead of
      rebuilding package outputs via a cascade.
    - Import the package and admin app stylesheets from SCSS source in a
      new preview.js, replacing the CopyWebpackPlugin patterns that copied
      pre-built build-style/ and admin app CSS (and with them the stale
      admin path this PR previously fixed in place).
    - Add a babel-loader rule in the Blocks Storybook config so package
      TypeScript source under node_modules resolves and transpiles.
    - Drop the hardcoded wcAdminPackages alias list; the export condition
      covers all current and future packages.

    The one remaining pre-step in the build script is the Blocks Storybook
    build itself, which the aggregate Storybook composes as a static dir
    and iframe; that is a composition input, not a package build cascade.

    Verified locally: blocks Storybook builds cold (no blocks webpack
    output present, matching CI), and the full aggregate build completes
    with package styles bundled into chunk CSS including RTL variants.

    * Build the Blocks Storybook before the aggregate Storybook build

    The aggregate Storybook composes the Blocks Storybook as a static dir
    and copies its iframe.html, so the blocks storybook dist must exist
    before the main build runs. This is a composition input rather than a
    package build cascade. Also updates the lockfile for the new
    base-styles dependency.

    * Fix prettier lint and drop dead storybook-rtl script

    - Reformat the babel-loader `include` regex in the Blocks Storybook
      webpack config so Prettier passes (CI Lint - `block-library`).
    - Remove the `storybook-rtl` script. The previous commit replaced the
      pre-built `*-rtl.css` CopyWebpackPlugin patterns with SCSS source
      imports in `preview.js`, so the `USE_RTL_STYLE` env var the script
      set is no longer consulted. The script also invoked a non-existent
      `pnpm storybook` command, so it was already broken.

    * Restore Storybook RTL preview via in-place rtlcss transform

    The source-based build serves CSS as content-hashed webpack chunks, so
    the old previewHead link-swap between style.css and style-rtl.css can no
    longer target a predictable filename. The inherited RTL plugin still ran
    but only emitted unused -rtl.css siblings while the document linked the
    LTR chunk, so RTL preview rendered nothing.

    Per review feedback, keep RTL on the existing rtlcss toolchain instead
    of re-adding a link toggle:

    - Add an inPlace option to WebpackRTLPlugin that updateAssets the
      compiled CSS to RTL in place rather than emitting a -rtl.css sibling.
      Default behavior (sibling emission) is unchanged for every other
      consumer.
    - In the Storybook webpack config, swap the inherited RTL plugin for an
      inPlace instance when USE_RTL_STYLE is set, so the existing chunk
      links render RTL with no new dependency.
    - Fix the storybook-rtl script, which has invoked a non-existent
      'storybook' script since 2023, to launch the real dev server.

    Verified locally with a differential build: LTR output is unchanged
    (12 chunks, 12 -rtl.css siblings); RTL output drops the siblings to 0
    and flips directional properties in place (a representative chunk went
    from 9 left / 4 right to 4 left / 9 right).

    * Fix Storybook build: resolve WordPress components CSS without exports gate

    The aggregate Storybook build failed under Node 24 with
    ERR_PACKAGE_PATH_NOT_EXPORTED for the WordPress components
    build-style/style.css. The package maps build-style as a deprecated
    trailing-slash folder export, which Node 24 no longer honors, so
    require.resolve of the stylesheet throws.

    Resolve the package root via its exported package.json and join the CSS
    path manually, which sidesteps the exports restriction while keeping the
    copied components.css that preview-head.html loads.

diff --git a/.github/workflows/storybook-pages.yml b/.github/workflows/storybook-pages.yml
index 143fdb8e2ee..6d8b03b3154 100644
--- a/.github/workflows/storybook-pages.yml
+++ b/.github/workflows/storybook-pages.yml
@@ -36,3 +36,11 @@ jobs:
                   github_token: ${{ secrets.GITHUB_TOKEN }}
                   publish_dir: ./tools/storybook/storybook-static
                   force_orphan: true
+
+            - name: Report failure on Slack
+              if: ${{ failure() }}
+              env:
+                  SLACK_TOKEN: ${{ secrets.E2E_SLACK_TOKEN }}
+                  DEFAULT_CHECKS_CHANNEL: ${{ secrets.TEST_REPORTS_SLACK_CHANNEL }}
+                  DAILY_CHECKS_CHANNEL: ${{ secrets.DAILY_CHECKS_CHANNEL }}
+              run: pnpm utils slack-test-report -c failure -r "nightly-checks Storybook build and deploy" --config ".github/workflows/slack-report-config.json"
diff --git a/packages/js/internal-build/src/style-build/webpack-rtl-plugin.cjs b/packages/js/internal-build/src/style-build/webpack-rtl-plugin.cjs
index b1a80d113d6..7c1af403b33 100644
--- a/packages/js/internal-build/src/style-build/webpack-rtl-plugin.cjs
+++ b/packages/js/internal-build/src/style-build/webpack-rtl-plugin.cjs
@@ -55,6 +55,29 @@ class WebpackRTLPlugin {
 									}
 								}

+								/*
+								 * In-place mode rewrites the original stylesheet to
+								 * RTL rather than emitting a separate `-rtl.css`
+								 * sibling, so an existing (LTR) `<link>` renders RTL.
+								 * Storybook uses this for its RTL preview, where the
+								 * content-hashed chunk filenames can't be predicted
+								 * to swap a link. Skips the sibling/caching path.
+								 */
+								if ( this.options.inPlace ) {
+									const rtlSource = rtlcss.process(
+										assets[ asset ].source(),
+										this.options.options,
+										this.options.plugins
+									);
+									compilation.updateAsset(
+										asset,
+										new compiler.webpack.sources.RawSource(
+											rtlSource
+										)
+									);
+									return;
+								}
+
 								// Compute the filename
 								const filename = asset.replace(
 									cssRe,
diff --git a/plugins/woocommerce/changelog/fix-storybook-nightly-build b/plugins/woocommerce/changelog/fix-storybook-nightly-build
new file mode 100644
index 00000000000..213de57793a
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-storybook-nightly-build
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Fix the Storybook nightly build: skip type-only tsconfig path mappings when generating Blocks Storybook webpack aliases. Dev tooling only, no user-facing changes.
diff --git a/plugins/woocommerce/client/blocks/storybook/webpack.config.js b/plugins/woocommerce/client/blocks/storybook/webpack.config.js
index 2c956dd6489..e6e9bbaba78 100644
--- a/plugins/woocommerce/client/blocks/storybook/webpack.config.js
+++ b/plugins/woocommerce/client/blocks/storybook/webpack.config.js
@@ -19,10 +19,17 @@ const aliases = Object.keys( tsConfig.compilerOptions.paths ).reduce(
 		// Filter out @wordpress/* paths to allow resolution from node_modules instead of build-types directory specified in tsconfig.
 		if ( ! key.startsWith( '@wordpress' ) ) {
 			const currentPath = tsConfig.compilerOptions.paths[ key ][ 0 ];
-			acc[ key.replace( '/*', '' ) ] = path.resolve(
-				__dirname,
-				'../' + currentPath.replace( '/*', '/' )
-			);
+			// Skip type-only mappings (`.d.ts`). These exist so `tsc` can
+			// resolve a package's types; turning them into webpack aliases
+			// makes the bundler import the declaration file instead of the
+			// runtime module (e.g. `dinero.js/currencies`). Let those resolve
+			// normally via the package's exports.
+			if ( ! currentPath.endsWith( '.d.ts' ) ) {
+				acc[ key.replace( '/*', '' ) ] = path.resolve(
+					__dirname,
+					'../' + currentPath.replace( '/*', '/' )
+				);
+			}
 		}
 		return acc;
 	},
@@ -45,6 +52,12 @@ module.exports = ( { config: storybookConfig } ) => {
 			'../node_modules/wordpress-components'
 		),
 	};
+
+	// Resolve `@woocommerce/*` monorepo packages from source via the `wc-source`
+	// export condition, mirroring the blocks' main webpack resolve. Required now
+	// that the package JS build outputs are no longer produced by a build cascade.
+	storybookConfig.resolve.conditionNames = [ 'wc-source', '...' ];
+
 	storybookConfig.module.rules.push(
 		{
 			test: /\/stories\/.+\.js$/,
@@ -75,5 +88,20 @@ module.exports = ( { config: storybookConfig } ) => {
 			)
 	);

+	// Storybook's default babel rule excludes node_modules, but `@woocommerce/*`
+	// packages now resolve to their TypeScript source (symlinked under
+	// node_modules), so that source must be transpiled. Added after the filter
+	// above so it isn't stripped. Uses the project's babel.config.js. Scoped to
+	// Storybook — does not touch the shared plugin webpack config.
+	storybookConfig.module.rules.push( {
+		test: /\.(j|t)sx?$/,
+		// pnpm resolves workspace packages to their real path, so `@woocommerce/*`
+		// source appears as `packages/js/<pkg>/src` (matched here) rather than the
+		// `node_modules/@woocommerce/<pkg>` symlink.
+		include:
+			/[\/\\](?:packages[\/\\]js|node_modules[\/\\]@woocommerce)[\/\\][^\/\\]+[\/\\]src[\/\\]/,
+		loader: 'babel-loader',
+	} );
+
 	return storybookConfig;
 };
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ea0ad30a118..62349e70a22 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4409,6 +4409,9 @@ importers:
       '@woocommerce/eslint-plugin':
         specifier: workspace:*
         version: link:../../packages/js/eslint-plugin
+      '@wordpress/base-styles':
+        specifier: catalog:wp-bundled
+        version: 6.9.1
       react:
         specifier: 18.3.x
         version: 18.3.1
diff --git a/tools/storybook/.storybook/main.js b/tools/storybook/.storybook/main.js
index 5c298d13697..badc1820c18 100644
--- a/tools/storybook/.storybook/main.js
+++ b/tools/storybook/.storybook/main.js
@@ -23,7 +23,6 @@ module.exports = {
 		'../../../packages/js/experimental/src/**/stories/*.story.@(js|tsx)',
 		// WooCommerce Admin / @woocommerce/onboarding components
 		'../../../packages/js/onboarding/src/**/stories/*.story.@(js|tsx)',
-		'../../../packages/js/product-editor/src/**/*.(stories|story).@(js|tsx)',
 		'../../../plugins/woocommerce/client/admin/client/**/stories/*.story.@(js|tsx)',
 	],
 	refs: ( config, { configType } ) => {
@@ -74,24 +73,6 @@ module.exports = {
 	previewHead: ( head ) => `
 		${ head }

-		${
-			process.env.USE_RTL_STYLE === 'true'
-				? `
-			<link href="experimental-css/style-rtl.css" rel="stylesheet" />
-			<link href="component-css/style-rtl.css" rel="stylesheet" />
-			<link href="onboarding-css/style-rtl.css" rel="stylesheet" />
-			<link href="product-editor-css/style-rtl.css" rel="stylesheet" />
-			<link href="app-css/style-rtl.css" rel="stylesheet" />
-			`
-				: `
-			<link href="component-css/style.css" rel="stylesheet" />
-			<link href="experimental-css/style.css" rel="stylesheet" />
-			<link href="onboarding-css/style.css" rel="stylesheet" />
-			<link href="product-editor-css/style.css" rel="stylesheet" />
-			<link href="app-css/style.css" rel="stylesheet" />
-			`
-		}
-
 		<style>
 			/* Use system font, consistent with WordPress core (wp-admin) */
 			body {
diff --git a/tools/storybook/.storybook/manager.js b/tools/storybook/.storybook/manager.js
index 64423431894..7be81d2aa57 100644
--- a/tools/storybook/.storybook/manager.js
+++ b/tools/storybook/.storybook/manager.js
@@ -16,7 +16,6 @@ addons.setConfig( {
 			'components',
 			'experimental',
 			'onboarding',
-			'product-editor',
 			'woocommerce-admin',
 		],
 	},
diff --git a/tools/storybook/.storybook/preview.js b/tools/storybook/.storybook/preview.js
new file mode 100644
index 00000000000..fdf190ca5a1
--- /dev/null
+++ b/tools/storybook/.storybook/preview.js
@@ -0,0 +1,13 @@
+/**
+ * Internal dependencies
+ */
+// Compile the package and admin app stylesheets from source using the same
+// SCSS pipeline Storybook inherits from the admin webpack config (sass-loader
+// + postcss + MiniCssExtractPlugin + WebpackRTLPlugin). These `src/style.scss`
+// files are standalone (not imported by each package's `index.ts`), so they
+// must be imported explicitly. This replaces copying pre-built `build-style`
+// artifacts, keeping Storybook self-contained — no package builds required.
+import '../../../packages/js/components/src/style.scss';
+import '../../../packages/js/experimental/src/style.scss';
+import '../../../packages/js/onboarding/src/style.scss';
+import '../../../plugins/woocommerce/client/admin/client/stylesheets/_index.scss';
diff --git a/tools/storybook/package.json b/tools/storybook/package.json
index e769834c4e0..715c7c9a6c4 100644
--- a/tools/storybook/package.json
+++ b/tools/storybook/package.json
@@ -11,12 +11,12 @@
 	"author": "Automattic",
 	"license": "GPL-2.0-or-later",
 	"scripts": {
-		"build": "pnpm build:storybook",
+		"build": "pnpm --filter='@woocommerce/block-library' build:storybook && pnpm build:storybook",
 		"build:storybook": "./import-wp-css-storybook.sh && STORYBOOK=true BABEL_ENV=storybook storybook build -c ./.storybook --quiet && pnpm run copy-blocks-storybook-iframe",
 		"watch:build": "pnpm watch:build:storybook",
 		"watch:build:storybook": "./import-wp-css-storybook.sh && STORYBOOK=true BABEL_ENV=storybook storybook dev -c ./.storybook -p 6007 --ci",
 		"copy-blocks-storybook-iframe": "cp ../../plugins/woocommerce/client/blocks/storybook/dist/iframe.html ./storybook-static/assets/woocommerce-blocks",
-		"storybook-rtl": "USE_RTL_STYLE=true pnpm storybook",
+		"storybook-rtl": "USE_RTL_STYLE=true pnpm watch:build:storybook",
 		"preinstall": "npx only-allow pnpm"
 	},
 	"engines": {
@@ -49,6 +49,7 @@
 		"@woocommerce/admin-library": "workspace:*",
 		"@woocommerce/block-library": "workspace:*",
 		"@woocommerce/eslint-plugin": "workspace:*",
+		"@wordpress/base-styles": "catalog:wp-bundled",
 		"react": "18.3.x",
 		"react-dom": "18.3.x",
 		"storybook": "7.6.19",
diff --git a/tools/storybook/webpack.config.js b/tools/storybook/webpack.config.js
index f4abde792d3..a83d565a2a7 100644
--- a/tools/storybook/webpack.config.js
+++ b/tools/storybook/webpack.config.js
@@ -8,19 +8,9 @@ const CopyWebpackPlugin = require( 'copy-webpack-plugin' );
  * External dependencies
  */
 const wcAdminWebpackConfig = require( '../../plugins/woocommerce/client/admin/webpack.config.js' );
-
-const wcAdminPackages = [
-	'components',
-	'csv-export',
-	'currency',
-	'date',
-	'navigation',
-	'number',
-	'data',
-	'tracks',
-	'experimental',
-	'onboarding',
-];
+const {
+	WebpackRTLPlugin,
+} = require( '@woocommerce/internal-build/style-build' );

 module.exports = ( storybookConfig ) => {
 	storybookConfig.module.rules = [
@@ -28,12 +18,14 @@ module.exports = ( storybookConfig ) => {
 		...wcAdminWebpackConfig.module.rules,
 	];

-	storybookConfig.resolve.alias = wcAdminWebpackConfig.resolve.alias;
+	// Copy (don't share) the admin alias object since we mutate it below.
+	storybookConfig.resolve.alias = { ...wcAdminWebpackConfig.resolve.alias };

-	wcAdminPackages.forEach( ( name ) => {
-		storybookConfig.resolve.alias[ `@woocommerce/${ name }` ] =
-			path.resolve( __dirname, `../../packages/js/${ name }/src` );
-	} );
+	// Bundle every `@woocommerce/*` package from source, mirroring the admin
+	// webpack config. Each package declares a `"wc-source"` conditional export
+	// resolving to its `./src/index.ts`, so activating the condition picks up
+	// all current and future packages without a hardcoded alias list.
+	storybookConfig.resolve.conditionNames = [ 'wc-source', '...' ];

 	storybookConfig.resolve.alias[ '@woocommerce/settings' ] = path.resolve(
 		__dirname,
@@ -58,8 +50,20 @@ module.exports = ( storybookConfig ) => {
 		'node_modules',
 	];

+	// When USE_RTL_STYLE is set (the `storybook-rtl` script), swap the
+	// inherited RTL plugin for an in-place instance so the compiled CSS is
+	// rewritten to RTL rather than emitted as an unused `-rtl.css` sibling.
+	// This keeps RTL preview on the existing rtlcss toolchain now that the
+	// stylesheets are bundled from source instead of copied as pre-built files.
+	const useRtl = process.env.USE_RTL_STYLE === 'true';
+	const inheritedPlugins = wcAdminWebpackConfig.plugins.map( ( plugin ) =>
+		useRtl && plugin instanceof WebpackRTLPlugin
+			? new WebpackRTLPlugin( { inPlace: true } )
+			: plugin
+	);
+
 	storybookConfig.plugins.push(
-		...wcAdminWebpackConfig.plugins,
+		...inheritedPlugins,
 		new CopyWebpackPlugin( {
 			patterns: [
 				{
@@ -67,39 +71,24 @@ module.exports = ( storybookConfig ) => {
 					to: 'wordpress/css/[name][ext]',
 				},
 				{
-					from: require.resolve(
-						'@wordpress/components/build-style/style.css'
+					/*
+					 * Resolve the package root via its exported `package.json`
+					 * and join the CSS path manually. `@wordpress/components`
+					 * still maps `build-style` as a deprecated trailing-slash
+					 * folder export, which Node 24 no longer honors, so a direct
+					 * `require.resolve` of the stylesheet throws
+					 * ERR_PACKAGE_PATH_NOT_EXPORTED.
+					 */
+					from: path.join(
+						path.dirname(
+							require.resolve(
+								'@wordpress/components/package.json'
+							)
+						),
+						'build-style/style.css'
 					),
 					to: 'wordpress/css/components.css',
 				},
-				{
-					from: path.resolve(
-						__dirname,
-						`../../packages/js/components/build-style/*.css`
-					),
-					to: `./component-css/[name][ext]`,
-				},
-				{
-					from: path.resolve(
-						__dirname,
-						`../../packages/js/onboarding/build-style/*.css`
-					),
-					to: `./onboarding-css/[name][ext]`,
-				},
-				{
-					from: path.resolve(
-						__dirname,
-						`../../packages/js/experimental/build-style/*.css`
-					),
-					to: `./experimental-css/[name][ext]`,
-				},
-				{
-					from: path.resolve(
-						__dirname,
-						`../../plugins/woocommerce/client/admin/build/app/*.css`
-					),
-					to: `./app-css/[name][ext]`,
-				},
 			],
 		} )
 	);