Skip to content

Commit 1baf5f8

Browse files
authored
[CLI] Support .0 versions, e.g. 6.4.0 (#2848)
## Motivation for the change, related issues Allows CLI to load WordPress versions ending with `.0`, such as `6.8.0`, by passing `--wp=6.8.0`. Before this PR, the only way to install exactly the `6.8.0` version was passing the URL to the zip file. `--wp=6.8.0` would not work – the version string is used to construct the download URL (`https://wordpress.org/wordpress-${versionQuery}.zip`), but the actual zip file on WordPress.org is named `6.8.zip`, not `6.8.0`. At the same time, the string `6.8` is implicitly resolves to the latest point release, e.g. `6.8.14`. Resolves #2749 ## Implementation details We just remove the trailing `.0` before constructing the URL. It would be useful to avoid inferring the release URL entirely and request the 6.8.0 details directly from `https://api.wordpress.org/core/version-check/1.7/?channel=beta`, I'm not sure how to do that. Any ideas @dd32? ## Testing Instructions (or ideally a Blueprint) * CI – this PR ships a new test suite `resolve-wordpress-release.spec.ts` * Run `node --no-warnings=ExperimentalWarning --experimental-strip-types --experimental-transform-types --import ./packages/meta/src/node-es-module-loader/register.mts ./packages/playground/cli/src/cli.ts server --wp=6.8.0` and confirm WordPress 6.8.0 is getting installed
1 parent a1f2b75 commit 1baf5f8

File tree

2 files changed

+178
-0
lines changed

2 files changed

+178
-0
lines changed

packages/playground/wordpress/src/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,20 @@ export async function resolveWordPressRelease(versionQuery = 'latest') {
708708
}
709709
}
710710

711+
/**
712+
* Replace "6.8.0" with "6.8" to support installing the exact "6.8.0" release.
713+
*
714+
* The remote release ZIP file URL for 6.8.0 is `https://wordpress.org/wordpress-6.8.zip`.
715+
* However, we already resolve `6.8` to the latest patch version, so that's not an option.
716+
* Therefore, version "6.8.0" can be resolved by requesting a version string "6.8.0", which
717+
* we then convert to "6.8" to construct the correct remote ZIP file URL.
718+
*
719+
* @see https://github.com/WordPress/wordpress-playground/issues/2749
720+
*/
721+
if (versionQuery.match(/^\d+\.\d+\.0$/)) {
722+
versionQuery = versionQuery.split('.').slice(0, 2).join('.');
723+
}
724+
711725
return {
712726
releaseUrl: `https://wordpress.org/wordpress-${versionQuery}.zip`,
713727
version: versionQuery,
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { describe, it, expect, beforeEach, vi } from 'vitest';
2+
3+
// Mock the WordPress API response
4+
const mockApiResponse = {
5+
offers: [
6+
{
7+
version: '6.8.3',
8+
download: 'https://wordpress.org/wordpress-6.8.3.zip',
9+
response: 'autoupdate',
10+
},
11+
{
12+
version: '6.8',
13+
download: 'https://wordpress.org/wordpress-6.8.zip',
14+
response: 'autoupdate',
15+
},
16+
{
17+
version: '6.7.1',
18+
download: 'https://wordpress.org/wordpress-6.7.1.zip',
19+
response: 'autoupdate',
20+
},
21+
{
22+
version: '6.6.2',
23+
download: 'https://wordpress.org/wordpress-6.6.2.zip',
24+
response: 'autoupdate',
25+
},
26+
{
27+
version: '6.9-beta1',
28+
download: 'https://wordpress.org/wordpress-6.9-beta1.zip',
29+
response: 'autoupdate',
30+
},
31+
],
32+
};
33+
34+
// Mock the fetch function before importing the module
35+
const mockFetch = vi.fn(() =>
36+
Promise.resolve({
37+
json: () => Promise.resolve(mockApiResponse),
38+
} as Response)
39+
);
40+
41+
// Mock the common module to bypass memoization
42+
vi.mock('@wp-playground/common', async () => {
43+
const actual = await vi.importActual('@wp-playground/common');
44+
return {
45+
...actual,
46+
createMemoizedFetch: () => mockFetch,
47+
};
48+
});
49+
50+
// Import after mocks are set up
51+
const { resolveWordPressRelease } = await import('../index');
52+
53+
describe('resolveWordPressRelease', () => {
54+
beforeEach(() => {
55+
mockFetch.mockClear();
56+
});
57+
58+
it('resolves latest to the first non-beta version', async () => {
59+
const result = await resolveWordPressRelease('latest');
60+
expect(result.version).toBe('6.8.3');
61+
expect(result.releaseUrl).toBe(
62+
'https://wordpress.org/wordpress-6.8.3.zip'
63+
);
64+
expect(result.source).toBe('api');
65+
});
66+
67+
it('resolves beta to a beta version', async () => {
68+
const result = await resolveWordPressRelease('beta');
69+
expect(result.version).toBe('6.9-beta1');
70+
expect(result.releaseUrl).toBe(
71+
'https://wordpress.org/wordpress-6.9-beta1.zip'
72+
);
73+
expect(result.source).toBe('api');
74+
});
75+
76+
it('resolves exact version match for minor release with .0 suffix', async () => {
77+
const result = await resolveWordPressRelease('6.8.0');
78+
expect(result.version).toBe('6.8');
79+
expect(result.releaseUrl).toBe(
80+
'https://wordpress.org/wordpress-6.8.zip'
81+
);
82+
expect(result.source).toBe('inferred');
83+
});
84+
85+
it('resolves exact version match for minor release without .0 suffix', async () => {
86+
const result = await resolveWordPressRelease('6.8');
87+
expect(result.version).toMatch(/^6\.8\.(?!0$)\d+$/);
88+
expect(result.releaseUrl).toMatch(
89+
/^https:\/\/wordpress\.org\/wordpress-6\.8\.(?!0$)\d+\.zip$/
90+
);
91+
expect(result.source).toBe('api');
92+
});
93+
94+
it('falls back to substring matching when no exact match exists', async () => {
95+
// 6.7 doesn't exist exactly, but 6.7.1 does (starts with 6.7)
96+
const result = await resolveWordPressRelease('6.7');
97+
expect(result.version).toBe('6.7.1');
98+
expect(result.releaseUrl).toBe(
99+
'https://wordpress.org/wordpress-6.7.1.zip'
100+
);
101+
expect(result.source).toBe('api');
102+
});
103+
104+
it('resolves specific patch version', async () => {
105+
const result = await resolveWordPressRelease('6.6.2');
106+
expect(result.version).toBe('6.6.2');
107+
expect(result.releaseUrl).toBe(
108+
'https://wordpress.org/wordpress-6.6.2.zip'
109+
);
110+
expect(result.source).toBe('api');
111+
});
112+
113+
it('returns inferred URL for version not in API', async () => {
114+
const result = await resolveWordPressRelease('5.0');
115+
expect(result.version).toBe('5.0');
116+
expect(result.releaseUrl).toBe(
117+
'https://wordpress.org/wordpress-5.0.zip'
118+
);
119+
expect(result.source).toBe('inferred');
120+
});
121+
122+
it('normalizes .0 suffix for version not in API', async () => {
123+
const result = await resolveWordPressRelease('5.0.0');
124+
expect(result.version).toBe('5.0');
125+
expect(result.releaseUrl).toBe(
126+
'https://wordpress.org/wordpress-5.0.zip'
127+
);
128+
expect(result.source).toBe('inferred');
129+
});
130+
131+
it('resolves trunk to nightly build', async () => {
132+
const result = await resolveWordPressRelease('trunk');
133+
expect(result.version).toContain('nightly-');
134+
expect(result.releaseUrl).toBe(
135+
'https://wordpress.org/nightly-builds/wordpress-latest.zip'
136+
);
137+
expect(result.source).toBe('inferred');
138+
});
139+
140+
it('resolves nightly to nightly build', async () => {
141+
const result = await resolveWordPressRelease('nightly');
142+
expect(result.version).toContain('nightly-');
143+
expect(result.releaseUrl).toBe(
144+
'https://wordpress.org/nightly-builds/wordpress-latest.zip'
145+
);
146+
expect(result.source).toBe('inferred');
147+
});
148+
149+
it('resolves custom URL with HTTPS', async () => {
150+
const customUrl = 'https://example.com/my-wordpress.zip';
151+
const result = await resolveWordPressRelease(customUrl);
152+
expect(result.version).toContain('custom-');
153+
expect(result.releaseUrl).toBe(customUrl);
154+
expect(result.source).toBe('inferred');
155+
});
156+
157+
it('resolves custom URL with HTTP', async () => {
158+
const customUrl = 'http://example.com/my-wordpress.zip';
159+
const result = await resolveWordPressRelease(customUrl);
160+
expect(result.version).toContain('custom-');
161+
expect(result.releaseUrl).toBe(customUrl);
162+
expect(result.source).toBe('inferred');
163+
});
164+
});

0 commit comments

Comments
 (0)