Bladeren bron

Merge branch 'master' into minor

Michael Bromley 9 maanden geleden
bovenliggende
commit
98f9507d16
47 gewijzigde bestanden met toevoegingen van 1035 en 382 verwijderingen
  1. 9 0
      CHANGELOG.md
  2. 1 1
      README.md
  3. 1 1
      lerna.json
  4. 32 0
      license/signatures/version1/cla.json
  5. 128 62
      package-lock.json
  6. 1 1
      package.json
  7. 4 4
      packages/admin-ui-plugin/package.json
  8. 2 2
      packages/admin-ui/package.json
  9. 1 1
      packages/admin-ui/src/lib/catalog/src/catalog.module.ts
  10. 2 2
      packages/admin-ui/src/lib/catalog/src/components/product-list/product-list.component.ts
  11. 31 17
      packages/admin-ui/src/lib/catalog/src/components/product-variant-list/product-variant-list.component.html
  12. 2 1
      packages/admin-ui/src/lib/catalog/src/components/product-variant-list/product-variant-list.component.ts
  13. 1 1
      packages/admin-ui/src/lib/core/src/common/version.ts
  14. 3 3
      packages/asset-server-plugin/package.json
  15. 3 3
      packages/cli/package.json
  16. 1 1
      packages/common/package.json
  17. 2 2
      packages/core/package.json
  18. 3 3
      packages/create/package.json
  19. 5 5
      packages/dashboard/package.json
  20. 2 2
      packages/dashboard/vite.config.mts
  21. 0 181
      packages/dashboard/vite/config-loader.ts
  22. 128 0
      packages/dashboard/vite/utils/ast-utils.spec.ts
  23. 119 0
      packages/dashboard/vite/utils/ast-utils.ts
  24. 408 0
      packages/dashboard/vite/utils/config-loader.ts
  25. 7 1
      packages/dashboard/vite/utils/schema-generator.ts
  26. 2 2
      packages/dashboard/vite/utils/ui-config.ts
  27. 2 2
      packages/dashboard/vite/vite-plugin-admin-api-schema.ts
  28. 25 13
      packages/dashboard/vite/vite-plugin-config-loader.ts
  29. 19 15
      packages/dashboard/vite/vite-plugin-dashboard-metadata.ts
  30. 2 2
      packages/dashboard/vite/vite-plugin-gql-tada.ts
  31. 3 2
      packages/dashboard/vite/vite-plugin-ui-config.ts
  32. 2 1
      packages/dev-server/dev-config.ts
  33. 13 12
      packages/dev-server/package.json
  34. 3 1
      packages/dev-server/test-plugins/reviews/dashboard/index.tsx
  35. 25 0
      packages/dev-server/test-plugins/reviews/entities/product-review-translation.entity.ts
  36. 9 6
      packages/dev-server/test-plugins/reviews/entities/product-review.entity.ts
  37. 3 2
      packages/dev-server/test-plugins/reviews/reviews-plugin.ts
  38. 2 1
      packages/dev-server/tsconfig.json
  39. 3 3
      packages/elasticsearch-plugin/package.json
  40. 3 3
      packages/email-plugin/package.json
  41. 3 3
      packages/harden-plugin/package.json
  42. 3 3
      packages/job-queue-plugin/package.json
  43. 4 4
      packages/payments-plugin/package.json
  44. 3 3
      packages/sentry-plugin/package.json
  45. 3 3
      packages/stellate-plugin/package.json
  46. 3 3
      packages/testing/package.json
  47. 4 4
      packages/ui-devkit/package.json

+ 9 - 0
CHANGELOG.md

@@ -1,3 +1,12 @@
+## <small>3.2.3 (2025-04-17)</small>
+
+
+#### Fixes
+
+* **admin-ui** Fix missing product variant list tab ([047eaa5](https://github.com/vendure-ecommerce/vendure/commit/047eaa5))
+* **core** Align Express & types versions ([ba4111b](https://github.com/vendure-ecommerce/vendure/commit/ba4111b))
+* **core** Relax some express typings to prevent v4/v5 types conflicts ([97e53d5](https://github.com/vendure-ecommerce/vendure/commit/97e53d5))
+
 ## <small>3.2.2 (2025-04-03)</small>
 
 #### Fixes

+ 1 - 1
README.md

@@ -114,7 +114,7 @@ cd packages/dev-server
 If you are making changes to the admin ui, you need to start the admin ui independent from the dev-server:
 
 1. `cd packages/admin-ui`
-2. `npm run start`
+2. `npm run dev`
 3. Go to http://localhost:4200 and log in with "superadmin", "superadmin"
 
 This will auto restart when you make changes to the admin ui. You don't need this step when you just use the admin ui just

+ 1 - 1
lerna.json

@@ -1,6 +1,6 @@
 {
     "packages": ["packages/*"],
-    "version": "3.2.2",
+    "version": "3.2.3",
     "npmClient": "npm",
     "command": {
         "version": {

+ 32 - 0
license/signatures/version1/cla.json

@@ -543,6 +543,38 @@
       "created_at": "2025-04-02T13:50:18Z",
       "repoId": 136938012,
       "pullRequestNo": 3438
+    },
+    {
+      "name": "Meriamhrz",
+      "id": 115230043,
+      "comment_id": 2798108582,
+      "created_at": "2025-04-11T22:23:45Z",
+      "repoId": 136938012,
+      "pullRequestNo": 3474
+    },
+    {
+      "name": "Balgha03",
+      "id": 115190679,
+      "comment_id": 2798136806,
+      "created_at": "2025-04-11T22:50:24Z",
+      "repoId": 136938012,
+      "pullRequestNo": 3475
+    },
+    {
+      "name": "TaharMeddeb03",
+      "id": 115250449,
+      "comment_id": 2798802221,
+      "created_at": "2025-04-12T11:56:04Z",
+      "repoId": 136938012,
+      "pullRequestNo": 3477
+    },
+    {
+      "name": "TaharMeddeb03",
+      "id": 115250449,
+      "comment_id": 2798826346,
+      "created_at": "2025-04-12T13:09:16Z",
+      "repoId": 136938012,
+      "pullRequestNo": 3477
     }
   ]
 }

+ 128 - 62
package-lock.json

@@ -4088,7 +4088,6 @@
     },
     "node_modules/@clack/prompts/node_modules/is-unicode-supported": {
       "version": "1.3.0",
-      "extraneous": true,
       "inBundle": true,
       "license": "MIT",
       "engines": {
@@ -14000,6 +13999,7 @@
       "version": "5.1.4",
       "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
       "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
+      "devOptional": true,
       "license": "MIT",
       "dependencies": {
         "@types/estree": "^1.0.0",
@@ -15446,6 +15446,7 @@
       "version": "1.11.13",
       "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.13.tgz",
       "integrity": "sha512-9BXdYz12Wl0zWmZ80PvtjBWeg2ncwJ9L5WJzjhN6yUTZWEV/AwAdVdJnIEp4pro3WyKmAaMxcVOSbhuuOZco5g==",
+      "devOptional": true,
       "hasInstallScript": true,
       "license": "Apache-2.0",
       "dependencies": {
@@ -15503,6 +15504,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -15519,6 +15521,7 @@
       "cpu": [
         "arm"
       ],
+      "dev": true,
       "license": "Apache-2.0",
       "optional": true,
       "os": [
@@ -15535,6 +15538,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -15551,6 +15555,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -15583,6 +15588,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -15599,6 +15605,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -15615,6 +15622,7 @@
       "cpu": [
         "ia32"
       ],
+      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -15631,6 +15639,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -15647,6 +15656,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -15663,6 +15673,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "Apache-2.0 AND MIT",
       "optional": true,
       "os": [
@@ -15676,12 +15687,14 @@
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
       "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
+      "devOptional": true,
       "license": "Apache-2.0"
     },
     "node_modules/@swc/types": {
       "version": "0.1.20",
       "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.20.tgz",
       "integrity": "sha512-/rlIpxwKrhz4BIplXf6nsEHtqlhzuNN34/k3kMAXH4/lvVoA3cdq+60aqVNnyvw2uITEaCi0WV3pxBe4dQqoXQ==",
+      "devOptional": true,
       "license": "Apache-2.0",
       "dependencies": {
         "@swc/counter": "^0.1.3"
@@ -24442,6 +24455,7 @@
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
       "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+      "devOptional": true,
       "license": "MIT"
     },
     "node_modules/esutils": {
@@ -31604,6 +31618,7 @@
       "version": "0.2.5",
       "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz",
       "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
@@ -42818,6 +42833,7 @@
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/unplugin-swc/-/unplugin-swc-1.5.1.tgz",
       "integrity": "sha512-/ZLrPNjChhGx3Z95pxJ4tQgfI6rWqukgYHKflrNB4zAV1izOQuDhkTn55JWeivpBxDCoK7M/TStb2aS/14PS/g==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@rollup/pluginutils": "^5.1.0",
@@ -42832,6 +42848,7 @@
       "version": "1.16.1",
       "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz",
       "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "acorn": "^8.14.0",
@@ -45542,7 +45559,7 @@
     },
     "packages/admin-ui": {
       "name": "@vendure/admin-ui",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "@angular/animations": "^19.2.4",
@@ -45565,7 +45582,7 @@
         "@ng-select/ng-select": "^14.2.6",
         "@ngx-translate/core": "^16.0.4",
         "@ngx-translate/http-loader": "^16.0.1",
-        "@vendure/common": "3.2.2",
+        "@vendure/common": "3.2.3",
         "@webcomponents/custom-elements": "^1.6.0",
         "apollo-angular": "^10.0.3",
         "apollo-upload-client": "^18.0.1",
@@ -45636,7 +45653,7 @@
     },
     "packages/admin-ui-plugin": {
       "name": "@vendure/admin-ui-plugin",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "date-fns": "^2.30.0",
@@ -45646,9 +45663,9 @@
       "devDependencies": {
         "@types/express": "^5.0.1",
         "@types/fs-extra": "^11.0.4",
-        "@vendure/admin-ui": "3.2.2",
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2",
+        "@vendure/admin-ui": "3.2.3",
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3",
         "express": "^5.1.0",
         "rimraf": "^5.0.5",
         "typescript": "5.8.2"
@@ -46004,7 +46021,7 @@
     },
     "packages/asset-server-plugin": {
       "name": "@vendure/asset-server-plugin",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "file-type": "^19.0.0",
@@ -46017,8 +46034,8 @@
         "@types/express": "^5.0.1",
         "@types/fs-extra": "^11.0.4",
         "@types/node-fetch": "^2.6.11",
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2",
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3",
         "express": "^5.1.0",
         "node-fetch": "^2.7.0",
         "rimraf": "^5.0.5",
@@ -46339,11 +46356,11 @@
     },
     "packages/cli": {
       "name": "@vendure/cli",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "@clack/prompts": "^0.7.0",
-        "@vendure/common": "3.2.2",
+        "@vendure/common": "3.2.3",
         "change-case": "^4.1.2",
         "commander": "^11.0.0",
         "dotenv": "^16.4.5",
@@ -46357,7 +46374,7 @@
         "vendure": "dist/cli.js"
       },
       "devDependencies": {
-        "@vendure/core": "3.2.2",
+        "@vendure/core": "3.2.3",
         "typescript": "5.8.2"
       },
       "funding": {
@@ -46392,7 +46409,7 @@
     },
     "packages/common": {
       "name": "@vendure/common",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "devDependencies": {
         "rimraf": "^5.0.5",
@@ -46404,7 +46421,7 @@
     },
     "packages/core": {
       "name": "@vendure/core",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "@apollo/server": "^4.11.3",
@@ -46416,7 +46433,7 @@
         "@nestjs/platform-express": "~11.0.12",
         "@nestjs/terminus": "~11.0.0",
         "@nestjs/typeorm": "~11.0.0",
-        "@vendure/common": "3.2.2",
+        "@vendure/common": "3.2.3",
         "bcrypt": "^5.1.1",
         "body-parser": "^1.20.2",
         "cookie-session": "^2.1.0",
@@ -46845,11 +46862,11 @@
     },
     "packages/create": {
       "name": "@vendure/create",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "@clack/prompts": "^0.7.0",
-        "@vendure/common": "3.2.2",
+        "@vendure/common": "3.2.3",
         "commander": "^11.0.0",
         "cross-spawn": "^7.0.3",
         "fs-extra": "^11.2.0",
@@ -46867,7 +46884,7 @@
         "@types/fs-extra": "^11.0.4",
         "@types/handlebars": "^4.1.0",
         "@types/semver": "^7.5.8",
-        "@vendure/core": "3.2.2",
+        "@vendure/core": "3.2.3",
         "rimraf": "^5.0.5",
         "ts-node": "^10.9.2",
         "typescript": "5.8.2"
@@ -46930,7 +46947,7 @@
     },
     "packages/dashboard": {
       "name": "@vendure/dashboard",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "dependencies": {
         "@dnd-kit/core": "^6.3.1",
         "@dnd-kit/sortable": "^10.0.0",
@@ -46972,8 +46989,8 @@
         "@types/react-dom": "^19.0.4",
         "@types/react-grid-layout": "^1.3.5",
         "@uidotdev/usehooks": "^2.4.1",
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2",
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3",
         "@vitejs/plugin-react": "^4.3.4",
         "awesome-graphql-client": "^2.1.0",
         "class-variance-authority": "^0.7.1",
@@ -46997,8 +47014,8 @@
         "tailwind-merge": "^3.0.1",
         "tailwindcss": "^4.0.6",
         "tailwindcss-animate": "^1.0.7",
+        "tsconfig-paths": "^4.2.0",
         "tw-animate-css": "^1.2.4",
-        "unplugin-swc": "^1.5.1",
         "vite": "^6.1.0",
         "zod": "^3.24.2"
       },
@@ -47285,33 +47302,57 @@
         "node": ">=8"
       }
     },
+    "packages/dashboard/node_modules/strip-bom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+      "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "packages/dashboard/node_modules/tsconfig-paths": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
+      "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
+      "license": "MIT",
+      "dependencies": {
+        "json5": "^2.2.2",
+        "minimist": "^1.2.6",
+        "strip-bom": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "packages/dashboard/node_modules/undici-types": {
       "version": "6.20.0",
       "dev": true,
       "license": "MIT"
     },
     "packages/dev-server": {
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "@nestjs/axios": "^4.0.0",
-        "@vendure/admin-ui-plugin": "3.2.2",
-        "@vendure/asset-server-plugin": "3.2.2",
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2",
-        "@vendure/elasticsearch-plugin": "3.2.2",
-        "@vendure/email-plugin": "3.2.2",
+        "@vendure/admin-ui-plugin": "3.2.3",
+        "@vendure/asset-server-plugin": "3.2.3",
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3",
+        "@vendure/elasticsearch-plugin": "3.2.3",
+        "@vendure/email-plugin": "3.2.3",
         "typescript": "5.8.2"
       },
       "devDependencies": {
-        "@vendure/testing": "3.2.2",
-        "@vendure/ui-devkit": "3.2.2",
+        "@vendure/testing": "3.2.3",
+        "@vendure/ui-devkit": "3.2.3",
         "commander": "^12.0.0",
         "concurrently": "^8.2.2",
         "csv-stringify": "^6.4.6",
         "dayjs": "^1.11.10",
         "jsdom": "^26.0.0",
-        "progress": "^2.0.3"
+        "progress": "^2.0.3",
+        "tsconfig-paths": "^4.2.0"
       }
     },
     "packages/dev-server/node_modules/commander": {
@@ -47322,9 +47363,34 @@
         "node": ">=18"
       }
     },
+    "packages/dev-server/node_modules/strip-bom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+      "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "packages/dev-server/node_modules/tsconfig-paths": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
+      "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "json5": "^2.2.2",
+        "minimist": "^1.2.6",
+        "strip-bom": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "packages/elasticsearch-plugin": {
       "name": "@vendure/elasticsearch-plugin",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "@elastic/elasticsearch": "~7.9.1",
@@ -47332,8 +47398,8 @@
         "fast-deep-equal": "^3.1.3"
       },
       "devDependencies": {
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2",
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3",
         "rimraf": "^5.0.5",
         "typescript": "5.8.2"
       },
@@ -47343,7 +47409,7 @@
     },
     "packages/email-plugin": {
       "name": "@vendure/email-plugin",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "@types/nodemailer": "^6.4.9",
@@ -47359,8 +47425,8 @@
         "@types/express": "^5.0.1",
         "@types/fs-extra": "^11.0.4",
         "@types/mjml": "^4.7.4",
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2",
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3",
         "rimraf": "^5.0.5",
         "typescript": "5.8.2"
       },
@@ -47661,14 +47727,14 @@
     },
     "packages/harden-plugin": {
       "name": "@vendure/harden-plugin",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "graphql-query-complexity": "^0.12.0"
       },
       "devDependencies": {
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2"
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3"
       },
       "funding": {
         "url": "https://github.com/sponsors/michaelbromley"
@@ -47676,12 +47742,12 @@
     },
     "packages/job-queue-plugin": {
       "name": "@vendure/job-queue-plugin",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "devDependencies": {
         "@google-cloud/pubsub": "^2.8.0",
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2",
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3",
         "bullmq": "^5.4.2",
         "ioredis": "^5.3.2",
         "rimraf": "^5.0.5",
@@ -47693,7 +47759,7 @@
     },
     "packages/payments-plugin": {
       "name": "@vendure/payments-plugin",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "currency.js": "2.0.4"
@@ -47702,9 +47768,9 @@
         "@mollie/api-client": "^3.7.0",
         "@types/braintree": "^3.3.11",
         "@types/localtunnel": "2.0.4",
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2",
-        "@vendure/testing": "3.2.2",
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3",
+        "@vendure/testing": "3.2.3",
         "braintree": "^3.22.0",
         "localtunnel": "2.0.2",
         "nock": "^13.1.4",
@@ -47734,12 +47800,12 @@
     },
     "packages/sentry-plugin": {
       "name": "@vendure/sentry-plugin",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "devDependencies": {
         "@sentry/node": "^7.106.1",
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2"
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3"
       },
       "funding": {
         "url": "https://github.com/sponsors/michaelbromley"
@@ -47750,14 +47816,14 @@
     },
     "packages/stellate-plugin": {
       "name": "@vendure/stellate-plugin",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "node-fetch": "^2.7.0"
       },
       "devDependencies": {
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2"
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3"
       },
       "funding": {
         "url": "https://github.com/sponsors/michaelbromley"
@@ -47765,11 +47831,11 @@
     },
     "packages/testing": {
       "name": "@vendure/testing",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "@graphql-typed-document-node/core": "^3.2.0",
-        "@vendure/common": "3.2.2",
+        "@vendure/common": "3.2.3",
         "faker": "^4.1.0",
         "form-data": "^4.0.0",
         "graphql": "^16.10.0",
@@ -47782,7 +47848,7 @@
         "@types/mysql": "^2.15.26",
         "@types/node-fetch": "^2.6.4",
         "@types/pg": "^8.11.2",
-        "@vendure/core": "3.2.2",
+        "@vendure/core": "3.2.3",
         "mysql": "^2.18.1",
         "pg": "^8.11.3",
         "rimraf": "^5.0.5",
@@ -47798,15 +47864,15 @@
     },
     "packages/ui-devkit": {
       "name": "@vendure/ui-devkit",
-      "version": "3.2.2",
+      "version": "3.2.3",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "@angular-devkit/build-angular": "^19.2.5",
         "@angular/cli": "^19.2.5",
         "@angular/compiler": "^19.2.4",
         "@angular/compiler-cli": "^19.2.4",
-        "@vendure/admin-ui": "3.2.2",
-        "@vendure/common": "3.2.2",
+        "@vendure/admin-ui": "3.2.3",
+        "@vendure/common": "3.2.3",
         "chalk": "^4.1.0",
         "chokidar": "^3.6.0",
         "fs-extra": "^11.2.0",
@@ -47817,7 +47883,7 @@
         "@rollup/plugin-node-resolve": "^15.2.3",
         "@rollup/plugin-terser": "^0.4.4",
         "@types/fs-extra": "^11.0.4",
-        "@vendure/core": "3.2.2",
+        "@vendure/core": "3.2.3",
         "react": "^19.0.0",
         "react-dom": "^19.0.0",
         "rimraf": "^5.0.5",

+ 1 - 1
package.json

@@ -20,7 +20,7 @@
     "docs:generate-graphql-docs": "ts-node scripts/docs/generate-graphql-docs.ts --api=shop && ts-node scripts/docs/generate-graphql-docs.ts --api=admin",
     "docs:build": "npm run docs:generate-typescript-docs && npm run docs:generate-graphql-docs",
     "codegen": "tsc -p scripts/codegen/plugins && ts-node scripts/codegen/generate-graphql-types.ts",
-    "version": "npm run check-imports && npm run check-angular-versions && npm run build && npm run check-core-type-defs && npm run generate-changelog && git add CHANGELOG* && git add */version.ts",
+    "version": "npm i && npm run check-imports && npm run check-angular-versions && npm run build && npm run check-core-type-defs && npm run generate-changelog && git add CHANGELOG* && git add */version.ts",
     "test": "lerna run test --stream --no-bail",
     "e2e": "lerna run e2e --stream --no-bail",
     "build": "lerna run build",

+ 4 - 4
packages/admin-ui-plugin/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/admin-ui-plugin",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "main": "lib/index.js",
     "types": "lib/index.d.ts",
     "files": [
@@ -25,9 +25,9 @@
     "devDependencies": {
         "@types/express": "^5.0.1",
         "@types/fs-extra": "^11.0.4",
-        "@vendure/admin-ui": "3.2.2",
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2",
+        "@vendure/admin-ui": "3.2.3",
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3",
         "express": "^5.1.0",
         "rimraf": "^5.0.5",
         "typescript": "5.8.2"

+ 2 - 2
packages/admin-ui/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/admin-ui",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "license": "GPL-3.0-or-later",
     "repository": {
         "type": "git",
@@ -53,7 +53,7 @@
         "@ng-select/ng-select": "^14.2.6",
         "@ngx-translate/core": "^16.0.4",
         "@ngx-translate/http-loader": "^16.0.1",
-        "@vendure/common": "3.2.2",
+        "@vendure/common": "3.2.3",
         "@webcomponents/custom-elements": "^1.6.0",
         "apollo-angular": "^10.0.3",
         "apollo-upload-client": "^18.0.1",

+ 1 - 1
packages/admin-ui/src/lib/catalog/src/catalog.module.ts

@@ -178,7 +178,7 @@ export class CatalogModule {
         });
         pageService.registerPageTab({
             priority: 0,
-            location: 'product-variant-list',
+            location: 'product-list',
             tab: _('catalog.product-variants'),
             route: 'variants',
             component: ProductVariantListComponent,

+ 2 - 2
packages/admin-ui/src/lib/catalog/src/components/product-list/product-list.component.ts

@@ -18,7 +18,7 @@ import { delay, switchMap } from 'rxjs/operators';
     selector: 'vdr-products-list',
     templateUrl: './product-list.component.html',
     styleUrls: ['./product-list.component.scss'],
-    standalone: false
+    standalone: false,
 })
 export class ProductListComponent
     extends TypedBaseListComponent<typeof ProductListQueryDocument, 'products'>
@@ -26,7 +26,7 @@ export class ProductListComponent
 {
     pendingSearchIndexUpdates = 0;
     dataTableListId = 'product-list';
-    pageLocationId = 'product-list';
+    pageLocationId = 'product-list' as const;
     readonly customFields = this.getCustomFieldConfig('Product');
     readonly filters = this.createFilterCollection()
         .addIdFilter()

+ 31 - 17
packages/admin-ui/src/lib/catalog/src/components/product-variant-list/product-variant-list.component.html

@@ -1,9 +1,17 @@
-<vdr-page-block *ngIf="!hideLanguageSelect">
-    <vdr-language-selector
-        [availableLanguageCodes]="availableLanguages$ | async"
-        [currentLanguageCode]="contentLanguage$ | async"
-        (languageCodeChange)="setLanguage($event)"
-    ></vdr-language-selector>
+<vdr-page-block>
+    <vdr-action-bar>
+        <vdr-ab-left>
+            <vdr-language-selector *ngIf="!hideLanguageSelect"
+                                   [availableLanguageCodes]="availableLanguages$ | async"
+                                   [currentLanguageCode]="contentLanguage$ | async"
+                                   (languageCodeChange)="setLanguage($event)"
+            ></vdr-language-selector>
+        </vdr-ab-left>
+        <vdr-ab-right>
+            <vdr-action-bar-items [locationId]="pageLocationId" />
+            <vdr-action-bar-dropdown-menu [locationId]="pageLocationId" />
+        </vdr-ab-right>
+    </vdr-action-bar>
 </vdr-page-block>
 <vdr-data-table-2
     class="mt-2"
@@ -68,9 +76,11 @@
             <a
                 class="button-ghost"
                 [routerLink]="['/catalog/products', variant.productId, 'variants', variant.id]"
-                ><span>{{ variant.name }}</span
-                ><clr-icon shape="arrow right"
-            /></a>
+            ><span>{{ variant.name }}</span
+            >
+                <clr-icon shape="arrow right"
+                />
+            </a>
         </ng-template>
     </vdr-dt2-column>
     <vdr-dt2-column [heading]="'catalog.sku' | translate" id="sku" [sort]="sorts.get('sku')">
@@ -81,11 +91,13 @@
     <vdr-dt2-column [heading]="'common.enabled' | translate" id="enabled">
         <ng-template let-variant="item">
             <vdr-chip *ngIf="variant.enabled" colorType="success">{{
-                'common.enabled' | translate
-            }}</vdr-chip>
+                    'common.enabled' | translate
+                }}
+            </vdr-chip>
             <vdr-chip *ngIf="!variant.enabled" colorType="warning">{{
-                'common.disabled' | translate
-            }}</vdr-chip>
+                    'common.disabled' | translate
+                }}
+            </vdr-chip>
         </ng-template>
     </vdr-dt2-column>
     <vdr-dt2-column
@@ -97,7 +109,8 @@
             {{ variant.price | localeCurrency : variant.currencyCode }}
         </ng-template>
     </vdr-dt2-column>
-    <vdr-dt2-column [heading]="'common.price-with-tax' | translate" id="price-with-tax" [sort]="sorts.get('priceWithTax')">
+    <vdr-dt2-column [heading]="'common.price-with-tax' | translate" id="price-with-tax"
+                    [sort]="sorts.get('priceWithTax')">
         <ng-template let-variant="item">
             {{ variant.priceWithTax | localeCurrency : variant.currencyCode }}
         </ng-template>
@@ -108,10 +121,11 @@
             <vdr-chip *ngFor="let stockLevel of variant.stockLevels" [title]="stockLevel.stockLocation?.name">
                 <div class="flex center">
                     <div>
-                        {{ stockLevel.stockOnHand
+                        {{
+                            stockLevel.stockOnHand
                         }}<span class="ml-1" *ngIf="stockLevel.stockAllocated"
-                            >({{ stockLevel.stockAllocated }} allocated)</span
-                        >
+                    >({{ stockLevel.stockAllocated }} allocated)</span
+                    >
                     </div>
                 </div>
             </vdr-chip>

+ 2 - 1
packages/admin-ui/src/lib/catalog/src/components/product-variant-list/product-variant-list.component.ts

@@ -12,7 +12,7 @@ import {
     selector: 'vdr-product-variant-list',
     templateUrl: './product-variant-list.component.html',
     styleUrls: ['./product-variant-list.component.scss'],
-    standalone: false
+    standalone: false,
 })
 export class ProductVariantListComponent
     extends TypedBaseListComponent<typeof ProductVariantListQueryDocument, 'productVariants'>
@@ -20,6 +20,7 @@ export class ProductVariantListComponent
 {
     @Input() productId?: string;
     @Input() hideLanguageSelect = false;
+    pageLocationId = 'product-variant-list' as const;
     @Input() dataTableId: DataTableLocationId | undefined;
     readonly customFields = this.getCustomFieldConfig('ProductVariant');
     readonly filters = this.createFilterCollection()

+ 1 - 1
packages/admin-ui/src/lib/core/src/common/version.ts

@@ -1,2 +1,2 @@
 // Auto-generated by the set-version.js script.
-export const ADMIN_UI_VERSION = '3.2.2';
+export const ADMIN_UI_VERSION = '3.2.3';

+ 3 - 3
packages/asset-server-plugin/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/asset-server-plugin",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "main": "lib/index.js",
     "types": "lib/index.d.ts",
     "files": [
@@ -30,8 +30,8 @@
         "@types/express": "^5.0.1",
         "@types/fs-extra": "^11.0.4",
         "@types/node-fetch": "^2.6.11",
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2",
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3",
         "express": "^5.1.0",
         "node-fetch": "^2.7.0",
         "rimraf": "^5.0.5",

+ 3 - 3
packages/cli/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/cli",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "description": "A modern, headless ecommerce framework",
     "repository": {
         "type": "git",
@@ -35,7 +35,7 @@
     ],
     "dependencies": {
         "@clack/prompts": "^0.7.0",
-        "@vendure/common": "3.2.2",
+        "@vendure/common": "3.2.3",
         "change-case": "^4.1.2",
         "commander": "^11.0.0",
         "dotenv": "^16.4.5",
@@ -46,7 +46,7 @@
         "tsconfig-paths": "^4.2.0"
     },
     "devDependencies": {
-        "@vendure/core": "3.2.2",
+        "@vendure/core": "3.2.3",
         "typescript": "5.8.2"
     }
 }

+ 1 - 1
packages/common/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/common",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "main": "index.js",
     "license": "GPL-3.0-or-later",
     "repository": {

+ 2 - 2
packages/core/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/core",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "description": "A modern, headless ecommerce framework",
     "repository": {
         "type": "git",
@@ -49,7 +49,7 @@
         "@nestjs/platform-express": "~11.0.12",
         "@nestjs/terminus": "~11.0.0",
         "@nestjs/typeorm": "~11.0.0",
-        "@vendure/common": "3.2.2",
+        "@vendure/common": "3.2.3",
         "bcrypt": "^5.1.1",
         "body-parser": "^1.20.2",
         "cookie-session": "^2.1.0",

+ 3 - 3
packages/create/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/create",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "license": "GPL-3.0-or-later",
     "bin": {
         "create": "./index.js"
@@ -31,14 +31,14 @@
         "@types/fs-extra": "^11.0.4",
         "@types/handlebars": "^4.1.0",
         "@types/semver": "^7.5.8",
-        "@vendure/core": "3.2.2",
+        "@vendure/core": "3.2.3",
         "rimraf": "^5.0.5",
         "ts-node": "^10.9.2",
         "typescript": "5.8.2"
     },
     "dependencies": {
         "@clack/prompts": "^0.7.0",
-        "@vendure/common": "3.2.2",
+        "@vendure/common": "3.2.3",
         "commander": "^11.0.0",
         "cross-spawn": "^7.0.3",
         "fs-extra": "^11.2.0",

+ 5 - 5
packages/dashboard/package.json

@@ -1,7 +1,7 @@
 {
     "name": "@vendure/dashboard",
     "private": false,
-    "version": "3.2.2",
+    "version": "3.2.3",
     "type": "module",
     "repository": {
         "type": "git",
@@ -85,8 +85,8 @@
         "@types/react-dom": "^19.0.4",
         "@types/react-grid-layout": "^1.3.5",
         "@uidotdev/usehooks": "^2.4.1",
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2",
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3",
         "@vitejs/plugin-react": "^4.3.4",
         "awesome-graphql-client": "^2.1.0",
         "class-variance-authority": "^0.7.1",
@@ -111,9 +111,9 @@
         "tailwindcss": "^4.0.6",
         "tailwindcss-animate": "^1.0.7",
         "tw-animate-css": "^1.2.4",
-        "unplugin-swc": "^1.5.1",
         "vite": "^6.1.0",
-        "zod": "^3.24.2"
+        "zod": "^3.24.2",
+        "tsconfig-paths": "^4.2.0"
     },
     "devDependencies": {
         "@eslint/js": "^9.19.0",

+ 2 - 2
packages/dashboard/vite.config.mts

@@ -10,8 +10,8 @@ import { defineConfig } from 'vitest/config';
 export default ({ mode }: { mode: string }) => {
     process.env = { ...process.env, ...loadEnv(mode, process.cwd()) };
 
-    const adminApiHost = process.env.VITE_ADMIN_API_HOST || 'http://localhost';
-    const adminApiPort = process.env.VITE_ADMIN_API_PORT ? +process.env.VITE_ADMIN_API_PORT : '3000';
+    const adminApiHost = process.env.VITE_ADMIN_API_HOST || 'http://localhost:3000';
+    const adminApiPort = process.env.VITE_ADMIN_API_PORT ? +process.env.VITE_ADMIN_API_PORT : 'auto';
 
     process.env.IS_LOCAL_DEV = adminApiHost.includes('localhost') ? 'true' : 'false';
 

+ 0 - 181
packages/dashboard/vite/config-loader.ts

@@ -1,181 +0,0 @@
-import { Options, parse, transform } from '@swc/core';
-import { BindingIdentifier, ModuleItem, Pattern, Statement } from '@swc/types';
-import { VendureConfig } from '@vendure/core';
-import fs from 'fs-extra';
-import path from 'path';
-import { pathToFileURL } from 'url';
-
-export interface ConfigLoaderOptions {
-    vendureConfigPath: string;
-    tempDir: string;
-    vendureConfigExport?: string;
-}
-
-/**
- * @description
- * This function compiles the given Vendure config file and any imported relative files (i.e.
- * project files, not npm packages) into a temporary directory, and returns the compiled config.
- *
- * The reason we need to do this is that Vendure code makes use of TypeScript experimental decorators
- * (e.g. for NestJS decorators and TypeORM column decorators) which are not supported by esbuild.
- *
- * In Vite, when we load some TypeScript into the top-level Vite config file (in the end-user project), Vite
- * internally uses esbuild to temporarily compile that TypeScript code. Unfortunately, esbuild does not support
- * these experimental decorators, errors will be thrown as soon as e.g. a TypeORM column decorator is encountered.
- *
- * To work around this, we compile the Vendure config file and all its imports using SWC, which does support
- * these experimental decorators. The compiled files are then loaded by Vite, which is able to handle the compiled
- * JavaScript output.
- */
-export async function loadVendureConfig(
-    options: ConfigLoaderOptions,
-): Promise<{ vendureConfig: VendureConfig; exportedSymbolName: string }> {
-    const { vendureConfigPath, vendureConfigExport, tempDir } = options;
-    const outputPath = tempDir;
-    const configFileName = path.basename(vendureConfigPath);
-    const inputRootDir = path.dirname(vendureConfigPath);
-    await fs.remove(outputPath);
-    await compileFile(inputRootDir, vendureConfigPath, outputPath);
-    const compiledConfigFilePath = pathToFileURL(path.join(outputPath, configFileName)).href.replace(
-        /.ts$/,
-        '.js',
-    );
-    // create package.json with type commonjs and save it to the output dir
-    await fs.writeFile(path.join(outputPath, 'package.json'), JSON.stringify({ type: 'commonjs' }, null, 2));
-
-    // We need to figure out the symbol exported by the config file by
-    // analyzing the AST and finding an export with the type "VendureConfig"
-    const ast = await parse(await fs.readFile(vendureConfigPath, 'utf-8'), {
-        syntax: 'typescript',
-        decorators: true,
-    });
-    const detectedExportedSymbolName = findConfigExport(ast.body);
-    const configExportedSymbolName = detectedExportedSymbolName || vendureConfigExport;
-    if (!configExportedSymbolName) {
-        throw new Error(
-            `Could not find a variable exported as VendureConfig. Please specify the name of the exported variable using the "vendureConfigExport" option.`,
-        );
-    }
-    const config = await import(compiledConfigFilePath).then(m => m[configExportedSymbolName]);
-    if (!config) {
-        throw new Error(
-            `Could not find a variable exported as VendureConfig with the name "${configExportedSymbolName}".`,
-        );
-    }
-    return { vendureConfig: config, exportedSymbolName: configExportedSymbolName };
-}
-
-/**
- * Given the AST of a TypeScript file, finds the name of the variable exported as VendureConfig.
- */
-function findConfigExport(statements: ModuleItem[]): string | undefined {
-    for (const statement of statements) {
-        if (statement.type === 'ExportDeclaration') {
-            if (statement.declaration.type === 'VariableDeclaration') {
-                for (const declaration of statement.declaration.declarations) {
-                    if (isBindingIdentifier(declaration.id)) {
-                        const typeRef = declaration.id.typeAnnotation?.typeAnnotation;
-                        if (typeRef?.type === 'TsTypeReference') {
-                            if (
-                                typeRef.typeName.type === 'Identifier' &&
-                                typeRef.typeName.value === 'VendureConfig'
-                            ) {
-                                return declaration.id.value;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-    return undefined;
-}
-
-function isBindingIdentifier(id: Pattern): id is BindingIdentifier {
-    return id.type === 'Identifier' && !!(id as BindingIdentifier).typeAnnotation;
-}
-
-export async function compileFile(
-    inputRootDir: string,
-    inputPath: string,
-    outputDir: string,
-    compiledFiles = new Set<string>(),
-): Promise<void> {
-    if (compiledFiles.has(inputPath)) {
-        return;
-    }
-    compiledFiles.add(inputPath);
-
-    // Ensure output directory exists
-    await fs.ensureDir(outputDir);
-
-    // Read the source file
-    const source = await fs.readFile(inputPath, 'utf-8');
-
-    // Transform config
-    const config: Options = {
-        filename: inputPath,
-        sourceMaps: true,
-        jsc: {
-            parser: {
-                syntax: 'typescript',
-                tsx: false,
-                decorators: true,
-            },
-            target: 'es2020',
-            loose: false,
-            transform: {
-                legacyDecorator: true,
-                decoratorMetadata: true,
-            },
-        },
-        module: {
-            type: 'commonjs',
-            strict: true,
-            strictMode: true,
-            lazy: false,
-            noInterop: false,
-        },
-    };
-
-    // Transform the code using SWC
-    const result = await transform(source, config);
-
-    // Generate output file path
-    const relativePath = path.relative(inputRootDir, inputPath);
-    const outputPath = path.join(outputDir, relativePath).replace(/\.ts$/, '.js');
-
-    // Ensure the subdirectory for the output file exists
-    await fs.ensureDir(path.dirname(outputPath));
-
-    // Write the transformed code
-    await fs.writeFile(outputPath, result.code);
-
-    // Write source map if available
-    if (result.map) {
-        await fs.writeFile(`${outputPath}.map`, JSON.stringify(result.map));
-    }
-
-    // Parse the source to find relative imports
-    const ast = await parse(source, { syntax: 'typescript', decorators: true });
-    const importPaths = new Set<string>();
-
-    function collectImports(node: any) {
-        if (node.type === 'ImportDeclaration' && node.source.value.startsWith('.')) {
-            const importPath = path.resolve(path.dirname(inputPath), node.source.value);
-            importPaths.add(importPath + '.ts');
-        }
-        for (const key in node) {
-            if (node[key] && typeof node[key] === 'object') {
-                collectImports(node[key]);
-            }
-        }
-    }
-
-    collectImports(ast);
-
-    // Recursively compile all relative imports
-    for (const importPath of importPaths) {
-        await compileFile(inputRootDir, importPath, outputDir, compiledFiles);
-    }
-}

+ 128 - 0
packages/dashboard/vite/utils/ast-utils.spec.ts

@@ -0,0 +1,128 @@
+import ts from 'typescript';
+import { describe, it, expect } from 'vitest';
+
+import { getPluginInfo, findConfigExport } from './ast-utils.js';
+
+describe('getPluginInfo', () => {
+    it('should return undefined when no plugin class is found', () => {
+        const sourceText = `
+            class NotAPlugin {
+                constructor() {}
+            }
+        `;
+        const sourceFile = ts.createSourceFile('path/to/test.ts', sourceText, ts.ScriptTarget.Latest, true);
+        const result = getPluginInfo(sourceFile);
+        expect(result).toBeUndefined();
+    });
+
+    it('should return plugin info when a valid plugin class is found', () => {
+        const sourceText = `
+            @VendurePlugin({
+                imports: [],
+                providers: []
+            })
+            class TestPlugin {
+                constructor() {}
+            }
+        `;
+        const sourceFile = ts.createSourceFile('path/to/test.ts', sourceText, ts.ScriptTarget.Latest, true);
+        const result = getPluginInfo(sourceFile);
+        expect(result).toEqual({
+            name: 'TestPlugin',
+            path: 'path/to',
+            dashboardEntryPath: undefined,
+        });
+    });
+
+    it('should handle multiple classes but only return the plugin one', () => {
+        const sourceText = `
+            class NotAPlugin {
+                constructor() {}
+            }
+
+            @VendurePlugin({
+                imports: [],
+                providers: []
+            })
+            class TestPlugin {
+                constructor() {}
+            }
+
+            class AnotherClass {
+                constructor() {}
+            }
+        `;
+        const sourceFile = ts.createSourceFile('path/to/test.ts', sourceText, ts.ScriptTarget.Latest, true);
+        const result = getPluginInfo(sourceFile);
+        expect(result).toEqual({
+            name: 'TestPlugin',
+            path: 'path/to',
+            dashboardEntryPath: undefined,
+        });
+    });
+
+    it('should determine the dashboard entry path when it is provided', () => {
+        const sourceText = `
+            @VendurePlugin({
+                imports: [],
+                providers: [],
+                dashboard: './dashboard/index.tsx',
+            })
+            class TestPlugin {
+                constructor() {}
+            }
+        `;
+        const sourceFile = ts.createSourceFile('path/to/test.ts', sourceText, ts.ScriptTarget.Latest, true);
+        const result = getPluginInfo(sourceFile);
+        expect(result).toEqual({
+            name: 'TestPlugin',
+            path: 'path/to',
+            dashboardEntryPath: './dashboard/index.tsx',
+        });
+    });
+});
+
+describe('findConfigExport', () => {
+    it('should return undefined when no VendureConfig export is found', () => {
+        const sourceText = `
+            export const notConfig = {
+                some: 'value'
+            };
+        `;
+        const sourceFile = ts.createSourceFile('path/to/test.ts', sourceText, ts.ScriptTarget.Latest, true);
+        const result = findConfigExport(sourceFile);
+        expect(result).toBeUndefined();
+    });
+
+    it('should find exported variable with VendureConfig type', () => {
+        const sourceText = `
+            import { VendureConfig } from '@vendure/core';
+            
+            export const config: VendureConfig = {
+                authOptions: {
+                    tokenMethod: 'bearer'
+                }
+            };
+        `;
+        const sourceFile = ts.createSourceFile('path/to/test.ts', sourceText, ts.ScriptTarget.Latest, true);
+        const result = findConfigExport(sourceFile);
+        expect(result).toBe('config');
+    });
+
+    it('should find exported variable with VendureConfig type among other exports', () => {
+        const sourceText = `
+            import { VendureConfig } from '@vendure/core';
+            
+            export const otherExport = 'value';
+            export const config: VendureConfig = {
+                authOptions: {
+                    tokenMethod: 'bearer'
+                }
+            };
+            export const anotherExport = 123;
+        `;
+        const sourceFile = ts.createSourceFile('path/to/test.ts', sourceText, ts.ScriptTarget.Latest, true);
+        const result = findConfigExport(sourceFile);
+        expect(result).toBe('config');
+    });
+});

+ 119 - 0
packages/dashboard/vite/utils/ast-utils.ts

@@ -0,0 +1,119 @@
+import path from 'path';
+import ts from 'typescript';
+
+import { PluginInfo } from './config-loader.js';
+
+/**
+ * Get the plugin info from the source file.
+ */
+export function getPluginInfo(sourceFile: ts.SourceFile): PluginInfo | undefined {
+    const classDeclaration = sourceFile.statements.find(statement => {
+        return (
+            statement.kind === ts.SyntaxKind.ClassDeclaration &&
+            statement.getText().includes('@VendurePlugin(')
+        );
+    });
+    if (classDeclaration) {
+        const identifier = classDeclaration.getChildren().find(child => {
+            return child.kind === ts.SyntaxKind.Identifier;
+        });
+        const dashboardEntryPath = classDeclaration
+            .getChildren()
+            .map(child => {
+                if (child.kind === ts.SyntaxKind.SyntaxList) {
+                    const pluginDecorator = child.getChildren().find(_child => {
+                        return _child.kind === ts.SyntaxKind.Decorator;
+                    });
+                    if (pluginDecorator) {
+                        const callExpression = findFirstDescendantOfKind(
+                            pluginDecorator,
+                            ts.SyntaxKind.CallExpression,
+                        );
+                        if (callExpression) {
+                            const objectLiteral = findFirstDescendantOfKind(
+                                callExpression,
+                                ts.SyntaxKind.ObjectLiteralExpression,
+                            );
+                            if (objectLiteral && ts.isObjectLiteralExpression(objectLiteral)) {
+                                // Now find the specific 'dashboard' property
+                                const dashboardProperty = objectLiteral.properties.find(
+                                    prop =>
+                                        ts.isPropertyAssignment(prop) && prop.name?.getText() === 'dashboard',
+                                );
+
+                                if (
+                                    dashboardProperty &&
+                                    ts.isPropertyAssignment(dashboardProperty) &&
+                                    ts.isStringLiteral(dashboardProperty.initializer)
+                                ) {
+                                    const dashboardPath = dashboardProperty.initializer.text;
+                                    return dashboardPath;
+                                }
+                            }
+                        }
+                    }
+                }
+            })
+            .filter(Boolean)?.[0];
+        if (identifier) {
+            return {
+                name: identifier.getText(),
+                pluginPath: path.dirname(sourceFile.fileName),
+                dashboardEntryPath,
+            };
+        }
+    }
+}
+
+/**
+ * Given the AST of a TypeScript file, finds the name of the variable exported as VendureConfig.
+ */
+export function findConfigExport(sourceFile: ts.SourceFile): string | undefined {
+    let exportedSymbolName: string | undefined;
+
+    function visit(node: ts.Node) {
+        if (
+            ts.isVariableStatement(node) &&
+            node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)
+        ) {
+            node.declarationList.declarations.forEach(declaration => {
+                if (ts.isVariableDeclaration(declaration)) {
+                    const typeNode = declaration.type;
+                    if (typeNode && ts.isTypeReferenceNode(typeNode)) {
+                        const typeName = typeNode.typeName;
+                        if (ts.isIdentifier(typeName) && typeName.text === 'VendureConfig') {
+                            if (ts.isIdentifier(declaration.name)) {
+                                exportedSymbolName = declaration.name.text;
+                            }
+                        }
+                    }
+                }
+            });
+        }
+        ts.forEachChild(node, visit);
+    }
+
+    visit(sourceFile);
+    return exportedSymbolName;
+}
+
+function findFirstDescendantOfKind(node: ts.Node, kind: ts.SyntaxKind): ts.Node | undefined {
+    let foundNode: ts.Node | undefined;
+
+    function visit(_node: ts.Node) {
+        if (foundNode) {
+            // Stop searching if we already found it
+            return;
+        }
+        if (_node.kind === kind) {
+            foundNode = _node;
+            return;
+        }
+        // Recursively visit children
+        ts.forEachChild(_node, visit);
+    }
+
+    // Start the traversal from the initial node's children
+    ts.forEachChild(node, visit);
+    return foundNode;
+}

+ 408 - 0
packages/dashboard/vite/utils/config-loader.ts

@@ -0,0 +1,408 @@
+import { VendureConfig } from '@vendure/core';
+import fs from 'fs-extra';
+import path from 'path';
+import tsConfigPaths from 'tsconfig-paths';
+import * as ts from 'typescript';
+import { pathToFileURL } from 'url';
+
+import { findConfigExport, getPluginInfo } from './ast-utils.js';
+
+type Logger = {
+    info: (message: string) => void;
+    warn: (message: string) => void;
+    debug: (message: string) => void;
+};
+
+export type PluginInfo = {
+    name: string;
+    pluginPath: string;
+    dashboardEntryPath: string | undefined;
+};
+
+const defaultLogger: Logger = {
+    info: (message: string) => {
+        /* noop */
+    },
+    warn: (message: string) => {
+        /* noop */
+    },
+    debug: (message: string) => {
+        /* noop */
+    },
+};
+
+export interface ConfigLoaderOptions {
+    vendureConfigPath: string;
+    tempDir: string;
+    vendureConfigExport?: string;
+    logger?: Logger;
+}
+
+export interface LoadVendureConfigResult {
+    vendureConfig: VendureConfig;
+    exportedSymbolName: string;
+    pluginInfo: PluginInfo[];
+}
+
+/**
+ * @description
+ * This function compiles the given Vendure config file and any imported relative files (i.e.
+ * project files, not npm packages) into a temporary directory, and returns the compiled config.
+ *
+ * The reason we need to do this is that Vendure code makes use of TypeScript experimental decorators
+ * (e.g. for NestJS decorators and TypeORM column decorators) which are not supported by esbuild.
+ *
+ * In Vite, when we load some TypeScript into the top-level Vite config file (in the end-user project), Vite
+ * internally uses esbuild to temporarily compile that TypeScript code. Unfortunately, esbuild does not support
+ * these experimental decorators, errors will be thrown as soon as e.g. a TypeORM column decorator is encountered.
+ *
+ * To work around this, we compile the Vendure config file and all its imports using the TypeScript compiler,
+ * which fully supports these experimental decorators. The compiled files are then loaded by Vite, which is able
+ * to handle the compiled JavaScript output.
+ */
+export async function loadVendureConfig(options: ConfigLoaderOptions): Promise<LoadVendureConfigResult> {
+    const { vendureConfigPath, vendureConfigExport, tempDir } = options;
+    const logger = options.logger || defaultLogger;
+    const outputPath = tempDir;
+    const configFileName = path.basename(vendureConfigPath);
+    const inputRootDir = path.dirname(vendureConfigPath);
+    await fs.remove(outputPath);
+    const pluginInfo = await compileFile(inputRootDir, vendureConfigPath, outputPath, logger);
+    const compiledConfigFilePath = pathToFileURL(path.join(outputPath, configFileName)).href.replace(
+        /.ts$/,
+        '.js',
+    );
+    // create package.json with type commonjs and save it to the output dir
+    await fs.writeFile(path.join(outputPath, 'package.json'), JSON.stringify({ type: 'commonjs' }, null, 2));
+
+    // We need to figure out the symbol exported by the config file by
+    // analyzing the AST and finding an export with the type "VendureConfig"
+    const sourceFile = ts.createSourceFile(
+        vendureConfigPath,
+        await fs.readFile(vendureConfigPath, 'utf-8'),
+        ts.ScriptTarget.Latest,
+        true,
+    );
+    const detectedExportedSymbolName = findConfigExport(sourceFile);
+    const configExportedSymbolName = detectedExportedSymbolName || vendureConfigExport;
+    if (!configExportedSymbolName) {
+        throw new Error(
+            `Could not find a variable exported as VendureConfig. Please specify the name of the exported variable using the "vendureConfigExport" option.`,
+        );
+    }
+
+    // Register path aliases from tsconfig before importing
+    const tsConfigInfo = await findTsConfigPaths(vendureConfigPath, logger);
+    if (tsConfigInfo) {
+        tsConfigPaths.register({
+            baseUrl: outputPath,
+            paths: tsConfigInfo.paths,
+        });
+    }
+
+    const config = await import(compiledConfigFilePath).then(m => m[configExportedSymbolName]);
+    if (!config) {
+        throw new Error(
+            `Could not find a variable exported as VendureConfig with the name "${configExportedSymbolName}".`,
+        );
+    }
+    return { vendureConfig: config, exportedSymbolName: configExportedSymbolName, pluginInfo };
+}
+
+/**
+ * Finds and parses tsconfig files in the given directory and its parent directories.
+ * Returns the paths configuration if found.
+ */
+async function findTsConfigPaths(
+    configPath: string,
+    logger: Logger,
+): Promise<{ baseUrl: string; paths: Record<string, string[]> } | undefined> {
+    const configDir = path.dirname(configPath);
+    let currentDir = configDir;
+
+    while (currentDir !== path.parse(currentDir).root) {
+        try {
+            const files = await fs.readdir(currentDir);
+            const tsConfigFiles = files.filter(file => /^tsconfig(\..*)?\.json$/.test(file));
+
+            for (const fileName of tsConfigFiles) {
+                const tsConfigPath = path.join(currentDir, fileName);
+                try {
+                    const tsConfigContent = await fs.readFile(tsConfigPath, 'utf-8');
+                    // Use JSON5 or similar parser if comments are expected in tsconfig.json
+                    // For simplicity, assuming standard JSON here. Handle parse errors.
+                    const tsConfig = JSON.parse(tsConfigContent);
+                    const compilerOptions = tsConfig.compilerOptions || {};
+
+                    if (compilerOptions.paths) {
+                        // Determine the effective baseUrl: explicitly set or the directory of tsconfig.json
+                        const tsConfigBaseUrl = path.resolve(currentDir, compilerOptions.baseUrl || '.');
+                        const paths: Record<string, string[]> = {};
+
+                        for (const [alias, patterns] of Object.entries(compilerOptions.paths)) {
+                            // Store paths as defined in tsconfig, they will be relative to baseUrl
+                            paths[alias] = (patterns as string[]).map(pattern =>
+                                // Normalize slashes for consistency, keep relative
+                                pattern.replace(/\\/g, '/'),
+                            );
+                        }
+                        logger.debug(
+                            `Found tsconfig paths in ${tsConfigPath}: ${JSON.stringify({ baseUrl: tsConfigBaseUrl, paths }, null, 2)}`,
+                        );
+                        return { baseUrl: tsConfigBaseUrl, paths };
+                    }
+                } catch (e: any) {
+                    logger.warn(
+                        `Could not read or parse tsconfig file ${tsConfigPath}: ${e.message as string}`,
+                    );
+                }
+            }
+        } catch (e: any) {
+            // If we can't read the directory, just continue to the parent
+            logger.warn(`Could not read directory ${currentDir}: ${e.message as string}`);
+        }
+        currentDir = path.dirname(currentDir);
+    }
+    logger.debug(`No tsconfig paths found traversing up from ${configDir}`);
+    return undefined;
+}
+
+export async function compileFile(
+    inputRootDir: string,
+    inputPath: string,
+    outputDir: string,
+    logger: Logger = defaultLogger,
+    compiledFiles = new Set<string>(),
+    isRoot = true,
+    pluginInfo: PluginInfo[] = [],
+): Promise<PluginInfo[]> {
+    const absoluteInputPath = path.resolve(inputPath);
+    if (compiledFiles.has(absoluteInputPath)) {
+        return pluginInfo;
+    }
+    compiledFiles.add(absoluteInputPath);
+
+    // Ensure output directory exists
+    await fs.ensureDir(outputDir);
+
+    // Read the source file
+    const source = await fs.readFile(inputPath, 'utf-8');
+
+    // Parse the source to find relative imports
+    const sourceFile = ts.createSourceFile(absoluteInputPath, source, ts.ScriptTarget.Latest, true);
+
+    const importPaths = new Set<string>();
+    let tsConfigInfo: { baseUrl: string; paths: Record<string, string[]> } | undefined;
+
+    if (isRoot) {
+        tsConfigInfo = await findTsConfigPaths(absoluteInputPath, logger);
+        if (tsConfigInfo) {
+            logger?.debug(`Using TypeScript configuration: ${JSON.stringify(tsConfigInfo, null, 2)}`);
+        }
+    }
+
+    async function collectImports(node: ts.Node) {
+        if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
+            const importPath = node.moduleSpecifier.text;
+
+            // Handle relative imports
+            if (importPath.startsWith('.')) {
+                const resolvedPath = path.resolve(path.dirname(absoluteInputPath), importPath);
+                let resolvedTsPath = resolvedPath + '.ts';
+                // Also check for .tsx if .ts doesn't exist
+                if (!(await fs.pathExists(resolvedTsPath))) {
+                    const resolvedTsxPath = resolvedPath + '.tsx';
+                    if (await fs.pathExists(resolvedTsxPath)) {
+                        resolvedTsPath = resolvedTsxPath;
+                    } else {
+                        // If neither exists, maybe it's an index file?
+                        const resolvedIndexPath = path.join(resolvedPath, 'index.ts');
+                        if (await fs.pathExists(resolvedIndexPath)) {
+                            resolvedTsPath = resolvedIndexPath;
+                        } else {
+                            const resolvedIndexTsxPath = path.join(resolvedPath, 'index.tsx');
+                            if (await fs.pathExists(resolvedIndexTsxPath)) {
+                                resolvedTsPath = resolvedIndexTsxPath;
+                            } else {
+                                // If still not found, log a warning or let TS handle it later
+                                logger?.warn(
+                                    `Could not resolve relative import "${importPath}" from "${absoluteInputPath}" to an existing .ts/.tsx file.`,
+                                );
+                                // Do not add to importPaths if we can't verify existence
+                                return;
+                            }
+                        }
+                    }
+                }
+                importPaths.add(resolvedTsPath);
+            }
+            // Handle path aliases if tsConfigInfo exists
+            else if (tsConfigInfo) {
+                // Attempt to resolve using path aliases
+                let resolved = false;
+                for (const [alias, patterns] of Object.entries(tsConfigInfo.paths)) {
+                    const aliasPrefix = alias.replace('*', '');
+                    const aliasSuffix = alias.endsWith('*') ? '*' : '';
+
+                    if (
+                        importPath.startsWith(aliasPrefix) &&
+                        (aliasSuffix === '*' || importPath === aliasPrefix)
+                    ) {
+                        const remainingImportPath = importPath.slice(aliasPrefix.length);
+                        for (const pattern of patterns) {
+                            const patternPrefix = pattern.replace('*', '');
+                            const patternSuffix = pattern.endsWith('*') ? '*' : '';
+                            // Ensure suffix match consistency (* vs exact)
+                            if (aliasSuffix !== patternSuffix) continue;
+
+                            const potentialPathBase = path.resolve(tsConfigInfo.baseUrl, patternPrefix);
+                            const resolvedPath = path.join(potentialPathBase, remainingImportPath);
+
+                            let resolvedTsPath = resolvedPath + '.ts';
+                            // Similar existence checks as relative paths
+                            if (!(await fs.pathExists(resolvedTsPath))) {
+                                const resolvedTsxPath = resolvedPath + '.tsx';
+                                if (await fs.pathExists(resolvedTsxPath)) {
+                                    resolvedTsPath = resolvedTsxPath;
+                                } else {
+                                    const resolvedIndexPath = path.join(resolvedPath, 'index.ts');
+                                    if (await fs.pathExists(resolvedIndexPath)) {
+                                        resolvedTsPath = resolvedIndexPath;
+                                    } else {
+                                        const resolvedIndexTsxPath = path.join(resolvedPath, 'index.tsx');
+                                        if (await fs.pathExists(resolvedIndexTsxPath)) {
+                                            resolvedTsPath = resolvedIndexTsxPath;
+                                        } else {
+                                            // Path doesn't resolve to a file for this pattern
+                                            continue;
+                                        }
+                                    }
+                                }
+                            }
+                            // Add the first successful resolution for this alias
+                            importPaths.add(resolvedTsPath);
+                            resolved = true;
+                            break; // Stop checking patterns for this alias
+                        }
+                    }
+                    if (resolved) break; // Stop checking other aliases if resolved
+                }
+            }
+            // For all other imports (node_modules, etc), we should still add them to be processed
+            // by the TypeScript compiler, even if we can't resolve them to a file
+            else {
+                // Add the import path as is - TypeScript will handle resolution
+                // importPaths.add(importPath);
+            }
+        } else {
+            const children = node.getChildren();
+            for (const child of children) {
+                // Only process nodes that could contain import statements
+                if (
+                    ts.isSourceFile(child) ||
+                    ts.isModuleBlock(child) ||
+                    ts.isModuleDeclaration(child) ||
+                    ts.isImportDeclaration(child) ||
+                    child.kind === ts.SyntaxKind.SyntaxList
+                ) {
+                    await collectImports(child);
+                }
+            }
+        }
+    }
+
+    // Start collecting imports from the source file
+    await collectImports(sourceFile);
+
+    const extractedPluginInfo = getPluginInfo(sourceFile);
+    if (extractedPluginInfo) {
+        pluginInfo.push(extractedPluginInfo);
+    }
+
+    // Store the tsConfigInfo on the first call if found
+    const rootTsConfigInfo = isRoot ? tsConfigInfo : undefined;
+
+    // Recursively collect all files that need to be compiled
+    for (const importPath of importPaths) {
+        // Pass rootTsConfigInfo down, but set isRoot to false
+        await compileFile(inputRootDir, importPath, outputDir, logger, compiledFiles, false, pluginInfo);
+    }
+
+    // If this is the root file (the one that started the compilation),
+    // use the TypeScript compiler API to compile all files together
+    if (isRoot) {
+        logger.info(`Starting compilation for ${compiledFiles.size} files...`);
+        const allFiles = Array.from(compiledFiles);
+        const compilerOptions: ts.CompilerOptions = {
+            // Base options
+            target: ts.ScriptTarget.ES2020,
+            module: ts.ModuleKind.CommonJS, // Output CommonJS for Node compatibility
+            experimentalDecorators: true,
+            emitDecoratorMetadata: true,
+            esModuleInterop: true,
+            skipLibCheck: true, // Faster compilation
+            forceConsistentCasingInFileNames: true,
+            moduleResolution: ts.ModuleResolutionKind.NodeJs, // Use Node.js module resolution
+            incremental: false, // No need for incremental compilation
+            noEmitOnError: false, // Continue emitting even with errors
+            isolatedModules: true, // Treat files as separate modules
+            strict: false, // Disable strict type checking for speed
+            noUnusedLocals: false, // Skip unused locals check
+            noUnusedParameters: false, // Skip unused parameters check
+
+            // Output options
+            outDir: outputDir, // Output directory for all compiled files
+            sourceMap: false, // Generate source maps
+            declaration: false, // Don't generate .d.ts files
+
+            // Path resolution options - use info found from tsconfig
+            baseUrl: rootTsConfigInfo ? rootTsConfigInfo.baseUrl : undefined, // Let TS handle resolution if no baseUrl
+            paths: rootTsConfigInfo ? rootTsConfigInfo.paths : undefined,
+            // rootDir: inputRootDir, // Often inferred correctly, can cause issues if set explicitly sometimes
+            allowJs: true, // Allow JS files if needed, though we primarily collect TS
+            resolveJsonModule: true, // Allow importing JSON
+        };
+
+        logger.debug(`compilerOptions: ${JSON.stringify(compilerOptions, null, 2)}`);
+
+        // Create a Program to represent the compilation context
+        const program = ts.createProgram(allFiles, compilerOptions);
+        logger.info(`Emitting compiled files to ${outputDir}`);
+        const emitResult = program.emit();
+
+        const hasEmitErrors = reportDiagnostics(program, emitResult, logger);
+
+        if (hasEmitErrors) {
+            throw new Error('TypeScript compilation failed with errors.');
+        }
+
+        logger.info(`Successfully compiled ${allFiles.length} files to ${outputDir}`);
+    }
+    return pluginInfo;
+}
+
+function reportDiagnostics(program: ts.Program, emitResult: ts.EmitResult, logger: Logger) {
+    const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
+    let hasEmitErrors = emitResult.emitSkipped;
+    allDiagnostics.forEach(diagnostic => {
+        if (diagnostic.file && diagnostic.start) {
+            const { line, character } = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
+            const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
+            const logFn = diagnostic.category === ts.DiagnosticCategory.Error ? logger.warn : logger.info;
+            logFn(
+                `TS${diagnostic.code} ${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`,
+            );
+            if (diagnostic.category === ts.DiagnosticCategory.Error) {
+                hasEmitErrors = true;
+            }
+        } else {
+            const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
+            const logFn = diagnostic.category === ts.DiagnosticCategory.Error ? logger.warn : logger.info;
+            logFn(`TS${diagnostic.code}: ${message}`);
+            if (diagnostic.category === ts.DiagnosticCategory.Error) {
+                hasEmitErrors = true;
+            }
+        }
+    });
+    return hasEmitErrors;
+}

+ 7 - 1
packages/dashboard/vite/schema-generator.ts → packages/dashboard/vite/utils/schema-generator.ts

@@ -11,14 +11,20 @@ import {
 import { buildSchema } from 'graphql';
 import { GraphQLSchema } from 'graphql';
 
-let schemaPromise: Promise<GraphQLSchema>;
+let schemaPromise: Promise<GraphQLSchema> | undefined;
 
+/**
+ * @description
+ * This function generates a GraphQL schema from the Vendure config.
+ * It is used to generate the schema for the dashboard.
+ */
 export async function generateSchema({
     vendureConfig,
 }: {
     vendureConfig: VendureConfig;
 }): Promise<GraphQLSchema> {
     if (!schemaPromise) {
+        /* eslint-disable-next-line @typescript-eslint/no-misused-promises */
         schemaPromise = new Promise(async (resolve, reject) => {
             resetConfig();
             await setConfig(vendureConfig ?? {});

+ 2 - 2
packages/dashboard/vite/ui-config.ts → packages/dashboard/vite/utils/ui-config.ts

@@ -6,8 +6,8 @@ import {
 import { AdminUiConfig } from '@vendure/common/lib/shared-types';
 import { VendureConfig } from '@vendure/core';
 
-import { defaultAvailableLocales } from './constants.js';
-import { defaultLocale, defaultLanguage, defaultAvailableLanguages } from './constants.js';
+import { defaultAvailableLocales } from '../constants.js';
+import { defaultLocale, defaultLanguage, defaultAvailableLanguages } from '../constants.js';
 
 export function getAdminUiConfig(
     config: VendureConfig,

+ 2 - 2
packages/dashboard/vite/vite-plugin-admin-api-schema.ts

@@ -21,7 +21,7 @@ import {
 } from 'graphql';
 import { Plugin } from 'vite';
 
-import { generateSchema } from './schema-generator.js';
+import { generateSchema } from './utils/schema-generator.js';
 import { ConfigLoaderApi, getConfigLoaderApi } from './vite-plugin-config-loader.js';
 
 export type FieldInfoTuple = readonly [
@@ -61,7 +61,7 @@ export function adminApiSchemaPlugin(): Plugin {
             configLoaderApi = getConfigLoaderApi(plugins);
         },
         async buildStart() {
-            const vendureConfig = await configLoaderApi.getVendureConfig();
+            const { vendureConfig } = await configLoaderApi.getVendureConfig();
             if (!schemaInfo) {
                 const safeSchema = await generateSchema({ vendureConfig });
                 schemaInfo = generateSchemaInfo(safeSchema);

+ 25 - 13
packages/dashboard/vite/vite-plugin-config-loader.ts

@@ -1,10 +1,9 @@
-import { VendureConfig } from '@vendure/core';
 import { Plugin } from 'vite';
 
-import { ConfigLoaderOptions, loadVendureConfig } from './config-loader.js';
+import { ConfigLoaderOptions, loadVendureConfig, LoadVendureConfigResult } from './utils/config-loader.js';
 
 export interface ConfigLoaderApi {
-    getVendureConfig(): Promise<VendureConfig>;
+    getVendureConfig(): Promise<LoadVendureConfigResult>;
 }
 
 export const configLoaderName = 'vendure:config-loader';
@@ -14,20 +13,33 @@ export const configLoaderName = 'vendure:config-loader';
  * makes it available to other plugins via the `ConfigLoaderApi`.
  */
 export function configLoaderPlugin(options: ConfigLoaderOptions): Plugin {
-    let vendureConfig: VendureConfig;
+    let result: LoadVendureConfigResult;
     const onConfigLoaded: Array<() => void> = [];
     return {
         name: configLoaderName,
         async buildStart() {
-            this.info(`Loading Vendure config...`);
+            this.info(
+                `Loading Vendure config. This can take a short while depending on the size of your project...`,
+            );
             try {
-                const result = await loadVendureConfig({
+                const startTime = Date.now();
+                result = await loadVendureConfig({
                     tempDir: options.tempDir,
                     vendureConfigPath: options.vendureConfigPath,
                     vendureConfigExport: options.vendureConfigExport,
+                    logger: {
+                        info: (message: string) => this.info(message),
+                        warn: (message: string) => this.warn(message),
+                        debug: (message: string) => this.debug(message),
+                    },
                 });
-                vendureConfig = result.vendureConfig;
-                this.info(`Vendure config loaded (using export "${result.exportedSymbolName}")`);
+                const endTime = Date.now();
+                const duration = endTime - startTime;
+                const pluginNames = result.pluginInfo.map(p => p.name).join(', ');
+                this.info(`Found ${result.pluginInfo.length} plugins: ${pluginNames}`);
+                this.info(
+                    `Vendure config loaded (using export "${result.exportedSymbolName}") in ${duration}ms`,
+                );
             } catch (e: unknown) {
                 if (e instanceof Error) {
                     this.error(`Error loading Vendure config: ${e.message}`);
@@ -36,13 +48,13 @@ export function configLoaderPlugin(options: ConfigLoaderOptions): Plugin {
             onConfigLoaded.forEach(fn => fn());
         },
         api: {
-            getVendureConfig(): Promise<VendureConfig> {
-                if (vendureConfig) {
-                    return Promise.resolve(vendureConfig);
+            getVendureConfig(): Promise<LoadVendureConfigResult> {
+                if (result) {
+                    return Promise.resolve(result);
                 } else {
-                    return new Promise<VendureConfig>(resolve => {
+                    return new Promise<LoadVendureConfigResult>(resolve => {
                         onConfigLoaded.push(() => {
-                            resolve(vendureConfig);
+                            resolve(result);
                         });
                     });
                 }

+ 19 - 15
packages/dashboard/vite/vite-plugin-dashboard-metadata.ts

@@ -1,8 +1,7 @@
-import { VendureConfig } from '@vendure/core';
-import { getPluginDashboardExtensions } from '@vendure/core';
 import path from 'path';
 import { Plugin } from 'vite';
 
+import { LoadVendureConfigResult } from './utils/config-loader.js';
 import { ConfigLoaderApi, getConfigLoaderApi } from './vite-plugin-config-loader.js';
 
 const virtualModuleId = 'virtual:dashboard-extensions';
@@ -15,7 +14,7 @@ const resolvedVirtualModuleId = `\0${virtualModuleId}`;
  */
 export function dashboardMetadataPlugin(options: { rootDir: string }): Plugin {
     let configLoaderApi: ConfigLoaderApi;
-    let vendureConfig: VendureConfig;
+    let loadVendureConfigResult: LoadVendureConfigResult;
     return {
         name: 'vendure:dashboard-extensions-metadata',
         configResolved({ plugins }) {
@@ -28,22 +27,27 @@ export function dashboardMetadataPlugin(options: { rootDir: string }): Plugin {
         },
         async load(id) {
             if (id === resolvedVirtualModuleId) {
-                if (!vendureConfig) {
-                    vendureConfig = await configLoaderApi.getVendureConfig();
+                if (!loadVendureConfigResult) {
+                    loadVendureConfigResult = await configLoaderApi.getVendureConfig();
                 }
-                const extensions = getPluginDashboardExtensions(vendureConfig.plugins ?? []);
-                const extensionData: Array<{ importPath: string }> = extensions.map(extension => {
-                    const providedPath = typeof extension === 'string' ? extension : extension.location;
-                    const jsPath = normalizeImportPath(options.rootDir, providedPath);
-                    return { importPath: `./${jsPath}` };
-                });
+                const { pluginInfo } = loadVendureConfigResult;
+                const pluginsWithExtensions =
+                    pluginInfo
+                        ?.map(
+                            ({ dashboardEntryPath, pluginPath }) =>
+                                dashboardEntryPath && path.join(pluginPath, dashboardEntryPath),
+                        )
+                        .filter(x => x != null) ?? [];
 
-                this.info(`Found ${extensionData.length} Dashboard extensions`);
+                this.info(`Found ${pluginsWithExtensions.length} Dashboard extensions`);
                 return `
                     export async function runDashboardExtensions() {
-                        ${extensionData.map(extension => `await import('${extension.importPath}');`).join('\n')}
-                    }
-                `;
+                        ${pluginsWithExtensions
+                            .map(extension => {
+                                return `await import('${extension}');`;
+                            })
+                            .join('\n')}
+                }`;
             }
         },
     };

+ 2 - 2
packages/dashboard/vite/vite-plugin-gql-tada.ts

@@ -4,7 +4,7 @@ import { printSchema } from 'graphql';
 import * as path from 'path';
 import { Plugin } from 'vite';
 
-import { generateSchema } from './schema-generator.js';
+import { generateSchema } from './utils/schema-generator.js';
 import { ConfigLoaderApi, getConfigLoaderApi } from './vite-plugin-config-loader.js';
 
 export function gqlTadaPlugin(options: {
@@ -20,7 +20,7 @@ export function gqlTadaPlugin(options: {
             configLoaderApi = getConfigLoaderApi(plugins);
         },
         async buildStart() {
-            const vendureConfig = await configLoaderApi.getVendureConfig();
+            const { vendureConfig } = await configLoaderApi.getVendureConfig();
             const safeSchema = await generateSchema({ vendureConfig });
 
             const tsConfigContent = {

+ 3 - 2
packages/dashboard/vite/vite-plugin-ui-config.ts

@@ -2,7 +2,7 @@ import { AdminUiConfig, VendureConfig } from '@vendure/core';
 import path from 'path';
 import { Plugin } from 'vite';
 
-import { getAdminUiConfig } from './ui-config.js';
+import { getAdminUiConfig } from './utils/ui-config.js';
 import { ConfigLoaderApi, getConfigLoaderApi } from './vite-plugin-config-loader.js';
 
 const virtualModuleId = 'virtual:vendure-ui-config';
@@ -38,7 +38,8 @@ export function uiConfigPlugin({ adminUiConfig }: UiConfigPluginOptions): Plugin
         async load(id) {
             if (id === resolvedVirtualModuleId) {
                 if (!vendureConfig) {
-                    vendureConfig = await configLoaderApi.getVendureConfig();
+                    const result = await configLoaderApi.getVendureConfig();
+                    vendureConfig = result.vendureConfig;
                 }
 
                 const config = getAdminUiConfig(vendureConfig, adminUiConfig);

+ 2 - 1
packages/dev-server/dev-config.ts

@@ -19,7 +19,8 @@ import 'dotenv/config';
 import path from 'path';
 import { DataSourceOptions } from 'typeorm';
 
-import { ReviewsPlugin } from './test-plugins/reviews/reviews-plugin';
+import { MultivendorPlugin } from './example-plugins/multivendor-plugin/multivendor.plugin';
+import { ReviewsPlugin } from '@plugins/test-plugins/reviews/reviews-plugin';
 
 /**
  * Config settings used during development

+ 13 - 12
packages/dev-server/package.json

@@ -1,13 +1,13 @@
 {
     "name": "dev-server",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "main": "index.js",
     "license": "GPL-3.0-or-later",
     "private": true,
     "scripts": {
         "populate": "node -r ts-node/register -r dotenv/config populate-dev-server.ts",
-        "dev:server": "node -r ts-node/register -r dotenv/config index.ts",
-        "dev:worker": "node -r ts-node/register -r dotenv/config index-worker.ts",
+        "dev:server": "node -r ts-node/register -r dotenv/config -r tsconfig-paths/register index.ts",
+        "dev:worker": "node -r ts-node/register -r dotenv/config -r tsconfig-paths/register index-worker.ts",
         "dev": "concurrently npm:dev:*",
         "dashboard:dev": "vite dev",
         "load-test:1k": "node -r ts-node/register load-testing/run-load-test.ts 1000",
@@ -16,22 +16,23 @@
     },
     "dependencies": {
         "@nestjs/axios": "^4.0.0",
-        "@vendure/admin-ui-plugin": "3.2.2",
-        "@vendure/asset-server-plugin": "3.2.2",
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2",
-        "@vendure/elasticsearch-plugin": "3.2.2",
-        "@vendure/email-plugin": "3.2.2",
+        "@vendure/admin-ui-plugin": "3.2.3",
+        "@vendure/asset-server-plugin": "3.2.3",
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3",
+        "@vendure/elasticsearch-plugin": "3.2.3",
+        "@vendure/email-plugin": "3.2.3",
         "typescript": "5.8.2"
     },
     "devDependencies": {
-        "@vendure/testing": "3.2.2",
-        "@vendure/ui-devkit": "3.2.2",
+        "@vendure/testing": "3.2.3",
+        "@vendure/ui-devkit": "3.2.3",
         "commander": "^12.0.0",
         "concurrently": "^8.2.2",
         "csv-stringify": "^6.4.6",
         "dayjs": "^1.11.10",
         "jsdom": "^26.0.0",
-        "progress": "^2.0.3"
+        "progress": "^2.0.3",
+        "tsconfig-paths": "^4.2.0"
     }
 }

+ 3 - 1
packages/dev-server/test-plugins/reviews/dashboard/index.tsx

@@ -18,7 +18,9 @@ export default defineDashboardExtension({
         {
             label: 'Custom Action Bar Item',
             component: props => {
-                return <Button>YOLO swag</Button>;
+                return <Button type="button" onClick={() => {
+                    console.log('Clicked custom action bar item');
+                }}>Test Button</Button>;
             },
             locationId: 'product-detail',
         },

+ 25 - 0
packages/dev-server/test-plugins/reviews/entities/product-review-translation.entity.ts

@@ -0,0 +1,25 @@
+import { DeepPartial } from '@vendure/common/lib/shared-types';
+import { LanguageCode, Translation, VendureEntity } from '@vendure/core';
+import { Column, Entity, Index, ManyToOne } from 'typeorm';
+
+import { ProductReview } from './product-review.entity';
+
+@Entity()
+export class ProductReviewTranslation
+    extends VendureEntity
+    implements Translation<ProductReview>
+{
+    constructor(input?: DeepPartial<Translation<ProductReviewTranslation>>) {
+        super(input);
+    }
+
+    @Column('varchar')
+    languageCode: LanguageCode;
+
+    @Column('varchar')
+    text: string; // same name as the translatable field in the base entity
+
+    @Index()
+    @ManyToOne(() => ProductReview, base => base.translations, { onDelete: 'CASCADE' })
+    base: ProductReview;
+}

+ 9 - 6
packages/dev-server/test-plugins/reviews/entities/product-review.entity.ts

@@ -1,18 +1,18 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
-import { Customer, DeepPartial, Product, ProductVariant, VendureEntity } from '@vendure/core';
-import { Column, Entity, ManyToOne } from 'typeorm';
-
+import { Customer, DeepPartial, LocaleString, Product, ProductVariant, Translatable, Translation, VendureEntity } from '@vendure/core';
+import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
 import { ReviewState } from '../types';
+import { ProductReviewTranslation } from './product-review-translation.entity';
 
 @Entity()
-export class ProductReview extends VendureEntity {
+export class ProductReview extends VendureEntity implements Translatable {
     constructor(input?: DeepPartial<ProductReview>) {
         super(input);
     }
-
     @ManyToOne(type => Product)
     product: Product;
 
+    text: LocaleString;
+
     @ManyToOne(type => ProductVariant)
     productVariant: ProductVariant | null;
 
@@ -48,4 +48,7 @@ export class ProductReview extends VendureEntity {
 
     @Column({ nullable: true, default: null })
     responseCreatedAt: Date;
+
+    @OneToMany(() => ProductReviewTranslation, translation => translation.base, { eager: true })
+    translations: Array<Translation<ProductReview>>;
 }

+ 3 - 2
packages/dev-server/test-plugins/reviews/reviews-plugin.ts

@@ -8,10 +8,11 @@ import { ProductReviewAdminResolver } from './api/product-review-admin.resolver'
 import { ProductReviewEntityResolver } from './api/product-review-entity.resolver';
 import { ProductReviewShopResolver } from './api/product-review-shop.resolver';
 import { ProductReview } from './entities/product-review.entity';
+import { ProductReviewTranslation } from './entities/product-review-translation.entity';
 
 @VendurePlugin({
     imports: [PluginCommonModule],
-    entities: [ProductReview],
+    entities: [ProductReview, ProductReviewTranslation],
     adminApiExtensions: {
         schema: adminApiExtensions,
         resolvers: [ProductEntityResolver, ProductReviewAdminResolver, ProductReviewEntityResolver],
@@ -48,7 +49,7 @@ import { ProductReview } from './entities/product-review.entity';
         });
         return config;
     },
-    dashboard: path.join(__dirname, './dashboard/index.tsx'),
+    dashboard: './dashboard/index.tsx',
 })
 export class ReviewsPlugin {
     static uiExtensions: AdminUiExtension = {

+ 2 - 1
packages/dev-server/tsconfig.json

@@ -5,7 +5,8 @@
         "sourceMap": true,
         "jsx": "react-jsx",
         "paths": {
-            "@vendure/admin-ui/*": ["../admin-ui/package/*"]
+            "@vendure/admin-ui/*": ["../admin-ui/package/*"],
+            "@plugins/*": ["./test-plugins/*"]
         }
     },
     "exclude": ["node_modules"]

+ 3 - 3
packages/elasticsearch-plugin/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/elasticsearch-plugin",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "license": "GPL-3.0-or-later",
     "main": "lib/index.js",
     "types": "lib/index.d.ts",
@@ -30,8 +30,8 @@
         "fast-deep-equal": "^3.1.3"
     },
     "devDependencies": {
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2",
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3",
         "rimraf": "^5.0.5",
         "typescript": "5.8.2"
     }

+ 3 - 3
packages/email-plugin/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/email-plugin",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "license": "GPL-3.0-or-later",
     "main": "lib/index.js",
     "types": "lib/index.d.ts",
@@ -38,8 +38,8 @@
         "@types/express": "^5.0.1",
         "@types/fs-extra": "^11.0.4",
         "@types/mjml": "^4.7.4",
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2",
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3",
         "rimraf": "^5.0.5",
         "typescript": "5.8.2"
     }

+ 3 - 3
packages/harden-plugin/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/harden-plugin",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "license": "GPL-3.0-or-later",
     "main": "lib/index.js",
     "types": "lib/index.d.ts",
@@ -25,7 +25,7 @@
         "graphql-query-complexity": "^0.12.0"
     },
     "devDependencies": {
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2"
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3"
     }
 }

+ 3 - 3
packages/job-queue-plugin/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/job-queue-plugin",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "license": "GPL-3.0-or-later",
     "main": "package/index.js",
     "types": "package/index.d.ts",
@@ -27,8 +27,8 @@
     },
     "devDependencies": {
         "@google-cloud/pubsub": "^2.8.0",
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2",
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3",
         "bullmq": "^5.4.2",
         "ioredis": "^5.3.2",
         "rimraf": "^5.0.5",

+ 4 - 4
packages/payments-plugin/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/payments-plugin",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "license": "GPL-3.0-or-later",
     "main": "package/index.js",
     "types": "package/index.d.ts",
@@ -50,9 +50,9 @@
         "@mollie/api-client": "^3.7.0",
         "@types/braintree": "^3.3.11",
         "@types/localtunnel": "2.0.4",
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2",
-        "@vendure/testing": "3.2.2",
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3",
+        "@vendure/testing": "3.2.3",
         "braintree": "^3.22.0",
         "localtunnel": "2.0.2",
         "nock": "^13.1.4",

+ 3 - 3
packages/sentry-plugin/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/sentry-plugin",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "license": "GPL-3.0-or-later",
     "main": "lib/index.js",
     "types": "lib/index.d.ts",
@@ -26,7 +26,7 @@
     },
     "devDependencies": {
         "@sentry/node": "^7.106.1",
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2"
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3"
     }
 }

+ 3 - 3
packages/stellate-plugin/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/stellate-plugin",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "license": "GPL-3.0-or-later",
     "main": "lib/index.js",
     "types": "lib/index.d.ts",
@@ -25,7 +25,7 @@
         "node-fetch": "^2.7.0"
     },
     "devDependencies": {
-        "@vendure/common": "3.2.2",
-        "@vendure/core": "3.2.2"
+        "@vendure/common": "3.2.3",
+        "@vendure/core": "3.2.3"
     }
 }

+ 3 - 3
packages/testing/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/testing",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "description": "End-to-end testing tools for Vendure projects",
     "keywords": [
         "vendure",
@@ -38,7 +38,7 @@
     },
     "dependencies": {
         "@graphql-typed-document-node/core": "^3.2.0",
-        "@vendure/common": "3.2.2",
+        "@vendure/common": "3.2.3",
         "faker": "^4.1.0",
         "form-data": "^4.0.0",
         "graphql": "^16.10.0",
@@ -51,7 +51,7 @@
         "@types/mysql": "^2.15.26",
         "@types/node-fetch": "^2.6.4",
         "@types/pg": "^8.11.2",
-        "@vendure/core": "3.2.2",
+        "@vendure/core": "3.2.3",
         "mysql": "^2.18.1",
         "pg": "^8.11.3",
         "rimraf": "^5.0.5",

+ 4 - 4
packages/ui-devkit/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/ui-devkit",
-    "version": "3.2.2",
+    "version": "3.2.3",
     "description": "A library for authoring Vendure Admin UI extensions",
     "keywords": [
         "vendure",
@@ -40,8 +40,8 @@
         "@angular/cli": "^19.2.5",
         "@angular/compiler": "^19.2.4",
         "@angular/compiler-cli": "^19.2.4",
-        "@vendure/admin-ui": "3.2.2",
-        "@vendure/common": "3.2.2",
+        "@vendure/admin-ui": "3.2.3",
+        "@vendure/common": "3.2.3",
         "chalk": "^4.1.0",
         "chokidar": "^3.6.0",
         "fs-extra": "^11.2.0",
@@ -52,7 +52,7 @@
         "@rollup/plugin-node-resolve": "^15.2.3",
         "@rollup/plugin-terser": "^0.4.4",
         "@types/fs-extra": "^11.0.4",
-        "@vendure/core": "3.2.2",
+        "@vendure/core": "3.2.3",
         "react": "^19.0.0",
         "react-dom": "^19.0.0",
         "rimraf": "^5.0.5",