Commit 49a8c9fd7e4 for woocommerce

commit 49a8c9fd7e4ecc7cb43347b16e694109212c23f4
Author: Christopher Allford <6451942+ObliviousHarmony@users.noreply.github.com>
Date:   Thu Jun 4 23:45:59 2026 -0700

    Build Packages With Admin/Blocks (#65254)

    * Use Absolute Product Editor Block Paths

    * Bundle Source When Building Admin

    Rather than requiring the build to cascade down to
    the packages, we now just bundle directly from
    the package source files.

    * Bundle Source When Build Admin

    * Build Classic Assets Into Plugin Dir

    * Remove Wireit Code

    Since we're leaving the entire dependency behind,
    there's no need for the dependency output
    generation script.

    * Removed Google's wireit

    Now that our builds have reached such a high
    speed, there's no need to try and maintain a
    build cache.

    * Fixed Classic Asset Watch

    * Use WooCommerce Package Symlinks

    Rather than having a `copy-assets` for
    the packages, just rely on Composer.
    It was already being used for the
    autoloader, however, the symlink
    meant that the build:zip would
    break. This change now uses
    the rsync -L option to deference
    the symlink when building the zip.

    * Removed Build Cascade

    We can now simplify all of the build
    scripts in our `package.json` files.

    * Prevent Local Store Commits

    * Fixed Package Resolution

    The caveat to using the conditional export
    is that Webpack tries to externalize the
    imports each package has to itself.
    Instead, let's go back to using the
    paths so it doesn't externalize like
    that.

    * Updated Lock File

    * Added Composer Package Watcher

    It's sometimes necessary that a Composer
    package be watched so that it can be
    copied to another directory. This adds
    a new build utility so that we can
    do just that.

    * Watch WooCommerce Package Changes

    * Fixed Package Symlinks

    * Lint Fixes

    * Lint Fixes

    * Fix Playwright Test Dependency

    Since we removed the cascade, we need to make
    it so that the Playwright package doesn't need
    to be built in order to be used.

    * Fix Admin Library CSS Build

    Prior to removing the cascade, the admin webpack
    build would copy the generated CSS files from the
    package directories. With the cascade removed,
    we need webpack to build the assets directly.

    * Fix Test Translations

    * Cache Webpack Artifacts

    Since we're using webpack's intermediate cache, we
    can take advantage of it by caching the directory
    it stores them it. This should _hopefully_ get us
    near-instant builds but you never know.

    * Fixed Missing --stream

    * Prevent Unnecessary Pending Resets

    * Use Better Build Cache Key

    * Removed Build Caching

    Basically, the post-build step was taking 20m.

    * Erase .wireit Directories

    * Fix `react-dom/client` Externalization

    * Only Copy Product Editor block.json

    * Use Composer Install Paths

    Composer actually supplies a way to get
    the path a package installs to. This lets
    us have a source of truth that doesn't
    require any guessing.

diff --git a/.github/actions/setup-woocommerce-monorepo/action.yml b/.github/actions/setup-woocommerce-monorepo/action.yml
index 446724c3f93..f9051d9d06c 100644
--- a/.github/actions/setup-woocommerce-monorepo/action.yml
+++ b/.github/actions/setup-woocommerce-monorepo/action.yml
@@ -98,12 +98,6 @@ runs:
             else
               pnpm install ${{ steps.project-filters.outputs.install }} --frozen-lockfile
             fi
-        # We want to include an option to build projects using this action so that we can make
-        # sure that the build cache is always used when building projects.
-        - name: 'Cache Build Output'
-          # Boolean inputs aren't parsed into filters so it'll either be "true" or there will be a filter.
-          if: ${{ inputs.build == 'true' || steps.project-filters.outputs.build != '' }}
-          uses: 'google/wireit@setup-github-actions-caching/v2'
         - name: 'Build'
           # Boolean inputs aren't parsed into filters so it'll either be "true" or there will be a filter.
           if: ${{ inputs.build == 'true' || steps.project-filters.outputs.build != '' }}
diff --git a/.gitignore b/.gitignore
index 7469b09c57b..23a03a6f330 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,6 +47,7 @@ dist/
 oclif.manifest.json

 # Project files
+.pnpm-store/
 node_modules/
 vendor/

diff --git a/.husky/post-merge b/.husky/post-merge
index 226b937c324..930bb478e01 100755
--- a/.husky/post-merge
+++ b/.husky/post-merge
@@ -17,20 +17,11 @@ if [ -n "$changedManifests" ]; then
 	pnpm install --frozen-lockfile
 fi

-# Cleanup .wireit cache that is older than 2 weeks (14 days); otherwise, the repository directory size keeps growing and growing.
-staleWireitDirectories=$(( \
-	$( find plugins/*/.wireit/*/cache/* -maxdepth 0 -type d -ctime +14 -print 2>/dev/null | wc -l ) + \
-	$( find packages/js/*/.wireit/*/cache/* -maxdepth 0 -type d -ctime +14 -print 2>/dev/null | wc -l ) + \
-	$( find plugins/woocommerce/client/*/.wireit/*/cache/* -maxdepth 0 -type d -ctime +14 -print 2>/dev/null | wc -l ) \
-))
-if [ $staleWireitDirectories -gt 0 ]; then
-	echo "Cleaning up stale wireit-cache ($staleWireitDirectories directories)"
-	# Main cleanup step - disk space usage reduction
-	find plugins/*/.wireit/*/cache/* -maxdepth 0 -type d -ctime +14 -exec rm -rf {} \; 2>/dev/null
-	find packages/js/*/.wireit/*/cache/* -maxdepth 0 -type d -ctime +14 -exec rm -rf {} \; 2>/dev/null
-	find plugins/woocommerce/client/*/.wireit/*/cache/* -maxdepth 0 -type d -ctime +14 -exec rm -rf {} \; 2>/dev/null
-	# Extra cleanup step - housekeeping `.wireit` directories
-	find plugins/*/.wireit/* -maxdepth 0 -type d -ctime +14 -exec rm -rf {} \; 2>/dev/null
-	find packages/js/*/.wireit/* -maxdepth 0 -type d -ctime +14 -exec rm -rf {} \; 2>/dev/null
-	find plugins/woocommerce/client/*/.wireit/* -maxdepth 0 -type d -ctime +14 -exec rm -rf {} \; 2>/dev/null
+# Wireit has been removed from the monorepo. Tear down any leftover .wireit
+# directories so they stop occupying disk space on machines that built with it
+# previously.
+leftoverWireitDirectories=$(find . -type d -name .wireit -not -path '*/node_modules/*' 2>/dev/null)
+if [ -n "$leftoverWireitDirectories" ]; then
+	echo "Removing leftover .wireit directories"
+	echo "$leftoverWireitDirectories" | xargs rm -rf
 fi
diff --git a/.pnpmfile.cjs b/.pnpmfile.cjs
index f5158263d8c..3ecaabfa39d 100644
--- a/.pnpmfile.cjs
+++ b/.pnpmfile.cjs
@@ -29,25 +29,6 @@ function loadPackageFile( packagePath ) {
 	return packageFile;
 }

-/**
- * Updates a package file on disk and in the cache.
- *
- * @param {string} packagePath The path to the package file to update.
- * @param {Object} packageFile The new package file contents.
- */
-function updatePackageFile( packagePath, packageFile ) {
-	// Resolve the absolute path for consistency when loading and updating.
-	packagePath = path.resolve( __dirname, packagePath );
-	packageFileCache[ packagePath ] = packageFile;
-
-	fs.writeFileSync(
-		path.join( packagePath, 'package.json' ),
-		// Make sure to keep the newline at the end of the file.
-		JSON.stringify( packageFile, null, '\t' ) + '\n',
-		'utf8'
-	);
-}
-
 /**
  * Loads a tsconfig.json, or null if missing or not plain JSON.
  *
@@ -230,77 +211,6 @@ function syncTsReferences( lockfile, context ) {
 	context.log( '[tsrefs] Done' );
 }

-/**
- * Populated config object based on declared and resolved dependencies.
- *
- * @param {string}            packageName          Package name.
- * @param {string}            packagePath          Package path.
- * @param {Object}            declaredDependencies Declared dependencies from package-file.
- * @param {Object}            resolvedDependencies Resolved dependencies from lock-file.
- * @param {Object}            config               Dependency output path configuration.
- * @param {Object}            context              The hook context object.
- * @param {Function.<string>} context.log          Logs a message to the console.
- *
- * @return void
- */
-function updateConfig(
-	packageName,
-	packagePath,
-	declaredDependencies,
-	resolvedDependencies,
-	config,
-	context
-) {
-	for ( const [ key, value ] of Object.entries( declaredDependencies ) ) {
-		if ( value.startsWith( 'workspace:' ) ) {
-			const normalizedPath = path.join(
-				packagePath,
-				resolvedDependencies[ key ].replace( 'link:', '' )
-			);
-			context.log(
-				`[wireit][${ packageName }]    Inspecting workspace dependency: ${ key } (${ normalizedPath })`
-			);
-
-			// Actualize output storage with the identified entries.
-			const dependencyFile = loadPackageFile( normalizedPath );
-			if ( dependencyFile.files ) {
-				for ( const entry in dependencyFile.files ) {
-					const entryValue = dependencyFile.files[ entry ];
-					// Since 'build-module' and 'build-types' are generated simultaneously, it is more efficient for WireIt to track changes
-					// to 'build-types' only. This approach also enables a clear separation of the CJS and ESM watch build cascades.
-					if ( entryValue === 'build-module' && dependencyFile.files.includes( 'build-types' ) ) {
-						continue;
-					}
-
-					let normalizedValue;
-					if ( entryValue.startsWith( '!' ) ) {
-						normalizedValue =
-							'!' +
-							path.join(
-								'node_modules',
-								key,
-								entryValue.substring( 1 )
-							);
-					} else {
-						normalizedValue = path.join(
-							'node_modules',
-							key,
-							entryValue
-						);
-					}
-					config.files.push( normalizedValue );
-
-					context.log(
-						`[wireit][${ packageName }]        - ${ normalizedValue }`
-					);
-				}
-			} else {
-				context.log( `[wireit][${ packageName }]        ---` );
-			}
-		}
-	}
-}
-
 /**
  * This hook allows for the mutation of the lockfile before it is serialized.
  *
@@ -313,72 +223,6 @@ function updateConfig(
  * @return {Object} lockfile The updated lockfile.
  */
 function afterAllResolved( lockfile, context ) {
-	context.log( '[wireit] Updating Dependency Lists' );
-
-	for ( const packagePath in lockfile.importers ) {
-		const packageFile = loadPackageFile( packagePath );
-		if ( packageFile.wireit ) {
-			context.log(
-				`[wireit][${ packageFile.name }] Verifying 'wireit.dependencyOutputs'`
-			);
-
-			// Include the lock file in the fingerprint in case resolved versions change.
-			const lockfilePath = path.join(
-				path.relative( packagePath, '.' ),
-				'pnpm-lock.yaml'
-			);
-
-			// Initialize outputs storage and hash it's original state.
-			const config = {
-				allowUsuallyExcludedPaths: true, // This is needed so we can reference files in `node_modules`.
-				files: [ 'package.json', lockfilePath ], // The files list will include globs for dependency files that we should fingerprint.
-			};
-			const originalConfigState = JSON.stringify( config );
-
-			// Walk through workspace-located dependencies and provision.
-			updateConfig(
-				packageFile.name,
-				packagePath,
-				{
-					...( packageFile.dependencies || {} ),
-					...( packageFile.devDependencies || {} ),
-				},
-				{
-					...( lockfile.importers[ packagePath ].dependencies || {} ),
-					...( lockfile.importers[ packagePath ].devDependencies ||
-						{} ),
-				},
-				config,
-				context
-			);
-
-			// Verify config state and update manifest on mismatch.
-			let updated = false;
-			const newConfigState = JSON.stringify( config );
-			if ( newConfigState !== originalConfigState ) {
-				const loadedConfigState = JSON.stringify(
-					packageFile.wireit?.dependencyOutputs || {}
-				);
-				if ( newConfigState !== loadedConfigState ) {
-					context.log(
-						`[wireit][${ packageFile.name }]    Conclusion: outdated, updating 'wireit.dependencyOutputs'`
-					);
-
-					packageFile.wireit.dependencyOutputs = config;
-					updatePackageFile( packagePath, packageFile );
-					updated = true;
-				}
-			}
-			if ( ! updated ) {
-				context.log(
-					`[wireit][${ packageFile.name }]    Conclusion: up to date`
-				);
-			}
-		}
-	}
-
-	context.log( '[wireit] Done' );
-
 	syncTsReferences( lockfile, context );

 	return lockfile;
diff --git a/.syncpackrc b/.syncpackrc
index 3c75a26c57a..39e8a34ad85 100644
--- a/.syncpackrc
+++ b/.syncpackrc
@@ -625,15 +625,6 @@
 			],
 			"pinVersion": "9.5.x"
 		},
-		{
-			"dependencies": [
-				"wireit"
-			],
-			"packages": [
-				"**"
-			],
-			"pinVersion": "0.14.12"
-		},
 		{
 			"dependencies": [
 				"dompurify"
diff --git a/package.json b/package.json
index b0be261ce6c..eb584889cc3 100644
--- a/package.json
+++ b/package.json
@@ -22,8 +22,8 @@
 		"test": "pnpm -r test",
 		"lint": "pnpm -r lint",
 		"cherry-pick": "node ./tools/cherry-pick/bin/run",
-		"clean": "rimraf -g '**/node_modules' '**/.wireit' 'packages/*/*/vendor' 'plugins/*/vendor' && pnpm store prune",
-		"clean:build": "rimraf -g '**/.wireit' '**/node_modules/.cache' '**/*.tsbuildinfo' 'packages/js/*/build' 'packages/js/*/build-*' 'packages/js/*/dist' 'plugins/*/build' 'plugins/woocommerce/client/*/build' && git clean --force -d -X --quiet ./plugins/woocommerce/assets",
+		"clean": "rimraf -g '**/node_modules' 'packages/*/*/vendor' 'plugins/*/vendor' && pnpm store prune",
+		"clean:build": "rimraf -g '**/node_modules/.cache' '**/*.tsbuildinfo' 'packages/js/*/build' 'packages/js/*/build-*' 'packages/js/*/dist' 'plugins/*/build' 'plugins/woocommerce/client/*/build' && git clean --force -d -X --quiet ./plugins/woocommerce/assets",
 		"preinstall": "npx only-allow pnpm",
 		"postinstall": "husky",
 		"run-canonical-extensions-tests": "bash bin/run-canonical-extensions-tests.sh",
diff --git a/packages/js/admin-layout/package.json b/packages/js/admin-layout/package.json
index e0d97ab6516..3f01d336186 100644
--- a/packages/js/admin-layout/package.json
+++ b/packages/js/admin-layout/package.json
@@ -49,14 +49,14 @@
 		}
 	},
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:bundle": "NODE_ENV=production webpack",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -65,8 +65,8 @@
 		"lint:lang:types": "tsc --build --emitDeclarationOnly",
 		"prepack": "pnpm build:publish:project",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:bundle": "NODE_ENV=development webpack --watch",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
diff --git a/packages/js/block-templates/package.json b/packages/js/block-templates/package.json
index 0d9135825e7..914a12a3a5b 100644
--- a/packages/js/block-templates/package.json
+++ b/packages/js/block-templates/package.json
@@ -39,14 +39,14 @@
 		"src/**/*.scss"
 	],
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:bundle": "NODE_ENV=production webpack",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -56,8 +56,8 @@
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:bundle": "NODE_ENV=development webpack --watch",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
diff --git a/packages/js/components/package.json b/packages/js/components/package.json
index 4795e7d1168..c1628d61435 100644
--- a/packages/js/components/package.json
+++ b/packages/js/components/package.json
@@ -43,14 +43,14 @@
 	],
 	"types": "build-types/index.d.ts",
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:bundle": "NODE_ENV=production webpack",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -60,8 +60,8 @@
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:bundle": "NODE_ENV=development webpack --watch",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
diff --git a/packages/js/csv-export/package.json b/packages/js/csv-export/package.json
index 89a66b9dcb5..6b984c60c0c 100644
--- a/packages/js/csv-export/package.json
+++ b/packages/js/csv-export/package.json
@@ -38,13 +38,13 @@
 		"build-types"
 	],
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -54,8 +54,8 @@
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
 	"dependencies": {
diff --git a/packages/js/currency/package.json b/packages/js/currency/package.json
index 75dba674c08..a98e20ec879 100644
--- a/packages/js/currency/package.json
+++ b/packages/js/currency/package.json
@@ -38,13 +38,13 @@
 		"build-types"
 	],
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -54,8 +54,8 @@
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
 	"lint-staged": {
diff --git a/packages/js/customer-effort-score/package.json b/packages/js/customer-effort-score/package.json
index f2ef6406cd8..9967ca16293 100644
--- a/packages/js/customer-effort-score/package.json
+++ b/packages/js/customer-effort-score/package.json
@@ -38,14 +38,14 @@
 		"build-types"
 	],
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:bundle": "NODE_ENV=production webpack",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -55,8 +55,8 @@
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:bundle": "NODE_ENV=development webpack --watch",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
diff --git a/packages/js/data/package.json b/packages/js/data/package.json
index a47967b25f2..ffee0c438c7 100644
--- a/packages/js/data/package.json
+++ b/packages/js/data/package.json
@@ -37,13 +37,13 @@
 		"build-types"
 	],
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
@@ -53,8 +53,8 @@
 		"lint:lang:types": "tsc --build --emitDeclarationOnly",
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
 	"lint-staged": {
diff --git a/packages/js/date/package.json b/packages/js/date/package.json
index ec923f4bbeb..0251a5aec45 100644
--- a/packages/js/date/package.json
+++ b/packages/js/date/package.json
@@ -77,13 +77,13 @@
 		}
 	},
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -93,8 +93,8 @@
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
 	"lint-staged": {
diff --git a/packages/js/e2e-utils-playwright/package.json b/packages/js/e2e-utils-playwright/package.json
index 7f19a77a9ee..441543191b9 100644
--- a/packages/js/e2e-utils-playwright/package.json
+++ b/packages/js/e2e-utils-playwright/package.json
@@ -13,6 +13,7 @@
 	},
 	"exports": {
 		".": {
+			"wc-source": "./src/index.ts",
 			"types": "./build-types/index.d.ts",
 			"import": "./build-module/index.js",
 			"require": "./build/index.js"
@@ -27,20 +28,22 @@
 		"build-types"
 	],
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:cjs": "node build.mjs --commonjs",
 		"build:project:esm": "node build.mjs",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "eslint --ext=js,ts,tsx src",
 		"lint:fix": "eslint --ext=js,ts,tsx src --fix",
 		"lint:lang:types": "tsc --build --emitDeclarationOnly",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"prepack": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
+		"prepack": "pnpm build:publish:project",
 		"test": "jest --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:cjs": "node build.mjs --commonjs --watch",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
diff --git a/packages/js/email-editor/package.json b/packages/js/email-editor/package.json
index 25bc959c820..2f0f1054bc5 100644
--- a/packages/js/email-editor/package.json
+++ b/packages/js/email-editor/package.json
@@ -50,14 +50,14 @@
 		"src/**/*.scss"
 	],
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:bundle": "NODE_ENV=production webpack",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"format": "prettier --write 'src/**/*.{js,jsx,json,ts,tsx,yml,yaml,scss}'",
 		"format:check": "prettier --check 'src/**/*.{js,jsx,json,ts,tsx,yml,yaml,scss}'",
@@ -71,8 +71,8 @@
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:bundle": "NODE_ENV=development webpack --watch",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
diff --git a/packages/js/experimental-products-app/package.json b/packages/js/experimental-products-app/package.json
index 31a74cf10a6..55d70f636d8 100644
--- a/packages/js/experimental-products-app/package.json
+++ b/packages/js/experimental-products-app/package.json
@@ -108,14 +108,14 @@
 		"webpack-remove-empty-scripts": "1.0.x"
 	},
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:bundle": "NODE_ENV=production webpack",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -125,8 +125,8 @@
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:bundle": "NODE_ENV=development webpack --watch",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
diff --git a/packages/js/experimental/package.json b/packages/js/experimental/package.json
index 44f750215ad..a042a135d14 100644
--- a/packages/js/experimental/package.json
+++ b/packages/js/experimental/package.json
@@ -101,14 +101,14 @@
 		"react-dom": "18.3.x"
 	},
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:bundle": "NODE_ENV=production webpack",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -118,8 +118,8 @@
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:bundle": "NODE_ENV=development webpack --watch",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
diff --git a/packages/js/explat/package.json b/packages/js/explat/package.json
index 31a04e504f2..309c732063d 100644
--- a/packages/js/explat/package.json
+++ b/packages/js/explat/package.json
@@ -38,13 +38,13 @@
 		"build-types"
 	],
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -54,8 +54,8 @@
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
 	"lint-staged": {
diff --git a/packages/js/expression-evaluation/package.json b/packages/js/expression-evaluation/package.json
index 051c12ff6fe..bdd9164aa01 100644
--- a/packages/js/expression-evaluation/package.json
+++ b/packages/js/expression-evaluation/package.json
@@ -53,13 +53,13 @@
 		}
 	},
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -68,8 +68,8 @@
 		"lint:lang:types": "tsc --build --emitDeclarationOnly",
 		"prepack": "pnpm build:publish:project",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
 	"devDependencies": {
diff --git a/packages/js/internal-build/package.json b/packages/js/internal-build/package.json
index 540df7924ce..aab39cea81b 100644
--- a/packages/js/internal-build/package.json
+++ b/packages/js/internal-build/package.json
@@ -42,8 +42,8 @@
 		"scripts"
 	],
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:lib": "tsc --build src/tsconfig.json",
 		"build:project:type-overrides": "node scripts/generate-types.mjs generate",
 		"postinstall": "pnpm build:project",
diff --git a/packages/js/internal-build/src/composer/index.ts b/packages/js/internal-build/src/composer/index.ts
new file mode 100644
index 00000000000..d748547d121
--- /dev/null
+++ b/packages/js/internal-build/src/composer/index.ts
@@ -0,0 +1,434 @@
+/**
+ * External dependencies
+ */
+import { spawn } from 'node:child_process';
+import { createHash } from 'node:crypto';
+import { existsSync } from 'node:fs';
+import { copyFile, mkdir, readFile, rm } from 'node:fs/promises';
+import path from 'node:path';
+import chokidar from 'chokidar';
+
+/**
+ * Internal dependencies
+ */
+import { log } from '../utils/logger.js';
+
+export interface ComposerPackageWatcherOptions {
+	composerJson: string;
+	ignoredPackages?: string[];
+}
+
+interface ComposerAutoload {
+	'psr-4'?: Record< string, string | string[] >;
+	'psr-0'?: Record< string, string | string[] >;
+	classmap?: string[];
+	files?: string[];
+}
+
+interface ComposerJson {
+	name?: string;
+	require?: Record< string, string >;
+	'require-dev'?: Record< string, string >;
+	repositories?: Array< {
+		type: string;
+		url?: string;
+		options?: { symlink?: boolean };
+	} >;
+	autoload?: ComposerAutoload;
+}
+
+interface WatchedPackage {
+	name: string;
+	sourceDir: string;
+	destDir: string;
+}
+
+const RESTART_DEBOUNCE_MS = 300;
+
+async function readJson< T >( file: string ): Promise< T > {
+	return JSON.parse( await readFile( file, 'utf8' ) ) as T;
+}
+
+function isPlatformReq( name: string ): boolean {
+	return (
+		name === 'php' || name.startsWith( 'ext-' ) || name.startsWith( 'lib-' )
+	);
+}
+
+function execComposer( args: string[], projectDir: string ): Promise< string > {
+	return new Promise( ( resolve, reject ) => {
+		const child = spawn( 'composer', args, {
+			cwd: projectDir,
+			env: { ...process.env, XDEBUG_MODE: 'off' },
+			stdio: [ 'ignore', 'pipe', 'pipe' ],
+		} );
+		const stdoutChunks: Buffer[] = [];
+		const stderrChunks: Buffer[] = [];
+		child.stdout?.on( 'data', ( chunk: Buffer ) =>
+			stdoutChunks.push( chunk )
+		);
+		child.stderr?.on( 'data', ( chunk: Buffer ) =>
+			stderrChunks.push( chunk )
+		);
+		child.on( 'exit', ( code ) => {
+			if ( code === 0 ) {
+				resolve( Buffer.concat( stdoutChunks ).toString( 'utf8' ) );
+				return;
+			}
+			const stderr = Buffer.concat( stderrChunks ).toString( 'utf8' );
+			reject(
+				new Error(
+					`composer ${ args.join( ' ' ) } exited ${ code }${
+						stderr ? `\n${ stderr }` : ''
+					}`
+				)
+			);
+		} );
+		child.on( 'error', reject );
+	} );
+}
+
+interface ComposerShowEntry {
+	name: string;
+	path: string;
+}
+
+interface ComposerShowOutput {
+	installed?: ComposerShowEntry[];
+}
+
+async function fetchPackageInstallPaths(
+	projectDir: string
+): Promise< Map< string, string > > {
+	const stdout = await execComposer(
+		[ 'show', '--path', '--format=json' ],
+		projectDir
+	);
+	const parsed = JSON.parse( stdout ) as ComposerShowOutput;
+	const paths = new Map< string, string >();
+	for ( const entry of parsed.installed ?? [] ) {
+		paths.set( entry.name, entry.path );
+	}
+	return paths;
+}
+
+async function resolvePackages(
+	composerJsonPath: string,
+	ignoredPackages: Set< string >,
+	packageInstallPaths: Map< string, string >
+): Promise< WatchedPackage[] > {
+	const projectDir = path.dirname( composerJsonPath );
+	const composer = await readJson< ComposerJson >( composerJsonPath );
+
+	const wanted = new Set< string >();
+	for ( const key of Object.keys( composer.require ?? {} ) ) {
+		if ( ! isPlatformReq( key ) ) wanted.add( key );
+	}
+	for ( const key of Object.keys( composer[ 'require-dev' ] ?? {} ) ) {
+		if ( ! isPlatformReq( key ) ) wanted.add( key );
+	}
+
+	const packages: WatchedPackage[] = [];
+
+	for ( const repo of composer.repositories ?? [] ) {
+		if ( repo.type !== 'path' || ! repo.url ) continue;
+
+		const sourceDir = path.resolve( projectDir, repo.url );
+		const pkgJsonPath = path.join( sourceDir, 'composer.json' );
+		if ( ! existsSync( pkgJsonPath ) ) continue;
+
+		const pkgJson = await readJson< ComposerJson >( pkgJsonPath );
+		if ( ! pkgJson.name || ! wanted.has( pkgJson.name ) ) continue;
+
+		// Composer's default for path repositories is "symlink if possible,
+		// copy otherwise" — so the only safe signal that Composer will copy
+		// (and therefore that we should mirror) is an explicit
+		// `symlink: false`.
+		//
+		// Explicit `symlink: true` is treated as a configuration error: the
+		// consuming project depends on the package, so an unmirrored symlink
+		// breaks environments that can't resolve it (wp-env mounts only the
+		// plugin dir). Throw so the failure surfaces at startup. The
+		// `ignoredPackages` allowlist opts a package out if the symlink is
+		// intentional for that consumer.
+		//
+		// Omitted `symlink` is ambiguous: Composer's behavior depends on the
+		// host filesystem. Warn and skip — mirroring on top of a symlink
+		// would just duplicate state.
+		if ( repo.options?.symlink === true ) {
+			if ( ignoredPackages.has( pkgJson.name ) ) continue;
+			throw new Error(
+				`path repository '${ pkgJson.name }' has symlink: true but the consuming project depends on it. ` +
+					`Set 'options: { symlink: false }' so Composer copies the package, or add it to ignoredPackages if the symlink is intentional.`
+			);
+		}
+		if ( repo.options?.symlink !== false ) {
+			if ( ignoredPackages.has( pkgJson.name ) ) continue;
+			log.warn(
+				'warn',
+				`path repository '${ pkgJson.name }' has no explicit symlink option; skipping. Set 'options: { symlink: false }' to enable mirroring, or add it to ignoredPackages to silence this warning.`
+			);
+			continue;
+		}
+
+		const destDir = packageInstallPaths.get( pkgJson.name );
+		if ( ! destDir ) {
+			throw new Error(
+				`Composer reports no install path for '${ pkgJson.name }'. Run 'composer install' in ${ projectDir } first.`
+			);
+		}
+
+		packages.push( {
+			name: pkgJson.name,
+			sourceDir,
+			destDir,
+		} );
+	}
+
+	return packages;
+}
+
+function packageFingerprint( pkgs: WatchedPackage[] ): string {
+	return createHash( 'sha1' )
+		.update(
+			JSON.stringify(
+				pkgs.map( ( p ) => [ p.name, p.sourceDir, p.destDir ] )
+			)
+		)
+		.digest( 'hex' );
+}
+
+function debounce( fn: () => Promise< void >, wait: number ): () => void {
+	let timer: NodeJS.Timeout | null = null;
+	let pending = false;
+	let running = false;
+
+	function schedule(): void {
+		if ( timer ) clearTimeout( timer );
+		timer = setTimeout( () => {
+			if ( running ) {
+				pending = true;
+				return;
+			}
+			running = true;
+			fn()
+				.catch( ( err: Error ) => log.error( 'error', err.message ) )
+				.finally( () => {
+					running = false;
+					if ( pending ) {
+						pending = false;
+						schedule();
+					}
+				} );
+		}, wait );
+	}
+
+	return schedule;
+}
+
+function runComposer( args: string[], projectDir: string ): Promise< void > {
+	return new Promise( ( resolve, reject ) => {
+		const child = spawn( 'composer', args, {
+			cwd: projectDir,
+			env: { ...process.env, XDEBUG_MODE: 'off' },
+			stdio: 'inherit',
+		} );
+		child.on( 'exit', ( code ) =>
+			code === 0
+				? resolve()
+				: reject(
+						new Error(
+							`composer ${ args.join( ' ' ) } exited ${ code }`
+						)
+				  )
+		);
+		child.on( 'error', reject );
+	} );
+}
+
+async function mirror( srcAbs: string, pkg: WatchedPackage ): Promise< void > {
+	const rel = path.relative( pkg.sourceDir, srcAbs );
+	const dest = path.join( pkg.destDir, rel );
+	await mkdir( path.dirname( dest ), { recursive: true } );
+	await copyFile( srcAbs, dest );
+}
+
+async function removeMirror(
+	srcAbs: string,
+	pkg: WatchedPackage
+): Promise< void > {
+	const rel = path.relative( pkg.sourceDir, srcAbs );
+	const dest = path.join( pkg.destDir, rel );
+	await rm( dest, { force: true } );
+}
+
+export async function watchComposerPackages(
+	options: ComposerPackageWatcherOptions
+): Promise< void > {
+	const composerJsonPath = path.resolve( options.composerJson );
+	const projectDir = path.dirname( composerJsonPath );
+	const ignoredPackages = new Set( options.ignoredPackages ?? [] );
+
+	const startWatching = async (): Promise< void > => {
+		const packageInstallPaths = await fetchPackageInstallPaths(
+			projectDir
+		);
+		const packages = await resolvePackages(
+			composerJsonPath,
+			ignoredPackages,
+			packageInstallPaths
+		);
+		if ( packages.length === 0 ) {
+			log.info(
+				'watch',
+				'no copy-mode path-repository packages to watch'
+			);
+			return;
+		}
+
+		const fingerprint = packageFingerprint( packages );
+		log.info(
+			'watch',
+			`watching ${ packages.length } package(s): ${ packages
+				.map( ( p ) => p.name )
+				.join( ', ' ) }`
+		);
+
+		const findPackageFor = (
+			absPath: string
+		): WatchedPackage | undefined =>
+			packages.find(
+				( pkg ) =>
+					absPath === pkg.sourceDir ||
+					absPath.startsWith( pkg.sourceDir + path.sep )
+			);
+
+		const dumpAutoload = debounce( async () => {
+			log.info( 'watch', 'composer dump-autoload' );
+			await runComposer(
+				[ 'dump-autoload', '--quiet', '--no-scripts' ],
+				projectDir
+			);
+		}, RESTART_DEBOUNCE_MS );
+
+		// Watch each package's source directory whole — Composer copies the
+		// directory verbatim on install, so the watcher mirrors any file event
+		// to match. Plus the consuming project's own composer.json, so a new
+		// path-repo declaration triggers a re-install + restart.
+		const allPaths = [
+			composerJsonPath,
+			...packages.map( ( p ) => p.sourceDir ),
+		];
+
+		const watcher = chokidar.watch( allPaths, {
+			ignoreInitial: true,
+			// Avoid watching directories that won't be used for source files.
+			ignored: ( p: string ) =>
+				/(\/|^)(node_modules|\.git|vendor|changelog)(\/|$)/.test( p ),
+			awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 20 },
+		} );
+
+		let composerInstallRunning = false;
+		const onComposerJsonChange = async (): Promise< void > => {
+			if ( composerInstallRunning ) return;
+			composerInstallRunning = true;
+			log.info( 'watch', 'composer.json changed; composer install' );
+			try {
+				await runComposer( [ 'install', '--quiet' ], projectDir );
+			} catch ( err ) {
+				log.error( 'error', ( err as Error ).message );
+				composerInstallRunning = false;
+				return;
+			}
+			composerInstallRunning = false;
+			const projectInstallPaths = await fetchPackageInstallPaths(
+				projectDir
+			);
+			const next = await resolvePackages(
+				composerJsonPath,
+				ignoredPackages,
+				projectInstallPaths
+			);
+			if ( packageFingerprint( next ) !== fingerprint ) {
+				log.info( 'watch', 'package set changed; restarting watcher' );
+				await watcher.close();
+				startWatching().catch( ( err: Error ) => {
+					log.error( 'error', `restart failed: ${ err.message }` );
+					process.exit( 1 );
+				} );
+			}
+		};
+
+		const handle = async (
+			event: 'add' | 'change' | 'unlink',
+			absPath: string
+		): Promise< void > => {
+			// Consuming project's composer.json is its own path; not in any pkg.
+			if ( absPath === composerJsonPath ) {
+				if ( event === 'change' || event === 'add' )
+					onComposerJsonChange();
+				return;
+			}
+
+			const pkg = findPackageFor( absPath );
+			if ( ! pkg ) return;
+
+			// A watched package's own composer.json changing means its autoload
+			// spec may have changed. Mirror it and treat as a project-level
+			// composer.json change (re-install + maybe restart).
+			if (
+				path.basename( absPath ) === 'composer.json' &&
+				absPath === path.join( pkg.sourceDir, 'composer.json' )
+			) {
+				await mirror( absPath, pkg ).catch( () => undefined );
+				onComposerJsonChange();
+				return;
+			}
+
+			try {
+				if ( event === 'unlink' ) {
+					await removeMirror( absPath, pkg );
+					log.info(
+						'watch',
+						`unlink ${ pkg.name }/${ path.relative(
+							pkg.sourceDir,
+							absPath
+						) }`
+					);
+				} else {
+					await mirror( absPath, pkg );
+					log.info(
+						'watch',
+						`${ event } ${ pkg.name }/${ path.relative(
+							pkg.sourceDir,
+							absPath
+						) }`
+					);
+				}
+			} catch ( err ) {
+				log.error(
+					'error',
+					`mirror failed for ${ absPath }: ${
+						( err as Error ).message
+					}`
+				);
+				return;
+			}
+
+			// Keep the autoloader up-to-date when the files available change.
+			if ( event === 'add' || event === 'unlink' ) {
+				dumpAutoload();
+			}
+		};
+
+		watcher
+			.on( 'add', ( p: string ) => void handle( 'add', p ) )
+			.on( 'change', ( p: string ) => void handle( 'change', p ) )
+			.on( 'unlink', ( p: string ) => void handle( 'unlink', p ) )
+			.on( 'error', ( err: unknown ) =>
+				log.error( 'error', ( err as Error ).message )
+			);
+	};
+
+	await startWatching();
+}
diff --git a/packages/js/internal-build/src/esbuild/index.ts b/packages/js/internal-build/src/esbuild/index.ts
index eadb563e68a..5892cf73c72 100644
--- a/packages/js/internal-build/src/esbuild/index.ts
+++ b/packages/js/internal-build/src/esbuild/index.ts
@@ -17,7 +17,7 @@ import {
 	type BuildOptions,
 } from './options.js';
 import { copyAssets } from './assets.js';
-import { log, setDebugEnabled } from './logger.js';
+import { log, setDebugEnabled } from '../utils/logger.js';

 export type { BuildOptions } from './options.js';
 export { parseBuildArgs } from './args.js';
diff --git a/packages/js/internal-build/src/index.ts b/packages/js/internal-build/src/index.ts
index cdf5514f016..ba06d10c788 100644
--- a/packages/js/internal-build/src/index.ts
+++ b/packages/js/internal-build/src/index.ts
@@ -4,3 +4,6 @@ export {
 	watchPackage,
 } from './esbuild/index.js';
 export type { BuildOptions } from './esbuild/index.js';
+
+export { watchComposerPackages } from './composer/index.js';
+export type { ComposerPackageWatcherOptions } from './composer/index.js';
diff --git a/packages/js/internal-build/src/esbuild/logger.ts b/packages/js/internal-build/src/utils/logger.ts
similarity index 100%
rename from packages/js/internal-build/src/esbuild/logger.ts
rename to packages/js/internal-build/src/utils/logger.ts
diff --git a/packages/js/internal-js-tests/package.json b/packages/js/internal-js-tests/package.json
index 1839e06e4f4..bb2b1f0ffb9 100644
--- a/packages/js/internal-js-tests/package.json
+++ b/packages/js/internal-js-tests/package.json
@@ -35,13 +35,13 @@
 		"jest-preset.js"
 	],
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -50,8 +50,8 @@
 		"lint:lang:types": "tsc --build --emitDeclarationOnly",
 		"prepack": "pnpm build:publish:project",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
 	"dependencies": {
diff --git a/packages/js/navigation/package.json b/packages/js/navigation/package.json
index 915078960d0..eaeabff7129 100644
--- a/packages/js/navigation/package.json
+++ b/packages/js/navigation/package.json
@@ -38,13 +38,13 @@
 		"build-types"
 	],
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -54,8 +54,8 @@
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
 	"lint-staged": {
diff --git a/packages/js/notices/package.json b/packages/js/notices/package.json
index 999e4e9b9cb..0158e2732fe 100644
--- a/packages/js/notices/package.json
+++ b/packages/js/notices/package.json
@@ -39,13 +39,13 @@
 		"build-types"
 	],
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -54,8 +54,8 @@
 		"lint:lang:types": "tsc --build --emitDeclarationOnly",
 		"prepack": "pnpm build:publish:project",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
 	"lint-staged": {
diff --git a/packages/js/number/package.json b/packages/js/number/package.json
index 3031725e584..3bf5e073d9e 100644
--- a/packages/js/number/package.json
+++ b/packages/js/number/package.json
@@ -50,13 +50,13 @@
 		}
 	},
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -66,8 +66,8 @@
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
 	"devDependencies": {
diff --git a/packages/js/onboarding/package.json b/packages/js/onboarding/package.json
index 231e605db6e..e43df3cb422 100644
--- a/packages/js/onboarding/package.json
+++ b/packages/js/onboarding/package.json
@@ -39,14 +39,14 @@
 		"build-types"
 	],
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:bundle": "NODE_ENV=production webpack",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -56,8 +56,8 @@
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:bundle": "NODE_ENV=development webpack --watch",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
diff --git a/packages/js/product-editor/config/block-entry-points.js b/packages/js/product-editor/config/block-entry-points.js
index dd8ba8e60dd..f54cfb58ba8 100644
--- a/packages/js/product-editor/config/block-entry-points.js
+++ b/packages/js/product-editor/config/block-entry-points.js
@@ -5,7 +5,7 @@ const fs = require( 'fs' );
 const path = require( 'path' );
 const { sync: glob } = require( 'fast-glob' );

-const srcDir = path.resolve( process.cwd(), 'src' );
+const srcDir = path.resolve( __dirname, '..', 'src' );
 const blocksBuildDir = '/build/blocks';

 /**
diff --git a/packages/js/product-editor/package.json b/packages/js/product-editor/package.json
index 9de870f6455..8bec8697ab8 100644
--- a/packages/js/product-editor/package.json
+++ b/packages/js/product-editor/package.json
@@ -136,8 +136,8 @@
 		"webpack-remove-empty-scripts": "1.0.x"
 	},
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:bundle": "NODE_ENV=production webpack",
 		"build:project:cjs": "node build.mjs --commonjs",
 		"build:project:esm": "node build.mjs",
@@ -147,12 +147,14 @@
 		"lint:fix:lang:js": "eslint src --fix",
 		"lint:lang:js": "eslint src",
 		"lint:lang:types": "tsc --build --emitDeclarationOnly",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"prepack": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
+		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:bundle": "NODE_ENV=development webpack --watch",
 		"watch:build:project:cjs": "node build.mjs --commonjs --watch",
 		"watch:build:project:esm": "node build.mjs --watch"
diff --git a/packages/js/remote-logging/package.json b/packages/js/remote-logging/package.json
index e16b53fa9c4..e7f954972b5 100644
--- a/packages/js/remote-logging/package.json
+++ b/packages/js/remote-logging/package.json
@@ -37,13 +37,13 @@
 		"build-types"
 	],
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -53,8 +53,8 @@
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
 	"lint-staged": {
diff --git a/packages/js/sanitize/package.json b/packages/js/sanitize/package.json
index a9500688d4c..6f7fdb2dde8 100644
--- a/packages/js/sanitize/package.json
+++ b/packages/js/sanitize/package.json
@@ -74,13 +74,13 @@
 		}
 	},
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -90,8 +90,8 @@
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
 	"lint-staged": {
diff --git a/packages/js/settings-ui-sdk/package.json b/packages/js/settings-ui-sdk/package.json
index 5a8a32c949d..da415ae84d2 100644
--- a/packages/js/settings-ui-sdk/package.json
+++ b/packages/js/settings-ui-sdk/package.json
@@ -72,13 +72,13 @@
 		}
 	},
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
 		"lint:fix:lang:js": "eslint src --fix",
@@ -86,8 +86,8 @@
 		"lint:lang:types": "tsc --build --emitDeclarationOnly",
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
 	"config": {
diff --git a/packages/js/tracks/package.json b/packages/js/tracks/package.json
index 5f526f5af19..bce9f20e799 100644
--- a/packages/js/tracks/package.json
+++ b/packages/js/tracks/package.json
@@ -51,13 +51,13 @@
 		}
 	},
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --stream --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"build:project:esm": "node build.mjs",
-		"build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+		"build:publish:project": "pnpm --stream '/^build:publish:project:.*$/'",
 		"build:publish:project:cjs": "node build.mjs --commonjs",
 		"build:publish:project:types": "tsc --build --emitDeclarationOnly",
-		"build:publish:project:deps": "pnpm build",
+		"build:publish:project:runtime": "pnpm build:project",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
@@ -67,8 +67,8 @@
 		"prepack": "pnpm build:publish:project",
 		"test:js": "jest --config ./jest.config.json --passWithNoTests",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
 		"watch:build:project:esm": "node build.mjs --watch"
 	},
 	"devDependencies": {
diff --git a/plugins/woocommerce-beta-tester/package.json b/plugins/woocommerce-beta-tester/package.json
index 484087d4166..dad07ac2ac1 100644
--- a/plugins/woocommerce-beta-tester/package.json
+++ b/plugins/woocommerce-beta-tester/package.json
@@ -23,8 +23,7 @@
 		"prettier": "npm:wp-prettier@^2.8.5",
 		"ts-loader": "9.5.x",
 		"typescript": "5.7.x",
-		"uglify-js": "^3.17.4",
-		"wireit": "0.14.12"
+		"uglify-js": "^3.17.4"
 	},
 	"dependencies": {
 		"@emotion/react": "^11.11.1",
@@ -65,7 +64,7 @@
 		"build_step": "pnpm run build:zip"
 	},
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/' && pnpm build:project",
+		"build": "pnpm build:project",
 		"build:project": "pnpm build:admin && pnpm uglify",
 		"build:admin": "wp-scripts build",
 		"build:dev": "pnpm lint:js && pnpm build",
diff --git a/plugins/woocommerce/bin/watch-packages.mjs b/plugins/woocommerce/bin/watch-packages.mjs
new file mode 100644
index 00000000000..22e763f5c26
--- /dev/null
+++ b/plugins/woocommerce/bin/watch-packages.mjs
@@ -0,0 +1,7 @@
+#!/usr/bin/env node
+import path from 'node:path';
+import { watchComposerPackages } from '@woocommerce/internal-build';
+
+await watchComposerPackages( {
+	composerJson: path.join( import.meta.dirname, '..', 'composer.json' ),
+} );
diff --git a/plugins/woocommerce/client/admin/package.json b/plugins/woocommerce/client/admin/package.json
index 2f03e67f8e2..5236f880623 100644
--- a/plugins/woocommerce/client/admin/package.json
+++ b/plugins/woocommerce/client/admin/package.json
@@ -13,9 +13,9 @@
 		"build"
 	],
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --if-present '/^build:project:.*$/'",
-		"build:project:bundle": "wireit",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
+		"build:project:bundle": "NODE_ENV=\"${NODE_ENV:-production}\" WC_ADMIN_PHASE=\"${WC_ADMIN_PHASE:-core}\" webpack",
 		"build:project:feature-config": "php ../../bin/generate-feature-config.php",
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
@@ -30,9 +30,9 @@
 		"postinstall": "node bin/xstate5-symlink.js",
 		"test:js": "jest --config client/jest.config.js",
 		"ts:check": "tsc --build",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
-		"watch:build:project:bundle": "wireit",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
+		"watch:build:project:bundle": "webpack --watch",
 		"watch:build:project:feature-config": "WC_ADMIN_PHASE=development php ../../bin/generate-feature-config.php"
 	},
 	"lint-staged": {
@@ -221,8 +221,7 @@
 		"webpack-cli": "5.1.x",
 		"webpack-dev-server": "4.15.x",
 		"webpack-fix-style-only-entries": "^0.6.1",
-		"whatwg-fetch": "^3.6.20",
-		"wireit": "0.14.12"
+		"whatwg-fetch": "^3.6.20"
 	},
 	"peerDependencies": {
 		"@wordpress/data": "^10.0.2"
@@ -254,110 +253,5 @@
 				}
 			]
 		}
-	},
-	"wireit": {
-		"build:project:bundle": {
-			"command": "webpack",
-			"clean": "if-file-deleted",
-			"env": {
-				"NODE_ENV": {
-					"external": true,
-					"default": "production"
-				},
-				"WC_ADMIN_PHASE": {
-					"external": true,
-					"default": "core"
-				}
-			},
-			"files": [
-				"webpack.config.js",
-				"tsconfig.json",
-				"client/**/*.{js,jsx,ts,tsx,scss}"
-			],
-			"output": [
-				"build"
-			],
-			"dependencies": [
-				"dependencyOutputs"
-			]
-		},
-		"watch:build:project:bundle": {
-			"command": "webpack --watch",
-			"service": true
-		},
-		"dependencyOutputs": {
-			"allowUsuallyExcludedPaths": true,
-			"files": [
-				"package.json",
-				"../../../../pnpm-lock.yaml",
-				"node_modules/@woocommerce/admin-layout/build",
-				"node_modules/@woocommerce/admin-layout/build-style",
-				"node_modules/@woocommerce/admin-layout/build-types",
-				"node_modules/@woocommerce/components/build",
-				"node_modules/@woocommerce/components/build-style",
-				"node_modules/@woocommerce/components/build-types",
-				"node_modules/@woocommerce/csv-export/build",
-				"node_modules/@woocommerce/csv-export/build-types",
-				"node_modules/@woocommerce/currency/build",
-				"node_modules/@woocommerce/currency/build-types",
-				"node_modules/@woocommerce/customer-effort-score/build",
-				"node_modules/@woocommerce/customer-effort-score/build-style",
-				"node_modules/@woocommerce/customer-effort-score/build-types",
-				"node_modules/@woocommerce/data/build",
-				"node_modules/@woocommerce/data/build-types",
-				"node_modules/@woocommerce/date/build",
-				"node_modules/@woocommerce/date/build-types",
-				"node_modules/@woocommerce/email-editor/build",
-				"node_modules/@woocommerce/email-editor/build-style",
-				"node_modules/@woocommerce/email-editor/build-types",
-				"node_modules/@woocommerce/email-editor/assets",
-				"node_modules/@woocommerce/experimental/build",
-				"node_modules/@woocommerce/experimental/build-style",
-				"node_modules/@woocommerce/experimental/build-types",
-				"node_modules/@woocommerce/explat/build",
-				"node_modules/@woocommerce/explat/build-types",
-				"node_modules/@woocommerce/settings-ui-sdk/build",
-				"node_modules/@woocommerce/settings-ui-sdk/build-types",
-				"node_modules/@woocommerce/navigation/build",
-				"node_modules/@woocommerce/navigation/build-types",
-				"node_modules/@woocommerce/number/build",
-				"node_modules/@woocommerce/number/build-types",
-				"node_modules/@woocommerce/onboarding/build",
-				"node_modules/@woocommerce/onboarding/build-style",
-				"node_modules/@woocommerce/onboarding/build-types",
-				"node_modules/@woocommerce/product-editor/build",
-				"node_modules/@woocommerce/product-editor/build-style",
-				"node_modules/@woocommerce/product-editor/build-types",
-				"node_modules/@woocommerce/remote-logging/build",
-				"node_modules/@woocommerce/remote-logging/build-types",
-				"node_modules/@woocommerce/sanitize/build",
-				"node_modules/@woocommerce/sanitize/build-style",
-				"node_modules/@woocommerce/sanitize/build-types",
-				"node_modules/@woocommerce/tracks/build",
-				"node_modules/@woocommerce/tracks/build-types",
-				"node_modules/@woocommerce/block-templates/build",
-				"node_modules/@woocommerce/block-templates/build-style",
-				"node_modules/@woocommerce/block-templates/build-types",
-				"node_modules/@woocommerce/dependency-extraction-webpack-plugin/src/",
-				"node_modules/@woocommerce/dependency-extraction-webpack-plugin/assets/",
-				"node_modules/@woocommerce/eslint-plugin/configs",
-				"node_modules/@woocommerce/eslint-plugin/rules",
-				"node_modules/@woocommerce/eslint-plugin/index.js",
-				"node_modules/@woocommerce/experimental-products-app/build",
-				"node_modules/@woocommerce/experimental-products-app/build-style",
-				"node_modules/@woocommerce/experimental-products-app/build-types",
-				"node_modules/@woocommerce/internal-build/build-types",
-				"node_modules/@woocommerce/internal-build/configs",
-				"node_modules/@woocommerce/internal-build/src/style-build",
-				"node_modules/@woocommerce/internal-build/styles",
-				"node_modules/@woocommerce/internal-build/type-overrides",
-				"node_modules/@woocommerce/internal-build/scripts",
-				"node_modules/@woocommerce/internal-js-tests/build",
-				"node_modules/@woocommerce/internal-js-tests/build-types",
-				"node_modules/@woocommerce/internal-js-tests/jest-preset.js",
-				"node_modules/@woocommerce/notices/build",
-				"node_modules/@woocommerce/notices/build-types"
-			]
-		}
 	}
 }
diff --git a/plugins/woocommerce/client/admin/webpack.config.js b/plugins/woocommerce/client/admin/webpack.config.js
index 46346bb4b20..f5626fca174 100644
--- a/plugins/woocommerce/client/admin/webpack.config.js
+++ b/plugins/woocommerce/client/admin/webpack.config.js
@@ -36,6 +36,11 @@ const getSubdirectoriesAt = ( searchPath ) => {
 const WC_ADMIN_PACKAGES_DIR = '../../../../packages/js';
 const WP_ADMIN_SCRIPTS_DIR = './client/wp-admin-scripts';

+// Admin writes directly to the plugin's `assets/client/admin/` so PHP can
+// enqueue without an intermediate copy step. The JS config and every composed
+// package CSS config use this constant for `output.path`.
+const BUILD_DIR = path.resolve( __dirname, '../../assets/client/admin' );
+
 // wpAdminScripts are loaded on wp-admin pages outside the context of WooCommerce Admin
 // See ./client/wp-admin-scripts/README.md for more details
 const wpAdminScripts = getSubdirectoriesAt( WP_ADMIN_SCRIPTS_DIR ); // automatically include all subdirs
@@ -64,13 +69,52 @@ const wcAdminPackages = [
 	'email-editor',
 ];

+// Resolve each entry to the package's `wc-source` export as an absolute path.
+// Using the package name (`@woocommerce/<name>`) as the entry request would
+// match WooCommerceDependencyExtractionWebpackPlugin's externals list and
+// collapse the entry into a `window.wc.<name>` shim that re-exports itself.
+// Pointing entries at the filesystem dodges that match while still letting
+// transitive `@woocommerce/*` imports inside the bundle externalize normally.
+const resolvePackageSourceEntry = ( name ) => {
+	const pkgJsonPath = path.resolve(
+		__dirname,
+		`${ WC_ADMIN_PACKAGES_DIR }/${ name }/package.json`
+	);
+	const pkgJson = require( pkgJsonPath );
+	const source = pkgJson.exports?.[ '.' ]?.[ 'wc-source' ];
+	if ( ! source ) {
+		throw new Error(
+			`Package @woocommerce/${ name } has no exports["."]["wc-source"] entry in ${ pkgJsonPath }`
+		);
+	}
+	return path.resolve( path.dirname( pkgJsonPath ), source );
+};
+
+// Packages opt into having admin bundle their stylesheet by exporting a
+// `src/style.scss` next to `src/index.ts`. The file isn't imported from
+// `src/index.ts` (consumers historically relied on a separate `style.css`
+// asset), so we add it as a second entry tuple. Webpack bundles both into
+// the same chunk and MiniCssExtractPlugin emits `<pkg>/style.css`.
+const resolvePackageStyleEntry = ( name ) => {
+	const styleScss = path.resolve(
+		__dirname,
+		`${ WC_ADMIN_PACKAGES_DIR }/${ name }/src/style.scss`
+	);
+	return fs.existsSync( styleScss ) ? styleScss : null;
+};
+
 const getEntryPoints = () => {
 	const entryPoints = {
 		app: './client/index.tsx',
 		embed: './client/embed.tsx',
 	};
 	wcAdminPackages.forEach( ( name ) => {
-		entryPoints[ name ] = `${ WC_ADMIN_PACKAGES_DIR }/${ name }`;
+		const source = resolvePackageSourceEntry( name );
+		const style = resolvePackageStyleEntry( name );
+		// Order matters: webpack uses the last item in an array entry as the
+		// chunk's export source. Stylesheet first so `src/index.ts`'s exports
+		// land on the `window.wc.<name>` global.
+		entryPoints[ name ] = style ? [ style, source ] : source;
 	} );
 	wpAdminScripts.forEach( ( name ) => {
 		entryPoints[ name ] = `${ WP_ADMIN_SCRIPTS_DIR }/${ name }`;
@@ -81,7 +125,8 @@ const getEntryPoints = () => {
 // WordPress.org’s translation infrastructure ignores files named “.min.js” so we need to name our JS files without min when releasing the plugin.
 const outputSuffix = WC_ADMIN_PHASE === 'core' ? '' : '.min';

-const webpackConfig = {
+const jsConfig = {
+	name: 'admin-js',
 	mode: NODE_ENV,
 	performance: {
 		hints: false,
@@ -93,7 +138,7 @@ const webpackConfig = {
 					type: 'filesystem',
 					cacheDirectory: path.resolve(
 						__dirname,
-						`node_modules/.cache/webpack-${ WC_ADMIN_PHASE }`
+						`node_modules/.cache/webpack-${ WC_ADMIN_PHASE }-source`
 					),
 					buildDependencies: {
 						config: [
@@ -121,7 +166,7 @@ const webpackConfig = {
 				: `[name]/index${ outputSuffix }.js`;
 		},
 		chunkFilename: `chunks/[name]${ outputSuffix }.js?ver=[contenthash]`,
-		path: path.join( __dirname, '/build' ),
+		path: BUILD_DIR,
 		library: {
 			// Expose the exports of entry points so we can consume the libraries in window.wc.[modulename] with WooCommerceDependencyExtractionWebpackPlugin.
 			name: [ 'wc', '[modulename]' ],
@@ -186,6 +231,14 @@ const webpackConfig = {
 			path: false,
 		},
 		extensions: [ '.json', '.js', '.jsx', '.ts', '.tsx' ],
+		// Activate the `"wc-source"` conditional export declared in each
+		// `packages/js/*` package.json. Webpack walks the package's exports map
+		// and picks `./src/index.ts` directly — no per-package alias is
+		// required, and transitive `@woocommerce/*` imports resolve to source
+		// through the same mechanism. The condition is namespaced (`wc-` prefix)
+		// so it never collides with third-party packages that publish their own
+		// `"source"` conditional export. `'...'` extends the default list.
+		conditionNames: [ 'wc-source', '...' ],
 		alias: {
 			'~': path.resolve( __dirname + '/client' ),
 			'react/jsx-dev-runtime': require.resolve( 'react/jsx-dev-runtime' ),
@@ -213,28 +266,18 @@ const webpackConfig = {
 				return outputPath;
 			},
 		} ),
-		// The package build process doesn't handle extracting CSS from JS files, so we copy them separately.
-		new CopyWebpackPlugin( {
-			patterns: wcAdminPackages.map( ( packageName ) => ( {
-				// Copy css and style.asset.php files.
-				from: `../../../../packages/js/${ packageName }/build-style/*.{css,php}`,
-				to: `./${ packageName }/[name][ext]`,
-				noErrorOnMissing: true,
-				// Overwrites files already in compilation.assets to ensure we use the assets from the build-style.
-				// This is required for @woocommerce/component to use @automattic/* packages because scss styles from @automattic/* packages will be automatically generated by mini-css-extract-plugin with the same output name.
-				force: true,
-			} ) ),
-		} ),

-		// Get all product editor blocks so they can be loaded via JSON.
+		// product-editor's block.json files come straight from source so PHP's
+		// BlockRegistry can load them without a separate package build.
 		new CopyWebpackPlugin( {
 			patterns: [
 				{
-					from: path.join(
+					context: path.join(
 						__dirname,
-						'../../../../packages/js/product-editor/build/blocks'
+						'../../../../packages/js/product-editor/src/blocks'
 					),
-					to: './product-editor/blocks',
+					from: '**/block.json',
+					to: './product-editor/blocks/[path][name][ext]',
 				},
 			],
 		} ),
@@ -257,6 +300,15 @@ const webpackConfig = {
 							// See https://github.com/WordPress/gutenberg/pull/61692 for more details about the dependency in general.
 							// For backward compatibility reasons we need to skip requesting to external here.
 							return null;
+						case 'react-dom/client':
+							// React 18 split createRoot/hydrateRoot into
+							// react-dom/client. WordPress's wp-react-dom UMD
+							// aggregates both entrypoints onto the same
+							// window.ReactDOM global. DEWP's default mapper
+							// doesn't know about the subpath yet
+							// (https://github.com/WordPress/gutenberg/pull/77326),
+							// so map it here.
+							return 'ReactDOM';
 						case '@wordpress/global-styles-engine':
 							// @wordpress/global-styles-engine is not a standard WordPress package available globally,
 							// so we need to bundle it instead of treating it as an external.
@@ -295,6 +347,9 @@ const webpackConfig = {
 					if ( request === 'moment-timezone' ) {
 						return 'moment';
 					}
+					if ( request === 'react-dom/client' ) {
+						return 'react-dom';
+					}
 				},
 			} ),
 		process.env.ANALYZE && new BundleAnalyzerPlugin(),
@@ -317,12 +372,12 @@ const webpackConfig = {
 };
 if ( ! isProduction || WC_ADMIN_PHASE === 'development' ) {
 	// Set default sourcemap mode if it wasn't set by WP_DEVTOOL.
-	webpackConfig.devtool = webpackConfig.devtool || 'source-map';
+	jsConfig.devtool = jsConfig.devtool || 'source-map';

 	if ( isHot ) {
 		// Add dev server config
 		// Copied from https://github.com/WordPress/gutenberg/blob/05bea6dd5c6198b0287c41a401d36a06b48831eb/packages/scripts/config/webpack.config.js#L312-L326
-		webpackConfig.devServer = {
+		jsConfig.devServer = {
 			devMiddleware: {
 				writeToDisk: true,
 			},
@@ -330,9 +385,9 @@ if ( ! isProduction || WC_ADMIN_PHASE === 'development' ) {
 			host: 'localhost',
 			port: 8887,
 			proxy: {
-				'/build': {
+				'/assets/client/admin': {
 					pathRewrite: {
-						'^/build': '',
+						'^/assets/client/admin': '',
 					},
 				},
 			},
@@ -340,4 +395,4 @@ if ( ! isProduction || WC_ADMIN_PHASE === 'development' ) {
 	}
 }

-module.exports = webpackConfig;
+module.exports = jsConfig;
diff --git a/plugins/woocommerce/client/blocks/CLAUDE.md b/plugins/woocommerce/client/blocks/CLAUDE.md
index 9e77f51192a..e2784f3788a 100644
--- a/plugins/woocommerce/client/blocks/CLAUDE.md
+++ b/plugins/woocommerce/client/blocks/CLAUDE.md
@@ -182,7 +182,7 @@ Webpack is configured with **11 separate configs** in `bin/webpack-configs.js`:

 - Core, Main, Frontend, Extensions, Payments, Styling, Site Editor, Interactivity, Cart/Checkout Frontend, Dependency Detection

-Build is orchestrated by **wireit** for caching. TypeScript uses **60+ path aliases** defined in `tsconfig.base.json`.
+Webpack writes directly to `plugins/woocommerce/assets/client/blocks/` so PHP enqueues run against the final asset locations with no copy step. TypeScript uses **60+ path aliases** defined in `tsconfig.base.json`.

 ## Testing

diff --git a/plugins/woocommerce/client/blocks/bin/webpack-config-dependency-detection.js b/plugins/woocommerce/client/blocks/bin/webpack-config-dependency-detection.js
index c6205bf42a6..dc86016bedd 100644
--- a/plugins/woocommerce/client/blocks/bin/webpack-config-dependency-detection.js
+++ b/plugins/woocommerce/client/blocks/bin/webpack-config-dependency-detection.js
@@ -15,8 +15,10 @@ const FilesystemCacheWarningsPlugin = require( './filesystem-cache-warnings-webp
 const { getProgressBarPluginConfig } = require( './webpack-helpers' );

 const ROOT_DIR = path.resolve( __dirname, '../../../../../' );
-// Output to the standard blocks build directory (gitignored).
-const BUILD_DIR = path.resolve( __dirname, '../build/' );
+// Blocks' webpack writes directly to the WooCommerce plugin's
+// `assets/client/blocks/` so PHP can enqueue files from their final location
+// without an intermediate rsync step.
+const BUILD_DIR = path.resolve( __dirname, '../../../assets/client/blocks' );
 const BABEL_CACHE_DIR = path.join(
 	ROOT_DIR,
 	'node_modules/.cache/babel-loader'
diff --git a/plugins/woocommerce/client/blocks/bin/webpack-config-interactive-blocks.js b/plugins/woocommerce/client/blocks/bin/webpack-config-interactive-blocks.js
index e6ff03045cd..5ce78447563 100644
--- a/plugins/woocommerce/client/blocks/bin/webpack-config-interactive-blocks.js
+++ b/plugins/woocommerce/client/blocks/bin/webpack-config-interactive-blocks.js
@@ -9,6 +9,16 @@ const [
 ] = require( '@wordpress/scripts/config/webpack.config' );
 const RemoveFilesPlugin = require( './remove-files-webpack-plugin' );

+/**
+ * Internal dependencies
+ */
+const { getResolve } = require( './webpack-helpers' );
+
+// Blocks' webpack writes directly to the WooCommerce plugin's
+// `assets/client/blocks/` so PHP can enqueue files from their final location
+// without an intermediate rsync step.
+const BUILD_DIR = path.resolve( __dirname, '../../../assets/client/blocks' );
+
 /**
  * Internal dependencies
  */
@@ -65,13 +75,14 @@ module.exports = {
 		library: {
 			type: 'module',
 		},
-		path: path.resolve( __dirname, '../build/' ),
+		path: BUILD_DIR,
 		asyncChunks: false,
 		chunkFormat: 'module',
 		environment: { module: true },
 		module: true,
 	},
 	resolve: {
+		...getResolve(),
 		extensions: [ '.js', '.ts', '.tsx' ],
 	},
 	plugins: [
@@ -89,7 +100,9 @@ module.exports = {
 		} ),
 		new WebpackRTLPlugin(),
 		// Remove JS files generated by MiniCssExtractPlugin.
-		new RemoveFilesPlugin( './build/**/*-@(editor|style).js' ),
+		new RemoveFilesPlugin(
+			path.join( BUILD_DIR, '**/*-@(editor|style).js' )
+		),
 		// Suppress file system cache warnings (unsupported serialization related).
 		new FilesystemCacheWarningsPlugin(),
 	],
diff --git a/plugins/woocommerce/client/blocks/bin/webpack-configs.js b/plugins/woocommerce/client/blocks/bin/webpack-configs.js
index 2d3f0d675b0..0d45d2ee522 100644
--- a/plugins/woocommerce/client/blocks/bin/webpack-configs.js
+++ b/plugins/woocommerce/client/blocks/bin/webpack-configs.js
@@ -29,12 +29,16 @@ const {
 	requestToHandle,
 	getProgressBarPluginConfig,
 	getCacheGroups,
+	getResolve,
 } = require( './webpack-helpers' );
 const AddSplitChunkDependencies = require( './add-split-chunk-dependencies' );
 const { sharedOptimizationConfig } = require( './webpack-shared-config' );

 const ROOT_DIR = path.resolve( __dirname, '../../../../../' );
-const BUILD_DIR = path.resolve( __dirname, '../build/' );
+// Blocks' webpack writes directly to the WooCommerce plugin's
+// `assets/client/blocks/` so PHP can enqueue files from their final location
+// without an intermediate rsync step.
+const BUILD_DIR = path.resolve( __dirname, '../../../assets/client/blocks' );
 const BABEL_CACHE_DIR = path.join(
 	ROOT_DIR,
 	'node_modules/.cache/babel-loader'
@@ -88,14 +92,7 @@ const getSharedPlugins = ( {
  */
 const getCoreConfig = ( options = {} ) => {
 	const { alias, resolvePlugins = [] } = options;
-	const resolve = alias
-		? {
-				alias,
-				plugins: resolvePlugins,
-		  }
-		: {
-				plugins: resolvePlugins,
-		  };
+	const resolve = getResolve( { alias, resolvePlugins } );
 	return {
 		entry: getEntryConfig( 'core', options.exclude || [] ),
 		output: {
@@ -162,14 +159,7 @@ const getCoreConfig = ( options = {} ) => {
 const getMainConfig = ( options = {} ) => {
 	const { alias, resolvePlugins = [] } = options;

-	const resolve = alias
-		? {
-				alias,
-				plugins: resolvePlugins,
-		  }
-		: {
-				plugins: resolvePlugins,
-		  };
+	const resolve = getResolve( { alias, resolvePlugins } );
 	return {
 		entry: getEntryConfig( 'main', options.exclude || [] ),
 		output: {
@@ -284,14 +274,7 @@ const getMainConfig = ( options = {} ) => {
  */
 const getFrontConfig = ( options = {} ) => {
 	const { alias, resolvePlugins = [] } = options;
-	const resolve = alias
-		? {
-				alias,
-				plugins: resolvePlugins,
-		  }
-		: {
-				plugins: resolvePlugins,
-		  };
+	const resolve = getResolve( { alias, resolvePlugins } );
 	return {
 		entry: getEntryConfig( 'frontend', options.exclude || [] ),
 		output: {
@@ -394,14 +377,7 @@ const getFrontConfig = ( options = {} ) => {
  */
 const getPaymentsConfig = ( options = {} ) => {
 	const { alias, resolvePlugins = [] } = options;
-	const resolve = alias
-		? {
-				alias,
-				plugins: resolvePlugins,
-		  }
-		: {
-				plugins: resolvePlugins,
-		  };
+	const resolve = getResolve( { alias, resolvePlugins } );
 	return {
 		entry: getEntryConfig( 'payments', options.exclude || [] ),
 		output: {
@@ -482,14 +458,7 @@ const getPaymentsConfig = ( options = {} ) => {
  */
 const getExtensionsConfig = ( options = {} ) => {
 	const { alias, resolvePlugins = [] } = options;
-	const resolve = alias
-		? {
-				alias,
-				plugins: resolvePlugins,
-		  }
-		: {
-				plugins: resolvePlugins,
-		  };
+	const resolve = getResolve( { alias, resolvePlugins } );
 	return {
 		entry: getEntryConfig( 'extensions', options.exclude || [] ),
 		output: {
@@ -570,14 +539,7 @@ const getExtensionsConfig = ( options = {} ) => {
  */
 const getSiteEditorConfig = ( options = {} ) => {
 	const { alias, resolvePlugins = [] } = options;
-	const resolve = alias
-		? {
-				alias,
-				plugins: resolvePlugins,
-		  }
-		: {
-				plugins: resolvePlugins,
-		  };
+	const resolve = getResolve( { alias, resolvePlugins } );
 	return {
 		entry: getEntryConfig( 'editor', options.exclude || [] ),
 		output: {
@@ -659,14 +621,7 @@ const getSiteEditorConfig = ( options = {} ) => {
 const getStylingConfig = ( options = {} ) => {
 	const { alias, resolvePlugins = [] } = options;

-	const resolve = alias
-		? {
-				alias,
-				plugins: resolvePlugins,
-		  }
-		: {
-				plugins: resolvePlugins,
-		  };
+	const resolve = getResolve( { alias, resolvePlugins } );
 	return {
 		entry: getEntryConfig( 'styling', options.exclude || [] ),
 		output: {
@@ -804,7 +759,7 @@ const getStylingConfig = ( options = {} ) => {
 			} ),
 			new WebpackRTLPlugin(),
 			// Remove JS files generated by MiniCssExtractPlugin.
-			new RemoveFilesPlugin( './build/*style.js' ),
+			new RemoveFilesPlugin( path.join( BUILD_DIR, '*style.js' ) ),
 		],
 		resolve: {
 			...resolve,
@@ -816,14 +771,7 @@ const getStylingConfig = ( options = {} ) => {
 const getCartAndCheckoutFrontendConfig = ( options = {} ) => {
 	const { alias, resolvePlugins = [] } = options;

-	const resolve = alias
-		? {
-				alias,
-				plugins: resolvePlugins,
-		  }
-		: {
-				plugins: resolvePlugins,
-		  };
+	const resolve = getResolve( { alias, resolvePlugins } );
 	return {
 		entry: getEntryConfig(
 			'cartAndCheckoutFrontend',
diff --git a/plugins/woocommerce/client/blocks/bin/webpack-helpers.js b/plugins/woocommerce/client/blocks/bin/webpack-helpers.js
index c068ee8ea02..aab4612a261 100644
--- a/plugins/woocommerce/client/blocks/bin/webpack-helpers.js
+++ b/plugins/woocommerce/client/blocks/bin/webpack-helpers.js
@@ -108,16 +108,41 @@ const getAlias = ( options = {} ) => {
 	};
 };

+// Activates the `"wc-source"` conditional export declared in each
+// `packages/js/*` package.json. Webpack walks the package's exports map and
+// picks `./src/index.ts` directly — eliminating the need to pre-build the few
+// `@woocommerce/*` packages that blocks bundles (i.e. not externalized via
+// `wcDepMap`). The condition is namespaced (`wc-` prefix) so it never collides
+// with third-party packages that publish their own `"source"` conditional
+// export. `'...'` extends the default webpack condition list.
+const getResolve = ( { alias, resolvePlugins = [] } = {} ) => ( {
+	conditionNames: [ 'wc-source', '...' ],
+	plugins: resolvePlugins,
+	...( alias ? { alias } : {} ),
+} );
+
 const requestToExternal = ( request ) => {
 	if ( request in wcDepMap ) {
 		return wcDepMap[ request ];
 	}
+	if ( request === 'react-dom/client' ) {
+		// React 18 split createRoot/hydrateRoot into react-dom/client.
+		// WordPress's wp-react-dom UMD aggregates both entrypoints onto the
+		// same window.ReactDOM global. DEWP's default mapper doesn't know
+		// about the subpath yet
+		// (https://github.com/WordPress/gutenberg/pull/77326),
+		// so map it here.
+		return 'ReactDOM';
+	}
 };

 const requestToHandle = ( request ) => {
 	if ( request in wcHandleMap ) {
 		return wcHandleMap[ request ];
 	}
+	if ( request === 'react-dom/client' ) {
+		return 'react-dom';
+	}
 };

 const getProgressBarPluginConfig = ( name ) => {
@@ -198,6 +223,7 @@ module.exports = {
 	CHECK_CIRCULAR_DEPS,
 	ASSET_CHECK,
 	getAlias,
+	getResolve,
 	requestToHandle,
 	requestToExternal,
 	getProgressBarPluginConfig,
diff --git a/plugins/woocommerce/client/blocks/package.json b/plugins/woocommerce/client/blocks/package.json
index 5892487115c..9527a8fe23a 100644
--- a/plugins/woocommerce/client/blocks/package.json
+++ b/plugins/woocommerce/client/blocks/package.json
@@ -43,15 +43,15 @@
 	],
 	"scripts": {
 		"analyze-bundles": "WP_BUNDLE_ANALYZER=1 pnpm run build",
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --if-present '/^build:project:.*$/'",
-		"build:project:bundle": "wireit",
-		"build:check-assets": "npx ajv validate -s bin/block.json-validation-schema.json -d assets/js/**/block.json && rimraf build/* && ASSET_CHECK=true BABEL_ENV=default NODE_ENV=production webpack",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
+		"build:project:bundle": "NODE_ENV=\"${NODE_ENV:-production}\" BABEL_ENV=\"${BABEL_ENV:-default}\" WP_EXPERIMENTAL_MODULES=\"${WP_EXPERIMENTAL_MODULES:-true}\" webpack && wp-scripts build-blocks-manifest --input=../../assets/client/blocks --output=../../assets/client/blocks/blocks-json.php",
+		"build:check-assets": "npx ajv validate -s bin/block.json-validation-schema.json -d assets/js/**/block.json && rimraf ../../assets/client/blocks/* && ASSET_CHECK=true BABEL_ENV=default NODE_ENV=production webpack",
 		"prebuild:docs": "rimraf docs/extensibility/actions.md & rimraf docs/extensibility/filters.md",
 		"build:docs": "./vendor/bin/wp-hooks-generator --input=src --output=bin/hook-docs/data && node ./bin/hook-docs && pnpm build:docs:block-references",
 		"build:docs:block-references": "node ./bin/gen-block-list-doc.js",
 		"postbuild:docs": "./bin/add-doc-footer.sh",
-		"dev": "rimraf build/* && BABEL_ENV=default webpack",
+		"dev": "rimraf ../../assets/client/blocks/* && BABEL_ENV=default webpack",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
 		"lint:fix:lang:css": "pnpm lint:css-fix",
@@ -69,12 +69,12 @@
 		"pre-commit": "lint-staged",
 		"reformat-files": "prettier --ignore-path .eslintignore --write \"**/*.{js,jsx,ts,tsx,css,scss}\"",
 		"rimraf": "./node_modules/rimraf/bin.js",
-		"start": "rimraf build/* && BABEL_ENV=default CHECK_CIRCULAR_DEPS=true webpack --watch",
+		"start": "rimraf ../../assets/client/blocks/* && BABEL_ENV=default CHECK_CIRCULAR_DEPS=true webpack --watch",
 		"storybook": "pnpm watch:build:storybook",
 		"storybook:build": "pnpm build:storybook",
 		"storybook:deploy": "rimraf ./storybook/dist/* && pnpm run storybook:build && gh-pages -d ./storybook/dist",
-		"build:storybook": "wireit",
-		"watch:build:storybook": "wireit",
+		"build:storybook": "BABEL_ENV=development storybook build -c ./storybook -o ./storybook/dist",
+		"watch:build:storybook": "storybook dev -c ./storybook -p 6006 --ci",
 		"test:js": "wp-scripts test-unit-js --config tests/js/jest.config.js",
 		"test:debug": "ndb .",
 		"test:e2e": "sh ./bin/check-env.sh && pnpm playwright test --config=tests/e2e/playwright.config.ts --project=chromium",
@@ -97,9 +97,9 @@
 		"ts:log-errors": "npm --silent run ts:check | npx -y @bartekbp/typescript-checkstyle > checkstyle.xml",
 		"wp-env": "wp-env",
 		"wp-env:config": "echo 'wp-env:config is no more. Use env:start instead.'",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
-		"watch:build:project:bundle": "wireit",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
+		"watch:build:project:bundle": "WP_EXPERIMENTAL_MODULES=\"${WP_EXPERIMENTAL_MODULES:-true}\" webpack --watch",
 		"knip": "WP_EXPERIMENTAL_MODULES=true knip"
 	},
 	"devDependencies": {
@@ -241,7 +241,6 @@
 		"webpack": "5.97.x",
 		"webpack-bundle-analyzer": "4.9.x",
 		"webpack-cli": "5.1.x",
-		"wireit": "0.14.12",
 		"wp-types": "3.63.0"
 	},
 	"engines": {
@@ -362,105 +361,5 @@
 			"!assets/js/**/test/**/*.{ts,tsx,jsx,js}"
 		],
 		"webpack": true
-	},
-	"wireit": {
-		"build:project:bundle": {
-			"command": "webpack",
-			"clean": "if-file-deleted",
-			"env": {
-				"NODE_ENV": {
-					"external": true,
-					"default": "production"
-				},
-				"BABEL_ENV": {
-					"external": true,
-					"default": "default"
-				},
-				"WP_EXPERIMENTAL_MODULES": {
-					"external": true,
-					"default": "true"
-				}
-			},
-			"files": [
-				"webpack.config.js",
-				"bin/webpack-*.js",
-				"tsconfig.json",
-				"tsconfig.base.json",
-				"babel.config.js",
-				"assets",
-				"packages"
-			],
-			"output": [
-				"build"
-			],
-			"dependencies": [
-				"dependencyOutputs"
-			]
-		},
-		"build:storybook": {
-			"command": "storybook build  -c ./storybook -o ./storybook/dist",
-			"env": {
-				"BABEL_ENV": "development"
-			},
-			"files": [
-				"storybook/webpack.config.js",
-				"bin/webpack-*.js",
-				"storybook/main.js",
-				"storybook/preview.js",
-				"storybook/style.scss"
-			],
-			"output": [
-				"storybook/dist"
-			],
-			"dependencies": [
-				"dependencyOutputs"
-			]
-		},
-		"watch:build:project:bundle": {
-			"command": "webpack --watch",
-			"service": true,
-			"env": {
-				"WP_EXPERIMENTAL_MODULES": {
-					"external": true,
-					"default": "true"
-				}
-			}
-		},
-		"watch:build:storybook": {
-			"command": "storybook dev  -c ./storybook -p 6006 --ci",
-			"service": true
-		},
-		"dependencyOutputs": {
-			"allowUsuallyExcludedPaths": true,
-			"files": [
-				"package.json",
-				"../../../../pnpm-lock.yaml",
-				"node_modules/@woocommerce/email-editor/build",
-				"node_modules/@woocommerce/email-editor/build-style",
-				"node_modules/@woocommerce/email-editor/build-types",
-				"node_modules/@woocommerce/email-editor/assets",
-				"node_modules/@woocommerce/sanitize/build",
-				"node_modules/@woocommerce/sanitize/build-style",
-				"node_modules/@woocommerce/sanitize/build-types",
-				"node_modules/@woocommerce/tracks/build",
-				"node_modules/@woocommerce/tracks/build-types",
-				"node_modules/@woocommerce/customer-effort-score/build",
-				"node_modules/@woocommerce/customer-effort-score/build-style",
-				"node_modules/@woocommerce/customer-effort-score/build-types",
-				"node_modules/@woocommerce/data/build",
-				"node_modules/@woocommerce/data/build-types",
-				"node_modules/@woocommerce/dependency-extraction-webpack-plugin/src/",
-				"node_modules/@woocommerce/dependency-extraction-webpack-plugin/assets/",
-				"node_modules/@woocommerce/internal-build/build-types",
-				"node_modules/@woocommerce/internal-build/configs",
-				"node_modules/@woocommerce/internal-build/src/style-build",
-				"node_modules/@woocommerce/internal-build/styles",
-				"node_modules/@woocommerce/internal-build/type-overrides",
-				"node_modules/@woocommerce/internal-build/scripts",
-				"node_modules/@woocommerce/eslint-plugin/configs",
-				"node_modules/@woocommerce/eslint-plugin/rules",
-				"node_modules/@woocommerce/eslint-plugin/index.js"
-			]
-		}
 	}
 }
diff --git a/plugins/woocommerce/client/blocks/tests/e2e/bin/generate-test-translations.js b/plugins/woocommerce/client/blocks/tests/e2e/bin/generate-test-translations.js
index b5145b0afaf..7655e7dd5c5 100644
--- a/plugins/woocommerce/client/blocks/tests/e2e/bin/generate-test-translations.js
+++ b/plugins/woocommerce/client/blocks/tests/e2e/bin/generate-test-translations.js
@@ -8,7 +8,7 @@ const { translations } = require( '../test-data/data/data.ts' );
 const { getTestTranslation } = require( '../utils/get-test-translation.js' );

 const ROOT_DIR = path.resolve( __dirname, '../../../../../' );
-const BUILD_DIR = path.resolve( __dirname, '../../../build/' );
+const BUILD_DIR = path.resolve( ROOT_DIR, 'assets/client/blocks/' );
 const TESTS_DIR = path.resolve( __dirname, '../tests/' );
 const LANGUAGES_DIR = path.join( ROOT_DIR, 'i18n/languages/' );

@@ -60,9 +60,12 @@ builtJsFiles.forEach( ( filePath ) => {
 	if ( stringsInFile.length === 0 ) {
 		return;
 	}
-	const relativeFilePath = filePath
-		.substring( filePath.indexOf( 'build/' ) )
-		.replace( 'build', 'assets/client/blocks' );
+	// Match WordPress's script handle path: assets are enqueued from
+	// `plugins/woocommerce/assets/client/blocks/...` and WP hashes that
+	// path (minus the plugin prefix) to find translation JSON files.
+	const relativeFilePath = filePath.substring(
+		filePath.indexOf( 'assets/client/blocks/' )
+	);
 	const data = {
 		locale_data: {
 			messages: {
diff --git a/plugins/woocommerce/client/legacy/Gruntfile.js b/plugins/woocommerce/client/legacy/Gruntfile.js
index 47f0b28d1ca..2ca05c936f5 100644
--- a/plugins/woocommerce/client/legacy/Gruntfile.js
+++ b/plugins/woocommerce/client/legacy/Gruntfile.js
@@ -6,11 +6,14 @@ module.exports = function ( grunt ) {
 		// Setting folder templates.
 		dirs: {
 			css: 'css',
-			cssDest: 'build/css',
+			// Write directly to the plugin's `assets/css` so PHP enqueues
+			// from the final location with no intermediate rsync step.
+			cssDest: '../../assets/css',
 			fonts: 'assets/fonts',
 			images: 'assets/images',
 			js: 'js',
-			jsDest: 'build/js',
+			// See `cssDest`. The plugin enqueues from `assets/js`.
+			jsDest: '../../assets/js',
 			php: 'includes',
 		},

@@ -142,30 +145,6 @@ module.exports = function ( grunt ) {
 			},
 		},

-		// Watch changes for assets.
-		watch: {
-			css: {
-				files: [ '<%= dirs.css %>/*.scss' ],
-				tasks: [
-					'sass',
-					'rtlcss',
-					'postcss',
-					'cssmin',
-					'concat:css',
-					'move:css',
-					'copy:css',
-				],
-			},
-			js: {
-				files: [
-					'GruntFile.js',
-					'<%= dirs.js %>/**/*.js',
-					'!<%= dirs.js %>/**/*.min.js',
-				],
-				tasks: [ 'eslint', 'copy:js', 'concat:js', 'newer:uglify' ],
-			},
-		},
-
 		// PHP Code Sniffer.
 		phpcs: {
 			options: {
@@ -263,11 +242,11 @@ module.exports = function ( grunt ) {
 	grunt.loadNpmTasks( 'grunt-rtlcss' );
 	grunt.loadNpmTasks( 'grunt-postcss' );
 	grunt.loadNpmTasks( 'grunt-stylelint' );
+	grunt.loadNpmTasks( 'gruntify-eslint' );
 	grunt.loadNpmTasks( 'grunt-contrib-uglify-es' );
 	grunt.loadNpmTasks( 'grunt-contrib-cssmin' );
 	grunt.loadNpmTasks( 'grunt-contrib-concat' );
 	grunt.loadNpmTasks( 'grunt-contrib-copy' );
-	grunt.loadNpmTasks( 'grunt-contrib-watch' );
 	grunt.loadNpmTasks( 'grunt-contrib-clean' );
 	grunt.loadNpmTasks( 'grunt-newer' );
 	grunt.loadNpmTasks( 'grunt-move' );
@@ -293,4 +272,68 @@ module.exports = function ( grunt ) {

 	// Only an alias to 'default' task.
 	grunt.registerTask( 'dev', [ 'default' ] );
+
+	grunt.registerTask(
+		'watch',
+		'Rebuild js/css assets when their sources change.',
+		function () {
+			const chokidar = require( 'chokidar' );
+			this.async(); // Keep this task alive until SIGINT.
+
+			let running = false;
+			const pending = new Set();
+			const runQueued = ( tasks ) => {
+				tasks.forEach( ( task ) => pending.add( task ) );
+				if ( running ) {
+					return;
+				}
+				running = true;
+				const next = [ ...pending ];
+				pending.clear();
+				grunt.util.spawn(
+					{
+						grunt: true,
+						args: next,
+						opts: { stdio: 'inherit' },
+					},
+					() => {
+						running = false;
+						if ( pending.size > 0 ) {
+							// Drain the queue.
+							runQueued( [] );
+						}
+					}
+				);
+			};
+
+			chokidar
+				.watch( [ 'css/*.scss' ], { ignoreInitial: true } )
+				.on( 'all', () =>
+					runQueued( [
+						'sass',
+						'rtlcss',
+						'postcss',
+						'cssmin',
+						'concat:css',
+						'move:css',
+						'copy:css',
+					] )
+				);
+
+			chokidar
+				.watch( [ 'js/**/*.js', 'Gruntfile.js' ], {
+					ignoreInitial: true,
+					ignored: '**/*.min.js',
+				} )
+				.on( 'all', () =>
+					runQueued( [
+						'copy:js',
+						'concat:js',
+						'newer:uglify',
+					] )
+				);
+
+			grunt.log.writeln( 'Watching css/ and js/ for changes...' );
+		}
+	);
 };
diff --git a/plugins/woocommerce/client/legacy/package.json b/plugins/woocommerce/client/legacy/package.json
index 03c8dc3312e..962bc77a3eb 100644
--- a/plugins/woocommerce/client/legacy/package.json
+++ b/plugins/woocommerce/client/legacy/package.json
@@ -5,16 +5,20 @@
 	"author": "Automattic",
 	"license": "GPL-2.0-or-later",
 	"main": "Gruntfile.js",
-	"files": [
-		"build"
-	],
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --if-present '/^build:project:.*$/'",
-		"build:project:assets": "wireit",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
-		"watch:build:project:assets": "pnpm build:project --watch",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
+		"build:project:js": "grunt js",
+		"build:project:css": "grunt css",
+		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
+		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
+		"lint:lang:js": "grunt eslint",
+		"lint:lang:css": "grunt stylelint",
+		"lint:fix:lang:js": "grunt eslint --fix",
+		"lint:fix:lang:css": "grunt stylelint --fix",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
+		"watch:build:project:assets": "grunt watch",
 		"test:js": "wp-scripts test-unit-js --config jest.config.json"
 	},
 	"devDependencies": {
@@ -24,13 +28,13 @@
 		"autoprefixer": "9.8.6",
 		"browserslist": "4.19.3",
 		"caniuse-lite": "1.0.30001146",
+		"chokidar": "^3.6.0",
 		"grunt": "1.3.0",
 		"grunt-contrib-clean": "2.0.0",
 		"grunt-contrib-concat": "1.0.1",
 		"grunt-contrib-copy": "1.0.0",
 		"grunt-contrib-cssmin": "3.0.0",
 		"grunt-contrib-uglify-es": "^3.3.0",
-		"grunt-contrib-watch": "1.1.0",
 		"grunt-move": "1.0.3",
 		"grunt-newer": "1.3.0",
 		"grunt-phpcs": "0.4.0",
@@ -41,54 +45,11 @@
 		"gruntify-eslint": "5.0.0",
 		"jest-fixed-jsdom": "^0.0.10",
 		"sass": "^1.69.5",
-		"stylelint": "^14.16.1",
-		"wireit": "0.14.12"
+		"stylelint": "^14.16.1"
 	},
 	"dependencies": {
 		"sourcebuster": "github:woocommerce/sourcebuster-js#d7f4616d5a17e17db925ca1842457f309379d861"
 	},
-	"wireit": {
-		"build:project:assets": {
-			"dependencies": [
-				"build:project:assets:js",
-				"build:project:assets:css"
-			]
-		},
-		"build:project:assets:js": {
-			"command": "grunt js",
-			"clean": "if-file-deleted",
-			"files": [
-				"Gruntfile.js",
-				"js/**/*.js"
-			],
-			"output": [
-				"build/js"
-			],
-			"dependencies": [
-				"dependencyOutputs"
-			]
-		},
-		"build:project:assets:css": {
-			"command": "grunt css",
-			"clean": "if-file-deleted",
-			"files": [
-				"Gruntfile.js",
-				"css/**/*.scss"
-			],
-			"output": [
-				"build/css"
-			],
-			"dependencies": [
-				"dependencyOutputs"
-			]
-		},
-		"dependencyOutputs": {
-			"allowUsuallyExcludedPaths": true,
-			"files": [
-				"package.json"
-			]
-		}
-	},
 	"config": {
 		"ci": {
 			"tests": [
diff --git a/plugins/woocommerce/composer.lock b/plugins/woocommerce/composer.lock
index e57a79d4489..92bb66fc17f 100644
--- a/plugins/woocommerce/composer.lock
+++ b/plugins/woocommerce/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "37bed2e19d8b44bc71451de7683919df",
+    "content-hash": "c98895a33d6c250d18ac5dd360435329",
     "packages": [
         {
             "name": "automattic/block-delimiter",
@@ -5434,7 +5434,7 @@
         },
         {
             "name": "woocommerce/monorepo-plugin",
-            "version": "dev-add/phpstan",
+            "version": "dev-refactor/64839-admin-blocks-build-packages",
             "dist": {
                 "type": "path",
                 "url": "../../packages/php/monorepo-plugin",
@@ -5594,5 +5594,5 @@
     "platform-overrides": {
         "php": "7.4"
     },
-    "plugin-api-version": "2.6.0"
+    "plugin-api-version": "2.9.0"
 }
diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json
index e1159911975..8ef1668cb1c 100644
--- a/plugins/woocommerce/package.json
+++ b/plugins/woocommerce/package.json
@@ -10,18 +10,17 @@
 	},
 	"license": "GPL-2.0-or-later",
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:admin": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"@woocommerce/admin-library...\" --filter=\"$npm_package_name\" '/^build:project:.*$/'",
-		"build:blocks": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"@woocommerce/block-library...\" --filter=\"$npm_package_name\" '/^build:project:.*$/'",
-		"build:classic-assets": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"@woocommerce/classic-assets...\" --filter=\"$npm_package_name\" '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:admin": "pnpm build:project:admin",
+		"build:blocks": "pnpm build:project:blocks",
+		"build:classic-assets": "pnpm build:project:classic-assets",
 		"build:zip": "./bin/build-zip.sh",
-		"build:project": "pnpm --if-present '/^build:project:.*$/'",
-		"build:project:copy-assets:legacy": "rsync -avhW --checksum --delete --quiet client/legacy/build/css/ assets/css && rsync -avhW --checksum --delete --quiet client/legacy/build/js/ assets/js",
-		"build:project:copy-assets:admin": "rsync -avhW --checksum --delete --quiet client/admin/build/ assets/client/admin",
-		"build:project:copy-assets:blocks": "rsync -avhW --checksum --delete --quiet client/blocks/build/ assets/client/blocks && wp-scripts build-blocks-manifest --input=assets/client/blocks --output=assets/client/blocks/blocks-json.php",
-		"build:project:copy-assets:email-editor": "rsync -avhW --checksum --delete --quiet ../../packages/php/email-editor/src/ packages/email-editor/src",
-		"build:project:copy-assets:blueprint": "rsync -avhW --checksum --delete --quiet ../../packages/php/blueprint/src/ packages/blueprint/src",
-		"build:project:actualize-translation-domains": "wireit",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
+		"build:project:admin": "pnpm --filter='@woocommerce/admin-library' build",
+		"build:project:blocks": "pnpm --filter='@woocommerce/block-library' build",
+		"build:project:classic-assets": "pnpm --filter='@woocommerce/classic-assets' build",
+		"build:project:packages": "XDEBUG_MODE=off composer install --quiet",
+		"build:project:actualize-translation-domains": "node ./bin/package-update-textdomain.js",
 		"build:api": "php bin/api-builder/build-api.php",
 		"build:api:check": "php bin/api-builder/check-api-staleness.php",
 		"build:api:test": "php bin/api-builder/build-api.php --api-dir=tests/php/src/Internal/Api/Fixtures/DummyApi --autogen-dir=tests/php/src/Internal/Api/Fixtures/DummyApiAutogenerated --api-namespace='Automattic\\WooCommerce\\Tests\\Internal\\Api\\Fixtures\\DummyApi' --autogen-namespace='Automattic\\WooCommerce\\Tests\\Internal\\Api\\Fixtures\\DummyApiAutogenerated'",
@@ -95,12 +94,15 @@
 		"test:unit:env": "pnpm test:php:env",
 		"test:unit:env:watch": "pnpm test:php:env:watch",
 		"update-wp-env": "php ./tests/e2e-pw/bin/update-wp-env.php",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:admin": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"@woocommerce/admin-library...\" --filter=\"$npm_package_name\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:blocks": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"@woocommerce/block-library...\" --filter=\"$npm_package_name\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:classic-assets": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"@woocommerce/classic-assets...\" --filter=\"$npm_package_name\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
-		"watch:build:project:copy-assets": "wireit",
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:admin": "pnpm watch:build:project:admin",
+		"watch:build:blocks": "pnpm watch:build:project:blocks",
+		"watch:build:classic-assets": "pnpm watch:build:project:classic-assets",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'",
+		"watch:build:project:admin": "pnpm --filter='@woocommerce/admin-library' watch:build",
+		"watch:build:project:blocks": "pnpm --filter='@woocommerce/block-library' watch:build",
+		"watch:build:project:classic-assets": "pnpm --filter='@woocommerce/classic-assets' watch:build",
+		"watch:build:project:packages": "node ./bin/watch-packages.mjs",
 		"wp-env": "wp-env"
 	},
 	"lint-staged": {
@@ -928,6 +930,7 @@
 		"@typescript-eslint/parser": "^5.62.0",
 		"@woocommerce/e2e-utils-playwright": "workspace:*",
 		"@woocommerce/eslint-plugin": "workspace:*",
+		"@woocommerce/internal-build": "workspace:*",
 		"@wordpress/browserslist-config": "next",
 		"@wordpress/e2e-test-utils-playwright": "wp-6.8",
 		"@wordpress/env": "11.0.1-next.v.20260206T143.0",
@@ -945,13 +948,11 @@
 		"eslint-plugin-jest": "23.20.0",
 		"eslint-plugin-playwright": "0.22.1",
 		"jest": "29.5.x",
-		"nodemon": "^3.0.2",
 		"playwright-ctrf-json-reporter": "0.0.27",
 		"prettier": "npm:wp-prettier@^2.8.5",
 		"rimraf": "5.0.5",
 		"stylelint": "^14.16.1",
 		"typescript": "5.7.x",
-		"wireit": "0.14.12",
 		"wp-textdomain": "1.0.1"
 	},
 	"engines": {
@@ -966,46 +967,5 @@
 		"@woocommerce/admin-library": "workspace:*",
 		"@woocommerce/block-library": "workspace:*",
 		"@woocommerce/classic-assets": "workspace:*"
-	},
-	"nodemonConfig": {
-		"delay": 2500,
-		"watch": [
-			"node_modules/@woocommerce/block-library/build",
-			"node_modules/@woocommerce/classic-assets/build",
-			"node_modules/@woocommerce/admin-library/build",
-			"../../packages/php/email-editor/src/",
-			"../../packages/php/blueprint/src/"
-		],
-		"ext": "js,css,php,json",
-		"ignoreRoot": []
-	},
-	"wireit": {
-		"build:project:actualize-translation-domains": {
-			"command": "node ./bin/package-update-textdomain.js",
-			"files": [
-				"packages/action-scheduler/action-scheduler.php",
-				"packages/woocommerce-admin/woocommerce-admin.php"
-			],
-			"output": []
-		},
-		"watch:build:project:copy-assets": {
-			"command": "nodemon --exec \"pnpm build:project\"",
-			"service": true
-		},
-		"dependencyOutputs": {
-			"allowUsuallyExcludedPaths": true,
-			"files": [
-				"package.json",
-				"../../pnpm-lock.yaml",
-				"node_modules/@woocommerce/admin-library/build",
-				"node_modules/@woocommerce/block-library/build",
-				"node_modules/@woocommerce/classic-assets/build",
-				"node_modules/@woocommerce/e2e-utils-playwright/build",
-				"node_modules/@woocommerce/e2e-utils-playwright/build-types",
-				"node_modules/@woocommerce/eslint-plugin/configs",
-				"node_modules/@woocommerce/eslint-plugin/rules",
-				"node_modules/@woocommerce/eslint-plugin/index.js"
-			]
-		}
 	}
 }
diff --git a/plugins/woocommerce/tests/e2e-pw/run-tests-with-env.sh b/plugins/woocommerce/tests/e2e-pw/run-tests-with-env.sh
index fc8221bce69..2c941e8d24b 100644
--- a/plugins/woocommerce/tests/e2e-pw/run-tests-with-env.sh
+++ b/plugins/woocommerce/tests/e2e-pw/run-tests-with-env.sh
@@ -82,6 +82,17 @@ echo "Using config file: $configFile"
 echo "Arguments: $*"
 title

+# Activate the `wc-source` conditional export so Playwright resolves
+# `@woocommerce/*` workspace packages to their TypeScript source instead of
+# their built `build/index.js`. Playwright bundles esbuild and transpiles
+# imported `.ts` on the fly, so this skips the pre-build step for these
+# packages. `--conditions` stacks with NODE_OPTIONS already in the env.
+#
+# Playwright has no built-in config for custom export conditions; setting
+# NODE_OPTIONS is the documented workaround.
+# See https://github.com/microsoft/playwright/issues/33684.
+export NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=wc-source"
+
 pnpm playwright test --config="$configFile" "$@"


diff --git a/plugins/woocommerce/tsconfig.json b/plugins/woocommerce/tsconfig.json
index 9b3bbd62d1c..f0251618df4 100644
--- a/plugins/woocommerce/tsconfig.json
+++ b/plugins/woocommerce/tsconfig.json
@@ -1,7 +1,15 @@
 {
 	"extends": "../../tsconfig.base.json",
 	"references": [
-		{ "path": "../../packages/js/api" }
+		{
+			"path": "client/admin"
+		},
+		{
+			"path": "client/blocks"
+		}
 	],
-	"files": []
+	"files": [],
+	"compilerOptions": {
+		"composite": true
+	}
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 438fd25f8b4..7d19d017410 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -170,7 +170,7 @@ overrides:
   react-resize-aware: 3.1.1
   sass: 1.69.5

-pnpmfileChecksum: sha256-0mGO2uxSzpGcsYJBjrnH8vcgzXYYgU2kSQKpHvcFKPM=
+pnpmfileChecksum: sha256-7lEtYKkj9MKUQ1wDfOySWQ01OX5tYIUQTnfpMq5epMI=

 patchedDependencies:
   '@wordpress/edit-site@5.15.0':
@@ -2966,6 +2966,9 @@ importers:
       '@woocommerce/eslint-plugin':
         specifier: workspace:*
         version: link:../../packages/js/eslint-plugin
+      '@woocommerce/internal-build':
+        specifier: workspace:*
+        version: link:../../packages/js/internal-build
       '@wordpress/browserslist-config':
         specifier: next
         version: 6.43.1-next.v.202604091042.0
@@ -3017,9 +3020,6 @@ importers:
       jest:
         specifier: 29.5.x
         version: 29.5.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3))
-      nodemon:
-        specifier: ^3.0.2
-        version: 3.1.14
       playwright-ctrf-json-reporter:
         specifier: 0.0.27
         version: 0.0.27
@@ -3035,9 +3035,6 @@ importers:
       typescript:
         specifier: 5.7.x
         version: 5.7.3
-      wireit:
-        specifier: 0.14.12
-        version: 0.14.12
       wp-textdomain:
         specifier: 1.0.1
         version: 1.0.1
@@ -3153,9 +3150,6 @@ importers:
       uglify-js:
         specifier: ^3.17.4
         version: 3.19.3
-      wireit:
-        specifier: 0.14.12
-        version: 0.14.12

   plugins/woocommerce/client/admin:
     dependencies:
@@ -3688,9 +3682,6 @@ importers:
       whatwg-fetch:
         specifier: ^3.6.20
         version: 3.6.20
-      wireit:
-        specifier: 0.14.12
-        version: 0.14.12

   plugins/woocommerce/client/blocks:
     dependencies:
@@ -4265,9 +4256,6 @@ importers:
       webpack-cli:
         specifier: 5.1.x
         version: 5.1.4(webpack-bundle-analyzer@4.9.1)(webpack-dev-server@4.15.2)(webpack@5.97.1)
-      wireit:
-        specifier: 0.14.12
-        version: 0.14.12
       wp-types:
         specifier: 3.63.0
         version: 3.63.0
@@ -4306,6 +4294,9 @@ importers:
       caniuse-lite:
         specifier: 1.0.30001146
         version: 1.0.30001146
+      chokidar:
+        specifier: ^3.6.0
+        version: 3.6.0
       grunt:
         specifier: 1.3.0
         version: 1.3.0
@@ -4324,9 +4315,6 @@ importers:
       grunt-contrib-uglify-es:
         specifier: ^3.3.0
         version: 3.3.0
-      grunt-contrib-watch:
-        specifier: 1.1.0
-        version: 1.1.0
       grunt-move:
         specifier: 1.0.3
         version: 1.0.3
@@ -4360,9 +4348,6 @@ importers:
       stylelint:
         specifier: ^14.16.1
         version: 14.16.1
-      wireit:
-        specifier: 0.14.12
-        version: 0.14.12

   tools/code-analyzer:
     dependencies:
@@ -4418,9 +4403,6 @@ importers:
       typescript:
         specifier: 5.7.x
         version: 5.7.3
-      wireit:
-        specifier: 0.14.12
-        version: 0.14.12

   tools/compare-perf:
     dependencies:
@@ -4497,9 +4479,6 @@ importers:
       typescript:
         specifier: 5.7.x
         version: 5.7.3
-      wireit:
-        specifier: 0.14.12
-        version: 0.14.12

   tools/monorepo-utils:
     dependencies:
@@ -4609,9 +4588,6 @@ importers:
       webpack-cli:
         specifier: 5.1.x
         version: 5.1.4(webpack@5.97.1)
-      wireit:
-        specifier: 0.14.12
-        version: 0.14.12

   tools/package-release:
     dependencies:
@@ -4652,9 +4628,6 @@ importers:
       typescript:
         specifier: 5.7.x
         version: 5.7.3
-      wireit:
-        specifier: 0.14.12
-        version: 0.14.12

   tools/release-posts:
     dependencies:
@@ -4728,9 +4701,6 @@ importers:
       typescript:
         specifier: 5.7.x
         version: 5.7.3
-      wireit:
-        specifier: 0.14.12
-        version: 0.14.12

   tools/storybook:
     devDependencies:
@@ -4818,9 +4788,6 @@ importers:
       webpack:
         specifier: 5.97.x
         version: 5.97.1(@swc/core@1.15.24)(webpack-cli@5.1.4)
-      wireit:
-        specifier: 0.14.12
-        version: 0.14.12

 packages:

@@ -12816,10 +12783,6 @@ packages:
   balanced-match@2.0.0:
     resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==}

-  balanced-match@3.0.1:
-    resolution: {integrity: sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==}
-    engines: {node: '>= 16'}
-
   balanced-match@4.0.4:
     resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
     engines: {node: 18 || 20 || >=22}
@@ -12976,10 +12939,6 @@ packages:
   brace-expansion@2.1.0:
     resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==}

-  brace-expansion@4.0.1:
-    resolution: {integrity: sha512-YClrbvTCXGe70pU2JiEiPLYXO9gQkyxYeKpJIQHVS/gOs6EWMQP2RYBwjFLNT322Ji8TOC3IMPfsYCedNpzKfA==}
-    engines: {node: '>= 18'}
-
   brace-expansion@5.0.5:
     resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==}
     engines: {node: 18 || 20 || >=22}
@@ -15988,10 +15947,6 @@ packages:
     engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
     deprecated: This package is no longer supported.

-  gaze@1.1.3:
-    resolution: {integrity: sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==}
-    engines: {node: '>= 4.0.0'}
-
   generator-function@2.0.1:
     resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
     engines: {node: '>= 0.4'}
@@ -16222,10 +16177,6 @@ packages:
   globjoin@0.1.4:
     resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==}

-  globule@1.3.4:
-    resolution: {integrity: sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==}
-    engines: {node: '>= 0.10'}
-
   gonzales-pe@4.3.0:
     resolution: {integrity: sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==}
     engines: {node: '>=0.6.0'}
@@ -16326,10 +16277,6 @@ packages:
     engines: {node: '>=0.10.0'}
     deprecated: deprecated

-  grunt-contrib-watch@1.1.0:
-    resolution: {integrity: sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==}
-    engines: {node: '>=0.10.0'}
-
   grunt-known-options@1.1.1:
     resolution: {integrity: sha512-cHwsLqoighpu7TuYj5RonnEuxGVFnztcUqTqp5rXFGYL4OuPFofwC4Ycg7n9fYwvK6F5WbYgeVOwph9Crs2fsQ==}
     engines: {node: '>=0.10.0'}
@@ -16820,9 +16767,6 @@ packages:
   iferr@0.1.5:
     resolution: {integrity: sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA==}

-  ignore-by-default@1.0.1:
-    resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
-
   ignore-loader@0.1.2:
     resolution: {integrity: sha512-yOJQEKrNwoYqrWLS4DcnzM7SEQhRKis5mB+LdKKh4cPmGYlLPR0ozRzHV5jmEk2IxptqJNQA5Cc0gw8Fj12bXA==}

@@ -19143,11 +19087,6 @@ packages:
     resolution: {integrity: sha512-RinNxoz4W1cep1b928fuFhvAQ5ag/+1UlMDV7rbyGthBIgsiEouS4kvRayvvboxii4m8eolKOIBo3OjDqbc+uQ==}
     engines: {node: '>=6'}

-  nodemon@3.1.14:
-    resolution: {integrity: sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==}
-    engines: {node: '>=10'}
-    hasBin: true
-
   nopt@3.0.6:
     resolution: {integrity: sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==}
     hasBin: true
@@ -20592,9 +20531,6 @@ packages:
   prop-types@15.8.1:
     resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}

-  proper-lockfile@4.1.2:
-    resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==}
-
   properties@1.2.1:
     resolution: {integrity: sha512-qYNxyMj1JeW54i/EWEFsM1cVwxJbtgPp8+0Wg9XjNaK6VE/c4oRi6PNu5p7w1mNXEIQIjV5Wwn8v8Gz82/QzdQ==}
     engines: {node: '>=0.10'}
@@ -20640,9 +20576,6 @@ packages:
   psl@1.15.0:
     resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}

-  pstree.remy@1.1.8:
-    resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
-
   public-encrypt@4.0.3:
     resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==}

@@ -21994,10 +21927,6 @@ packages:
   simple-peer@9.11.1:
     resolution: {integrity: sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==}

-  simple-update-notifier@2.0.0:
-    resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
-    engines: {node: '>=10'}
-
   sirv@2.0.4:
     resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==}
     engines: {node: '>= 10'}
@@ -22921,10 +22850,6 @@ packages:
     resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
     engines: {node: '>=6'}

-  touch@3.1.1:
-    resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==}
-    hasBin: true
-
   tough-cookie@2.5.0:
     resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==}
     engines: {node: '>=0.8'}
@@ -23206,9 +23131,6 @@ packages:
     resolution: {integrity: sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==}
     engines: {node: '>=0.10.0'}

-  undefsafe@2.0.5:
-    resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
-
   underscore.string@3.3.6:
     resolution: {integrity: sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==}

@@ -23990,11 +23912,6 @@ packages:
     engines: {node: '>= 0.10.0'}
     hasBin: true

-  wireit@0.14.12:
-    resolution: {integrity: sha512-gNSd+nZmMo6cuICezYXRIayu6TSOeCSCDzjSF0q6g8FKDsRbdqrONrSZYzdk/uBISmRcv4vZtsno6GyGvdXwGA==}
-    engines: {node: '>=18.0.0'}
-    hasBin: true
-
   word-wrap@1.2.5:
     resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
     engines: {node: '>=0.10.0'}
@@ -28924,7 +28841,7 @@ snapshots:
       '@remote-ui/rpc': 1.4.7
     optionalDependencies:
       '@babel/core': 7.25.7
-      webpack: 5.97.1(@swc/core@1.15.24)(uglify-js@3.19.3)(webpack-cli@4.10.0)
+      webpack: 5.97.1(@swc/core@1.15.24)(webpack-cli@5.1.4)
       webpack-virtual-modules: 0.6.2

   '@sideway/address@4.1.5':
@@ -40235,8 +40152,6 @@ snapshots:

   balanced-match@2.0.0: {}

-  balanced-match@3.0.1: {}
-
   balanced-match@4.0.4: {}

   bare-events@2.8.2: {}
@@ -40415,10 +40330,6 @@ snapshots:
     dependencies:
       balanced-match: 1.0.2

-  brace-expansion@4.0.1:
-    dependencies:
-      balanced-match: 3.0.1
-
   brace-expansion@5.0.5:
     dependencies:
       balanced-match: 4.0.4
@@ -42004,12 +41915,6 @@ snapshots:
     dependencies:
       ms: 2.1.2

-  debug@4.4.3(supports-color@5.5.0):
-    dependencies:
-      ms: 2.1.3
-    optionalDependencies:
-      supports-color: 5.5.0
-
   debug@4.4.3(supports-color@8.1.1):
     dependencies:
       ms: 2.1.3
@@ -44470,10 +44375,6 @@ snapshots:
       strip-ansi: 6.0.1
       wide-align: 1.1.5

-  gaze@1.1.3:
-    dependencies:
-      globule: 1.3.4
-
   generator-function@2.0.1: {}

   gensync@1.0.0-beta.2: {}
@@ -44770,12 +44671,6 @@ snapshots:

   globjoin@0.1.4: {}

-  globule@1.3.4:
-    dependencies:
-      glob: 7.1.7
-      lodash: 4.17.21
-      minimatch: 3.0.8
-
   gonzales-pe@4.3.0:
     dependencies:
       minimist: 1.2.8
@@ -44897,15 +44792,6 @@ snapshots:
       uglify-es: 3.3.9
       uri-path: 1.0.0

-  grunt-contrib-watch@1.1.0:
-    dependencies:
-      async: 2.6.4
-      gaze: 1.1.3
-      lodash: 4.17.21
-      tiny-lr: 1.1.1
-    transitivePeerDependencies:
-      - supports-color
-
   grunt-known-options@1.1.1: {}

   grunt-legacy-log-utils@2.1.3:
@@ -45543,8 +45429,6 @@ snapshots:

   iferr@0.1.5: {}

-  ignore-by-default@1.0.1: {}
-
   ignore-loader@0.1.2: {}

   ignore-walk@4.0.1:
@@ -48765,19 +48649,6 @@ snapshots:

   node-watch@0.7.4: {}

-  nodemon@3.1.14:
-    dependencies:
-      chokidar: 3.6.0
-      debug: 4.4.3(supports-color@5.5.0)
-      ignore-by-default: 1.0.1
-      minimatch: 10.2.5
-      pstree.remy: 1.1.8
-      semver: 7.7.4
-      simple-update-notifier: 2.0.0
-      supports-color: 5.5.0
-      touch: 3.1.1
-      undefsafe: 2.0.5
-
   nopt@3.0.6:
     dependencies:
       abbrev: 1.1.1
@@ -50430,12 +50301,6 @@ snapshots:
       object-assign: 4.1.1
       react-is: 16.13.1

-  proper-lockfile@4.1.2:
-    dependencies:
-      graceful-fs: 4.2.11
-      retry: 0.12.0
-      signal-exit: 3.0.7
-
   properties@1.2.1: {}

   property-information@5.6.0:
@@ -50491,8 +50356,6 @@ snapshots:
     dependencies:
       punycode: 2.3.1

-  pstree.remy@1.1.8: {}
-
   public-encrypt@4.0.3:
     dependencies:
       bn.js: 4.12.3
@@ -52209,10 +52072,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color

-  simple-update-notifier@2.0.0:
-    dependencies:
-      semver: 7.7.4
-
   sirv@2.0.4:
     dependencies:
       '@polka/url': 1.0.0-next.29
@@ -53558,8 +53417,6 @@ snapshots:

   totalist@3.0.1: {}

-  touch@3.1.1: {}
-
   tough-cookie@2.5.0:
     dependencies:
       psl: 1.15.0
@@ -53830,8 +53687,6 @@ snapshots:

   unc-path-regex@0.1.2: {}

-  undefsafe@2.0.5: {}
-
   underscore.string@3.3.6:
     dependencies:
       sprintf-js: 1.1.3
@@ -54964,14 +54819,6 @@ snapshots:

   window-size@0.2.0: {}

-  wireit@0.14.12:
-    dependencies:
-      brace-expansion: 4.0.1
-      chokidar: 3.6.0
-      fast-glob: 3.3.3
-      jsonc-parser: 3.3.1
-      proper-lockfile: 4.1.2
-
   word-wrap@1.2.5: {}

   wordwrap@1.0.0: {}
diff --git a/tools/README.md b/tools/README.md
index 247ffbe383b..339880cabfa 100644
--- a/tools/README.md
+++ b/tools/README.md
@@ -1,74 +1,44 @@
 # Monorepo Infrastructure & Tools

-This document aims to provide an outline for the monorepo's infrastructure as well as the rationale behind the decisions we've made.
+This document outlines the monorepo's infrastructure and the rationale behind the decisions we've made.

 ## Task Orchestration

-Our repository makes aggressive use of [parallelization using PNPM's `--filter` syntax](https://pnpm.io/filtering). This allows us to minimize the amount of time developers spend waiting for tasks like builds to complete. Each project within the monorepo will contain the following scripts if applicable:
+Each project within the monorepo follows a small, consistent script naming scheme so we can run related tasks together with [pnpm's script-pattern execution](https://pnpm.io/cli/run#running-multiple-scripts):

 ```json
 {
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-		"build:project": "pnpm --if-present '/^build:project:.*$/'",
+		"build": "pnpm build:project",
+		"build:project": "pnpm --stream '/^build:project:.*$/'",
 		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
 		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
-		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'"
+		"watch:build": "pnpm watch:build:project",
+		"watch:build:project": "pnpm --stream '/^watch:build:project:.*$/'"
 	}
 }
 ```

-These scripts outline the naming scheme used in order to facilitate [task parallelization using regular expressions](https://pnpm.io/cli/run#running-multiple-scripts). **To ensure consistency across the monorepo, these scripts should not be edited.** New scripts should be added using the naming scheme outlined by the above regular expressions, for example, `build:project:bundle` might be a script to run a tool like `webpack`. We also utilize a number of PNPM options to ensure a positive developer experience:
+`build` aliases `build:project`; `watch:build` aliases `watch:build:project`. New work is added under the regex naming scheme — for example, `build:project:bundle` might be a script that runs `webpack`. pnpm runs every regex match concurrently, so no extra orchestrator (`concurrently`, `--parallel`) is needed.

-- `--if-present`: Ensures that PNPM will not error if a script is not found.
-- `--workspace-concurrency=Infinity`: Runs as many of the tasks in parallel as possible.
-- `--stream`: Makes the script output legible by putting all of their output into a single stream.
-- `--filter="$npm_package_name..."`: This filter tells PNPM that we want to run the script against the current project _and_ all of its dependencies down the graph (dependencies first).
+`--if-present` is only used when the regex may match nothing for a given project (e.g. linting groups where some packages have no rules). If a project has the scripts, drop `--if-present`; if it doesn't, drop the wrapper script entirely.

-To further improve the build times, we used two additional techniques:
+`--parallel` is never used here — it tells pnpm to run the script across the entire workspace rather than the current project, which is not what these wrappers want.

-- In the case of the `build` script, we offer both building packages with (`build`) and without (`build:project`) its dependencies.
-- Using `wireit`-based task output caching (details are below).
+### Cross-project orchestration

-## Task Output Caching
+When one project needs to build another, prefer an explicit `--filter` to the dependency over `--filter="$npm_package_name..."` topological cascade. The cascade implicitly rebuilds every transitive dependency; explicit filters keep the call site honest about what runs.

-Our repository uses [`wireit`](https://github.com/google/wireit) to provide task output caching. In particular, this allows us to cache the output of `build` scripts so that they don't run unnecessarily.
-The goal is to minimize the amount of time that developers spend waiting for projects to build.
+For example, `@woocommerce/plugin-woocommerce` fans out to its three asset producers:

 ```json
 {
-  "scripts": {
-    "build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
-    "build:project": "pnpm --if-present '/^build:project:.*$/'",
-    "build:project:bundle": "wireit"
-  },
-  "wireit": {
-    "build:project:bundle": {
-      "command": "webpack",
-      "files": [
-        "... - package resources as input"
-      ],
-      "output": [
-        "... - package resources as output"
-      ],
-      "dependencies": [
-        "dependencyOutputs"
-      ]
-    },
-    "dependencyOutputs": {
-      "files": [
-        "... - dependencies resources as input",
-        "... - updated automatically by monorepo tooling which hooks up into `pnpm install`",
-        "... - see `.pnpmfile.cjs` file and https://pnpm.io/pnpmfile for details"
-      ]
-    }
-  }
+	"scripts": {
+		"build:project:admin": "pnpm --filter='@woocommerce/admin-library' build",
+		"build:project:blocks": "pnpm --filter='@woocommerce/block-library' build",
+		"build:project:classic-assets": "pnpm --filter='@woocommerce/classic-assets' build"
+	}
 }
 ```

-In the example above, `build:project:bundle` invokes `wireit`, which conditionally executes `webpack` (based on the state of resources).
-A simplified take on `wireit` is the following:
-
-- if input sources are changed or output sources are not generated yet, `wireit` will execute `webpack` command and cache output sources
-- if input sources are unchanged, `wireit` will create output sources from their cached version
+Each producer writes its bundles directly into the plugin's final asset locations (`plugins/woocommerce/assets/{client/admin,client/blocks,css,js}`), so there is no intermediate copy step.
diff --git a/tools/code-analyzer/package.json b/tools/code-analyzer/package.json
index 7ae21b572c3..1edc917f446 100644
--- a/tools/code-analyzer/package.json
+++ b/tools/code-analyzer/package.json
@@ -33,8 +33,7 @@
 		"ts-jest": "29.1.x",
 		"ts-node": "^10.9.2",
 		"tslib": "^2.6.2",
-		"typescript": "5.7.x",
-		"wireit": "0.14.12"
+		"typescript": "5.7.x"
 	},
 	"engines": {
 		"node": "^24.15.0"
diff --git a/tools/monorepo-merge/package.json b/tools/monorepo-merge/package.json
index 591d220a491..e93b3fef506 100644
--- a/tools/monorepo-merge/package.json
+++ b/tools/monorepo-merge/package.json
@@ -39,8 +39,7 @@
 		"shx": "^0.3.4",
 		"ts-node": "^10.9.2",
 		"tslib": "^2.6.2",
-		"typescript": "5.7.x",
-		"wireit": "0.14.12"
+		"typescript": "5.7.x"
 	},
 	"oclif": {
 		"bin": "monorepo-merge",
diff --git a/tools/monorepo-utils/package.json b/tools/monorepo-utils/package.json
index a69d7a36d30..32ff7b0c7c9 100644
--- a/tools/monorepo-utils/package.json
+++ b/tools/monorepo-utils/package.json
@@ -60,8 +60,7 @@
 		"ts-loader": "9.5.x",
 		"typescript": "5.7.x",
 		"webpack": "5.97.x",
-		"webpack-cli": "5.1.x",
-		"wireit": "0.14.12"
+		"webpack-cli": "5.1.x"
 	},
 	"engines": {
 		"node": "^24.15.0"
diff --git a/tools/package-release/package.json b/tools/package-release/package.json
index 830ea1ed975..e3b80472de2 100644
--- a/tools/package-release/package.json
+++ b/tools/package-release/package.json
@@ -37,8 +37,7 @@
 		"shx": "^0.3.4",
 		"ts-node": "^10.9.2",
 		"tslib": "^2.6.2",
-		"typescript": "5.7.x",
-		"wireit": "0.14.12"
+		"typescript": "5.7.x"
 	},
 	"oclif": {
 		"bin": "package-release",
diff --git a/tools/release-posts/package.json b/tools/release-posts/package.json
index 08bca77131e..9c069664dd1 100644
--- a/tools/release-posts/package.json
+++ b/tools/release-posts/package.json
@@ -20,8 +20,7 @@
 		"@types/node": "^24.1.0",
 		"@types/node-fetch": "^2.6.9",
 		"@types/semver": "^7.5.6",
-		"typescript": "5.7.x",
-		"wireit": "0.14.12"
+		"typescript": "5.7.x"
 	},
 	"dependencies": {
 		"@commander-js/extra-typings": "^0.1.0",
diff --git a/tools/storybook/package.json b/tools/storybook/package.json
index 6b7321ba5ef..e769834c4e0 100644
--- a/tools/storybook/package.json
+++ b/tools/storybook/package.json
@@ -11,10 +11,10 @@
 	"author": "Automattic",
 	"license": "GPL-2.0-or-later",
 	"scripts": {
-		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:(project:|storybook).*$/'",
-		"build:storybook": "wireit",
-		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:storybook$/'",
-		"watch:build:storybook": "wireit",
+		"build": "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",
 		"preinstall": "npx only-allow pnpm"
@@ -53,63 +53,6 @@
 		"react-dom": "18.3.x",
 		"storybook": "7.6.19",
 		"typescript": "5.7.x",
-		"webpack": "5.97.x",
-		"wireit": "0.14.12"
-	},
-	"wireit": {
-		"build:storybook": {
-			"command": "storybook build -c ./.storybook --quiet && pnpm run copy-blocks-storybook-iframe",
-			"env": {
-				"STORYBOOK": "true",
-				"BABEL_ENV": "storybook"
-			},
-			"clean": "if-file-deleted",
-			"files": [
-				".storybook/*.{js,jsx,ts,tsx,html}",
-				"import-wp-css-storybook.sh",
-				"../../packages/js/components/src/**/stories/*.story.@(js|tsx)",
-				"../../packages/js/experimental/src/**/stories/*.story.@(js|tsx)",
-				"../../packages/js/onboarding/src/**/stories/*.story.@(js|tsx)",
-				"../../packages/js/product-editor/src/**/*.(stories|story).@(js|tsx)",
-				"../../plugins/woocommerce-admin/client/**/stories/*.story.@(js|tsx)"
-			],
-			"output": [
-				"storybook-static"
-			],
-			"dependencies": [
-				"import-wp-css-for-storybook",
-				"dependencyOutputs"
-			]
-		},
-		"watch:build:storybook": {
-			"command": "storybook dev -c ./.storybook -p 6007 --ci",
-			"service": true,
-			"env": {
-				"STORYBOOK": "true",
-				"BABEL_ENV": "storybook"
-			},
-			"dependencies": [
-				"import-wp-css-for-storybook",
-				"dependencyOutputs"
-			]
-		},
-		"import-wp-css-for-storybook": {
-			"command": "./import-wp-css-storybook.sh",
-			"output": [
-				"wordpress"
-			]
-		},
-		"dependencyOutputs": {
-			"allowUsuallyExcludedPaths": true,
-			"files": [
-				"package.json",
-				"../../pnpm-lock.yaml",
-				"node_modules/@woocommerce/admin-library/build",
-				"node_modules/@woocommerce/block-library/build",
-				"node_modules/@woocommerce/eslint-plugin/configs",
-				"node_modules/@woocommerce/eslint-plugin/rules",
-				"node_modules/@woocommerce/eslint-plugin/index.js"
-			]
-		}
+		"webpack": "5.97.x"
 	}
 }