diff --git a/.changeset/gentle-otters-cheer.md b/.changeset/gentle-otters-cheer.md new file mode 100644 index 000000000..086bd7d2f --- /dev/null +++ b/.changeset/gentle-otters-cheer.md @@ -0,0 +1,5 @@ +--- +"@shopware/nuxt-module": patch +--- + +Register the `#shopware` types in every TypeScript context (app, server/nitro, node, shared) instead of only the app one. This fixes `Cannot find module '#shopware'` in server-side code (e.g. `server/` routes and API builders) when a project uses the Nuxt 4 project-references `tsconfig.json` layout. Projects that ship their own `shopware.d.ts` are referenced in place so their relative imports keep resolving. diff --git a/packages/nuxt-module/src/index.test.ts b/packages/nuxt-module/src/index.test.ts index 6f87d592f..a4cb619a2 100644 --- a/packages/nuxt-module/src/index.test.ts +++ b/packages/nuxt-module/src/index.test.ts @@ -75,25 +75,44 @@ describe("@shopware/nuxt-module", () => { isConfigDeprecatedMock.mockReturnValue(false); }); - it("should inject default shopware.d.ts when project has no custom types", async () => { + const ALL_TYPE_CONTEXTS = { + nuxt: true, + nitro: true, + node: true, + shared: true, + }; + + it("should inject default shopware.d.ts in all type contexts when project has no custom types", async () => { existsSyncMock.mockReturnValue(false); const setup = await getModuleSetup(); await setup({}, createNuxtMock("/tmp/test-project")); - expect(addTypeTemplateMock).toHaveBeenCalledWith({ - filename: "shopware.d.ts", - src: "/mocked-module-dir/../shopware.d.ts", - }); + expect(addTypeTemplateMock).toHaveBeenCalledWith( + { + filename: "shopware.d.ts", + src: "/mocked-module-dir/../shopware.d.ts", + }, + ALL_TYPE_CONTEXTS, + ); }); - it("should skip injecting shopware.d.ts when project has custom types", async () => { + it("should reference the project's own shopware.d.ts in all type contexts when it exists", async () => { existsSyncMock.mockReturnValue(true); const setup = await getModuleSetup(); await setup({}, createNuxtMock("/tmp/test-project")); - expect(addTypeTemplateMock).not.toHaveBeenCalled(); + expect(addTypeTemplateMock).toHaveBeenCalledTimes(1); + const [template, context] = addTypeTemplateMock.mock.calls[0] as [ + { filename: string; getContents: () => string }, + Record, + ]; + expect(template.filename).toBe("shopware.d.ts"); + expect(template.getContents()).toContain( + '/// ', + ); + expect(context).toEqual(ALL_TYPE_CONTEXTS); }); it("should check for shopware.d.ts in the project root directory", async () => { diff --git a/packages/nuxt-module/src/index.ts b/packages/nuxt-module/src/index.ts index 77ee5a885..0ca4fb9ad 100644 --- a/packages/nuxt-module/src/index.ts +++ b/packages/nuxt-module/src/index.ts @@ -81,11 +81,31 @@ export default defineNuxtModule({ }); const projectShopwareTypes = resolve(nuxt.options.rootDir, "shopware.d.ts"); - if (!existsSync(projectShopwareTypes)) { - addTypeTemplate({ - filename: "shopware.d.ts", - src: resolver.resolve("../shopware.d.ts"), - }); + // Register `#shopware` in every type-check context, not just the app one. + const shopwareTypeContexts = { + nuxt: true, + nitro: true, + node: true, + shared: true, + }; + if (existsSync(projectShopwareTypes)) { + // Reference the project's file in place so its relative imports keep resolving. + const referencePath = projectShopwareTypes.replace(/\\/g, "/"); + addTypeTemplate( + { + filename: "shopware.d.ts", + getContents: () => `/// \n`, + }, + shopwareTypeContexts, + ); + } else { + addTypeTemplate( + { + filename: "shopware.d.ts", + src: resolver.resolve("../shopware.d.ts"), + }, + shopwareTypeContexts, + ); } addCustomTab({