Skip to content

Commit 4a9e32e

Browse files
authored
[PHP] Refresh the latest PHP versions before recompiling (#2372)
This PR refreshes the supported-php-versions.mjs file with the latest PHP versions when you attempt to rebuild any PHP version (but only if the list is older than 24 hours). This will help Playground stay up to date with latest PHP point releases. Right now it's a few point versions behind. ## Testing instructions * Manually set `lastRefreshed` to a past date in `supported-php-versions` * Run npm run recompile:php:node:asyncify:8.4 * Confirm the versions were refreshed before the build and that it builds 8.4.10 instead of 8.4.0 cc @zaerl
1 parent dda41d5 commit 4a9e32e

File tree

3 files changed

+345
-5
lines changed

3 files changed

+345
-5
lines changed

packages/php-wasm/compile/build.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,32 @@ import util from 'util';
33
import fs from 'fs';
44
const rmAsync = util.promisify(fs.rm);
55
import { spawn } from 'child_process';
6-
import { phpVersions } from '../supported-php-versions.mjs';
6+
import { phpVersions, lastRefreshed } from '../supported-php-versions.mjs';
7+
8+
// Refresh PHP versions if they need updating (if last refreshed more than 24 hours ago)
9+
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
10+
const lastRefreshedDate = new Date(lastRefreshed);
11+
12+
if (lastRefreshedDate < twentyFourHoursAgo) {
13+
console.log(
14+
'📅 PHP versions data is older than 24 hours, checking for updates...'
15+
);
16+
try {
17+
const { updatePHPVersions } = await import('./update-php-versions.mjs');
18+
await updatePHPVersions();
19+
20+
// Reload the supported-php-versions.mjs module to get the updated versions
21+
const { phpVersions: updatedPhpVersions } = await import(
22+
'../supported-php-versions.mjs?' + Date.now()
23+
);
24+
// Replace the original phpVersions with the updated ones
25+
phpVersions.length = 0;
26+
phpVersions.push(...updatedPhpVersions);
27+
} catch (error) {
28+
console.warn('⚠️ Failed to update PHP versions:', error.message);
29+
process.exit(1);
30+
}
31+
}
732

833
// yargs parse
934
import yargs from 'yargs';
Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Script to automatically update PHP version numbers in supported-php-versions.mjs
5+
*
6+
* This script fetches the latest release information from multiple sources:
7+
* - PHP.watch API for comprehensive version data
8+
* - phpreleases.com API as fallback
9+
* - Direct GitHub API for php/php-src releases
10+
*
11+
* Usage: node tools/update-php-versions.mjs
12+
*/
13+
14+
import fs from 'fs';
15+
import path from 'path';
16+
import { fileURLToPath } from 'url';
17+
18+
const __filename = fileURLToPath(import.meta.url);
19+
const __dirname = path.dirname(__filename);
20+
21+
// Path to the supported PHP versions file
22+
const SUPPORTED_VERSIONS_FILE = path.resolve(
23+
__dirname,
24+
'../supported-php-versions.mjs'
25+
);
26+
27+
/**
28+
* Fetch data from a URL with error handling
29+
*/
30+
async function fetchJSON(url, description = '') {
31+
try {
32+
console.log(`Fetching ${description || url}...`);
33+
const response = await fetch(url);
34+
if (!response.ok) {
35+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
36+
}
37+
return await response.json();
38+
} catch (error) {
39+
console.warn(`Failed to fetch from ${url}: ${error.message}`);
40+
return null;
41+
}
42+
}
43+
44+
/**
45+
* Fetch latest version data from GitHub API
46+
*/
47+
async function fetchFromGitHub() {
48+
let data = [];
49+
try {
50+
console.log('Fetching GitHub API for php/php-src tags (8 pages)...');
51+
52+
// Fetch 8 pages in parallel to get more comprehensive tag data
53+
const pagePromises = [];
54+
for (let page = 1; page <= 8; page++) {
55+
pagePromises.push(
56+
fetch(
57+
`https://api.github.com/repos/php/php-src/tags?per_page=100&page=${page}`
58+
).then((response) => {
59+
if (!response.ok) {
60+
throw new Error(
61+
`HTTP ${response.status}: ${response.statusText}`
62+
);
63+
}
64+
return response.json();
65+
})
66+
);
67+
}
68+
69+
const pageResults = await Promise.allSettled(pagePromises);
70+
71+
// Combine successful results
72+
for (const result of pageResults) {
73+
if (result.status === 'fulfilled' && Array.isArray(result.value)) {
74+
data = data.concat(result.value);
75+
}
76+
}
77+
78+
if (data.length === 0) {
79+
throw new Error('No tag data retrieved from any page');
80+
}
81+
} catch (error) {
82+
console.warn(`Failed to fetch from GitHub API: ${error.message}`);
83+
data = null;
84+
}
85+
if (!Array.isArray(data)) return null;
86+
87+
const versions = {};
88+
89+
// Process tags to extract version numbers
90+
for (const tag of data) {
91+
const tagName = tag.name;
92+
// Match patterns like "php-8.3.15", "php-8.2.26", etc.
93+
const match = tagName.match(/^php-(\d+)\.(\d+)\.(\d+)$/);
94+
if (match) {
95+
const [, major, minor, patch] = match;
96+
const version = `${major}.${minor}`;
97+
const fullVersion = `${major}.${minor}.${patch}`;
98+
99+
// Keep the latest (highest) version for each major.minor
100+
if (
101+
!versions[version] ||
102+
compareVersions(fullVersion, versions[version]) > 0
103+
) {
104+
versions[version] = fullVersion;
105+
}
106+
}
107+
}
108+
109+
return versions;
110+
}
111+
112+
/**
113+
* Compare two semantic version strings
114+
* Returns: 1 if a > b, -1 if a < b, 0 if equal
115+
*/
116+
function compareVersions(a, b) {
117+
const aParts = a.split('.').map(Number);
118+
const bParts = b.split('.').map(Number);
119+
120+
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
121+
const aVal = aParts[i] || 0;
122+
const bVal = bParts[i] || 0;
123+
124+
if (aVal > bVal) return 1;
125+
if (aVal < bVal) return -1;
126+
}
127+
128+
return 0;
129+
}
130+
131+
/**
132+
* Merge version data from multiple sources, preferring more recent/reliable sources
133+
*/
134+
function mergeVersionData(...sources) {
135+
const merged = {};
136+
137+
// Merge all sources, later sources take precedence
138+
for (const source of sources) {
139+
if (source) {
140+
Object.assign(merged, source);
141+
}
142+
}
143+
144+
return merged;
145+
}
146+
147+
/**
148+
* Read the current supported-php-versions.mjs file
149+
*/
150+
function readCurrentVersions() {
151+
try {
152+
const content = fs.readFileSync(SUPPORTED_VERSIONS_FILE, 'utf8');
153+
154+
// Extract the phpVersions array using regex
155+
const arrayMatch = content.match(
156+
/export const phpVersions = (\[[\s\S]*?\]);/
157+
);
158+
if (!arrayMatch) {
159+
throw new Error('Could not find phpVersions array in file');
160+
}
161+
162+
// Parse the array (this is a bit hacky but works for our specific format)
163+
const arrayString = arrayMatch[1];
164+
165+
// Extract version objects using regex
166+
const versionMatches = arrayString.matchAll(
167+
/\{[\s\S]*?version:\s*['"`]([^'"`]+)['"`][\s\S]*?lastRelease:\s*['"`]([^'"`]+)['"`][\s\S]*?\}/g
168+
);
169+
170+
const versions = [];
171+
for (const match of versionMatches) {
172+
const [fullMatch, version, lastRelease] = match;
173+
174+
// Extract other properties
175+
const loaderMatch = fullMatch.match(
176+
/loaderFilename:\s*['"`]([^'"`]+)['"`]/
177+
);
178+
const wasmMatch = fullMatch.match(
179+
/wasmFilename:\s*['"`]([^'"`]+)['"`]/
180+
);
181+
182+
versions.push({
183+
version,
184+
loaderFilename: loaderMatch
185+
? loaderMatch[1]
186+
: `php_${version.replace('.', '_')}.js`,
187+
wasmFilename: wasmMatch
188+
? wasmMatch[1]
189+
: `php_${version.replace('.', '_')}.wasm`,
190+
lastRelease,
191+
});
192+
}
193+
194+
return versions;
195+
} catch (error) {
196+
console.error(`Error reading current versions: ${error.message}`);
197+
return [];
198+
}
199+
}
200+
201+
/**
202+
* Update the supported-php-versions.mjs file with new version data
203+
*/
204+
function updateVersionsFile(currentVersions, latestVersions) {
205+
let updatedCount = 0;
206+
207+
// Update last release versions
208+
const updatedVersions = currentVersions.map((versionObj) => {
209+
const version = versionObj.version;
210+
const newVersion = latestVersions[version];
211+
212+
if (newVersion && newVersion !== versionObj.lastRelease) {
213+
console.log(
214+
`Updating ${version}: ${versionObj.lastRelease}${newVersion}`
215+
);
216+
updatedCount++;
217+
return {
218+
...versionObj,
219+
lastRelease: newVersion,
220+
};
221+
}
222+
223+
return versionObj;
224+
});
225+
226+
// Generate the new file content
227+
const fileContent = generateFileContent(updatedVersions);
228+
229+
// Write the updated file
230+
fs.writeFileSync(SUPPORTED_VERSIONS_FILE, fileContent, 'utf8');
231+
232+
console.log(
233+
`\nUpdated ${updatedCount} PHP versions in ${SUPPORTED_VERSIONS_FILE}`
234+
);
235+
return updatedCount;
236+
}
237+
238+
/**
239+
* Generate the complete file content for supported-php-versions.mjs
240+
*/
241+
function generateFileContent(versions) {
242+
const header = `/**
243+
* @typedef {Object} PhpVersion
244+
* @property {string} version
245+
* @property {string} loaderFilename
246+
* @property {string} wasmFilename
247+
* @property {string} lastRelease
248+
*/
249+
250+
export const lastRefreshed = ${JSON.stringify(new Date().toISOString())};
251+
252+
/**
253+
* @type {PhpVersion[]}
254+
* @see https://www.php.net/releases/index.php
255+
*/
256+
export const phpVersions = [`;
257+
258+
const footer = `];
259+
`;
260+
261+
const versionEntries = versions
262+
.map((version) => {
263+
return `\t{
264+
\t\tversion: '${version.version}',
265+
\t\tloaderFilename: '${version.loaderFilename}',
266+
\t\twasmFilename: '${version.wasmFilename}',
267+
\t\tlastRelease: '${version.lastRelease}',
268+
\t}`;
269+
})
270+
.join(',\n');
271+
272+
return header + '\n' + versionEntries + '\n' + footer;
273+
}
274+
275+
/**
276+
* Main function
277+
*/
278+
export async function updatePHPVersions() {
279+
console.log('🔄 Updating PHP versions...\n');
280+
281+
// Fetch version data from multiple sources
282+
console.log('📡 Fetching version data from APIs...');
283+
const latestVersions = await fetchFromGitHub();
284+
285+
if (Object.keys(latestVersions).length === 0) {
286+
console.error('❌ Failed to fetch version data from any source');
287+
process.exit(1);
288+
}
289+
290+
console.log('\n📋 Latest versions found:');
291+
for (const [version, release] of Object.entries(latestVersions)) {
292+
console.log(` ${version}: ${release}`);
293+
}
294+
295+
// Read current versions
296+
console.log('\n📖 Reading current supported-php-versions.mjs...');
297+
const currentVersions = readCurrentVersions();
298+
299+
if (currentVersions.length === 0) {
300+
console.error('❌ Failed to read current versions');
301+
process.exit(1);
302+
}
303+
304+
// Update the file
305+
console.log('\n✏️ Updating versions...');
306+
const updatedCount = updateVersionsFile(currentVersions, latestVersions);
307+
308+
if (updatedCount > 0) {
309+
console.log('\n✅ Successfully updated PHP versions!');
310+
} else {
311+
console.log('\n✨ All PHP versions are already up to date!');
312+
}
313+
}

packages/php-wasm/supported-php-versions.mjs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
* @property {string} lastRelease
77
*/
88

9+
export const lastRefreshed = '2025-07-15T12:04:38.826Z';
10+
911
/**
1012
* @type {PhpVersion[]}
1113
* @see https://www.php.net/releases/index.php
@@ -15,25 +17,25 @@ export const phpVersions = [
1517
version: '8.4',
1618
loaderFilename: 'php_8_4.js',
1719
wasmFilename: 'php_8_4.wasm',
18-
lastRelease: '8.4.0',
20+
lastRelease: '8.4.10',
1921
},
2022
{
2123
version: '8.3',
2224
loaderFilename: 'php_8_3.js',
2325
wasmFilename: 'php_8_3.wasm',
24-
lastRelease: '8.3.0',
26+
lastRelease: '8.3.23',
2527
},
2628
{
2729
version: '8.2',
2830
loaderFilename: 'php_8_2.js',
2931
wasmFilename: 'php_8_2.wasm',
30-
lastRelease: '8.2.10',
32+
lastRelease: '8.2.29',
3133
},
3234
{
3335
version: '8.1',
3436
loaderFilename: 'php_8_1.js',
3537
wasmFilename: 'php_8_1.wasm',
36-
lastRelease: '8.1.23',
38+
lastRelease: '8.1.33',
3739
},
3840
{
3941
version: '8.0',

0 commit comments

Comments
 (0)