Skip to content

Commit b8fc887

Browse files
authored
Fix/rewrites config bug (#928)
1 parent be47979 commit b8fc887

4 files changed

Lines changed: 408 additions & 6 deletions

File tree

.changeset/slick-nights-double.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@headstartwp/next": patch
3+
---
4+
5+
Fix issue with rewrites in multisite setups

.vscode/settings.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,19 @@
55
"phpsab.snifferShowSources": true,
66
"[php]": {
77
"editor.defaultFormatter": "valeryanm.vscode-phpsab"
8-
}
8+
},
9+
"[javascript]": {
10+
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
11+
},
12+
"[typescript]": {
13+
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
14+
},
15+
"[javascriptreact]": {
16+
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
17+
},
18+
"[typescriptreact]": {
19+
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
20+
},
21+
"editor.formatOnSave": true,
22+
"biome.enabled": false
923
}
Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
import { setHeadstartWPConfig } from '@headstartwp/core/utils';
2+
import { Rewrite } from 'next/dist/lib/load-custom-routes';
3+
import { withHeadstartWPConfig } from '../withHeadstartWPConfig';
4+
5+
// Mock fs module
6+
jest.mock('fs', () => ({
7+
existsSync: jest.fn(),
8+
readFileSync: jest.fn(),
9+
}));
10+
11+
// Mock path module
12+
jest.mock('path', () => ({
13+
join: jest.fn((...args) => args.join('/')),
14+
resolve: jest.fn((...args) => args.join('/')),
15+
normalize: jest.fn((path) => path),
16+
}));
17+
18+
// Mock require.resolve for next package.json
19+
jest.mock('module', () => ({
20+
...jest.requireActual('module'),
21+
createRequire: jest.fn(() => ({
22+
resolve: jest.fn((path) => {
23+
if (path === 'next/package.json') {
24+
return '/mock/path/next/package.json';
25+
}
26+
throw new Error('Module not found');
27+
}),
28+
})),
29+
}));
30+
31+
describe('withHeadstartWPConfig - Host Check', () => {
32+
let mockExistsSync: jest.Mock;
33+
let mockReadFileSync: jest.Mock;
34+
35+
beforeEach(() => {
36+
// Reset config before each test
37+
setHeadstartWPConfig({});
38+
39+
// Get the mocked functions
40+
// eslint-disable-next-line global-require
41+
const fs = require('fs');
42+
mockExistsSync = fs.existsSync;
43+
mockReadFileSync = fs.readFileSync;
44+
45+
// Mock file system to return a config file
46+
mockExistsSync.mockImplementation((path: string) => {
47+
if (path.includes('headstartwp.config.js') || path.includes('headless.config.js')) {
48+
return true;
49+
}
50+
return false;
51+
});
52+
53+
// Mock readFileSync to return a valid config
54+
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '14.0.0' }));
55+
});
56+
57+
describe('Single Site Configuration', () => {
58+
it('should add has check when site.host is explicitly defined', async () => {
59+
const headlessConfig = {
60+
sourceUrl: 'https://wp.example.com',
61+
host: 'example.com',
62+
};
63+
64+
const nextConfig = withHeadstartWPConfig({}, headlessConfig);
65+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
66+
67+
expect(Array.isArray(rewrites)).toBe(true);
68+
expect(rewrites).toHaveLength(8);
69+
70+
// Check that all rewrites have the has check
71+
(rewrites as Rewrite[]).forEach((rewrite) => {
72+
expect(rewrite).toHaveProperty('has');
73+
expect(rewrite.has).toEqual([
74+
{ type: 'header', key: 'host', value: 'example.com' },
75+
]);
76+
});
77+
});
78+
79+
it('should add has check when host is inferred from hostUrl', async () => {
80+
const headlessConfig = {
81+
sourceUrl: 'https://wp.example.com',
82+
hostUrl: 'https://example.com',
83+
};
84+
85+
const nextConfig = withHeadstartWPConfig({}, headlessConfig);
86+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
87+
88+
expect(Array.isArray(rewrites)).toBe(true);
89+
expect(rewrites).toHaveLength(8);
90+
91+
// Check that all rewrites have the has check with inferred host
92+
(rewrites as Rewrite[]).forEach((rewrite) => {
93+
expect(rewrite).toHaveProperty('has');
94+
expect(rewrite.has).toEqual([
95+
{ type: 'header', key: 'host', value: 'example.com' },
96+
]);
97+
});
98+
});
99+
100+
it('should not add has check when neither host nor hostUrl is defined', async () => {
101+
const headlessConfig = {
102+
sourceUrl: 'https://wp.example.com',
103+
};
104+
105+
const nextConfig = withHeadstartWPConfig({}, headlessConfig);
106+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
107+
108+
expect(Array.isArray(rewrites)).toBe(true);
109+
expect(rewrites).toHaveLength(8);
110+
111+
// Check that no rewrites have the has check
112+
(rewrites as Rewrite[]).forEach((rewrite) => {
113+
expect(rewrite).not.toHaveProperty('has');
114+
});
115+
});
116+
117+
it('should handle invalid hostUrl gracefully', async () => {
118+
const headlessConfig = {
119+
sourceUrl: 'https://wp.example.com',
120+
hostUrl: 'invalid-url',
121+
};
122+
123+
const nextConfig = withHeadstartWPConfig({}, headlessConfig);
124+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
125+
126+
expect(Array.isArray(rewrites)).toBe(true);
127+
expect(rewrites).toHaveLength(8);
128+
129+
// Check that no rewrites have the has check due to invalid URL
130+
(rewrites as Rewrite[]).forEach((rewrite) => {
131+
expect(rewrite).not.toHaveProperty('has');
132+
});
133+
});
134+
135+
it('should prefer explicitly defined host over hostUrl', async () => {
136+
const headlessConfig = {
137+
sourceUrl: 'https://wp.example.com',
138+
host: 'explicit.example.com',
139+
hostUrl: 'https://inferred.example.com',
140+
};
141+
142+
const nextConfig = withHeadstartWPConfig({}, headlessConfig);
143+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
144+
145+
expect(Array.isArray(rewrites)).toBe(true);
146+
expect(rewrites).toHaveLength(8);
147+
148+
// Check that all rewrites use the explicitly defined host
149+
(rewrites as Rewrite[]).forEach((rewrite) => {
150+
expect(rewrite).toHaveProperty('has');
151+
expect(rewrite.has).toEqual([
152+
{ type: 'header', key: 'host', value: 'explicit.example.com' },
153+
]);
154+
});
155+
});
156+
});
157+
158+
describe('Multisite Configuration', () => {
159+
it('should add different has checks for different sites', async () => {
160+
const headlessConfig = {
161+
sites: [
162+
{
163+
sourceUrl: 'https://wp.site1.com',
164+
host: 'site1.com',
165+
},
166+
{
167+
sourceUrl: 'https://wp.site2.com',
168+
hostUrl: 'https://site2.com',
169+
},
170+
{
171+
sourceUrl: 'https://wp.site3.com',
172+
// No host or hostUrl
173+
},
174+
],
175+
};
176+
177+
const nextConfig = withHeadstartWPConfig({}, headlessConfig);
178+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
179+
180+
expect(Array.isArray(rewrites)).toBe(true);
181+
expect(rewrites).toHaveLength(24); // 8 rewrites per site
182+
183+
// First 8 rewrites should have site1.com host check
184+
for (let i = 0; i < 8; i++) {
185+
expect(rewrites[i]).toHaveProperty('has');
186+
expect(rewrites[i].has).toEqual([
187+
{ type: 'header', key: 'host', value: 'site1.com' },
188+
]);
189+
}
190+
191+
// Next 8 rewrites should have site2.com host check (inferred from hostUrl)
192+
for (let i = 8; i < 16; i++) {
193+
expect(rewrites[i]).toHaveProperty('has');
194+
expect(rewrites[i].has).toEqual([
195+
{ type: 'header', key: 'host', value: 'site2.com' },
196+
]);
197+
}
198+
199+
// Last 8 rewrites should not have has check (no host defined)
200+
for (let i = 16; i < 24; i++) {
201+
expect(rewrites[i]).not.toHaveProperty('has');
202+
}
203+
});
204+
205+
it('should handle mixed host configurations in multisite', async () => {
206+
const headlessConfig = {
207+
sites: [
208+
{
209+
sourceUrl: 'https://wp.example.com',
210+
host: 'example.com',
211+
hostUrl: 'https://example.com',
212+
},
213+
{
214+
sourceUrl: 'https://wp.test.com',
215+
hostUrl: 'https://test.com',
216+
},
217+
],
218+
};
219+
220+
const nextConfig = withHeadstartWPConfig({}, headlessConfig);
221+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
222+
223+
expect(Array.isArray(rewrites)).toBe(true);
224+
expect(rewrites).toHaveLength(16); // 8 rewrites per site
225+
226+
// All rewrites should have has checks
227+
(rewrites as Rewrite[]).forEach((rewrite) => {
228+
expect(rewrite).toHaveProperty('has');
229+
expect(rewrite.has).toHaveLength(1);
230+
expect(rewrite.has?.[0]).toHaveProperty('type', 'header');
231+
expect(rewrite.has?.[0]).toHaveProperty('key', 'host');
232+
expect(['example.com', 'test.com']).toContain(rewrite.has?.[0].value);
233+
});
234+
});
235+
});
236+
237+
describe('Rewrite Structure', () => {
238+
it('should maintain correct rewrite structure with has check', async () => {
239+
const headlessConfig = {
240+
sourceUrl: 'https://wp.example.com',
241+
host: 'example.com',
242+
};
243+
244+
const nextConfig = withHeadstartWPConfig({}, headlessConfig);
245+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
246+
247+
expect(Array.isArray(rewrites)).toBe(true);
248+
249+
// Check specific rewrite patterns
250+
const cacheHealthcheckRewrite = (rewrites as Rewrite[]).find(
251+
(r) => r.source === '/cache-healthcheck',
252+
);
253+
expect(cacheHealthcheckRewrite).toBeDefined();
254+
expect(cacheHealthcheckRewrite).toMatchObject({
255+
source: '/cache-healthcheck',
256+
destination: '/api/cache-healthcheck',
257+
has: [{ type: 'header', key: 'host', value: 'example.com' }],
258+
});
259+
260+
const feedRewrite = (rewrites as Rewrite[]).find((r) => r.source === '/feed');
261+
expect(feedRewrite).toBeDefined();
262+
expect(feedRewrite).toMatchObject({
263+
source: '/feed',
264+
destination: 'https://wp.example.com/feed/?rewrite_urls=1',
265+
has: [{ type: 'header', key: 'host', value: 'example.com' }],
266+
});
267+
});
268+
269+
it('should work with existing rewrites', async () => {
270+
const existingRewrites = [
271+
{
272+
source: '/custom-rewrite',
273+
destination: '/custom-destination',
274+
},
275+
];
276+
277+
const headlessConfig = {
278+
sourceUrl: 'https://wp.example.com',
279+
host: 'example.com',
280+
};
281+
282+
const nextConfig = withHeadstartWPConfig(
283+
{ rewrites: () => Promise.resolve(existingRewrites) },
284+
headlessConfig,
285+
);
286+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
287+
288+
expect(Array.isArray(rewrites)).toBe(true);
289+
expect(rewrites).toHaveLength(9); // 1 existing + 8 default
290+
291+
// Check that existing rewrites are preserved
292+
expect(rewrites[0]).toEqual(existingRewrites[0]);
293+
294+
// Check that default rewrites have has checks
295+
for (let i = 1; i < 9; i++) {
296+
expect(rewrites[i]).toHaveProperty('has');
297+
}
298+
});
299+
});
300+
301+
describe('Edge Cases', () => {
302+
it('should handle empty sites array', async () => {
303+
const headlessConfig = {
304+
sites: [],
305+
};
306+
307+
const nextConfig = withHeadstartWPConfig({}, headlessConfig);
308+
const rewrites = await nextConfig.rewrites?.();
309+
310+
expect(Array.isArray(rewrites)).toBe(true);
311+
expect(rewrites).toHaveLength(0);
312+
});
313+
314+
it('should handle hostUrl with port', async () => {
315+
const headlessConfig = {
316+
sourceUrl: 'https://wp.example.com',
317+
hostUrl: 'https://example.com:3000',
318+
};
319+
320+
const nextConfig = withHeadstartWPConfig({}, headlessConfig);
321+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
322+
323+
expect(Array.isArray(rewrites)).toBe(true);
324+
expect(rewrites).toHaveLength(8);
325+
326+
// Check that host includes port
327+
(rewrites as Rewrite[]).forEach((rewrite) => {
328+
expect(rewrite).toHaveProperty('has');
329+
expect(rewrite.has).toEqual([
330+
{ type: 'header', key: 'host', value: 'example.com:3000' },
331+
]);
332+
});
333+
});
334+
335+
it('should handle hostUrl with subdomain', async () => {
336+
const headlessConfig = {
337+
sourceUrl: 'https://wp.example.com',
338+
hostUrl: 'https://subdomain.example.com',
339+
};
340+
341+
const nextConfig = withHeadstartWPConfig({}, headlessConfig);
342+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
343+
344+
expect(Array.isArray(rewrites)).toBe(true);
345+
expect(rewrites).toHaveLength(8);
346+
347+
// Check that subdomain is preserved
348+
(rewrites as Rewrite[]).forEach((rewrite) => {
349+
expect(rewrite).toHaveProperty('has');
350+
expect(rewrite.has).toEqual([
351+
{ type: 'header', key: 'host', value: 'subdomain.example.com' },
352+
]);
353+
});
354+
});
355+
});
356+
});

0 commit comments

Comments
 (0)