Skip to content

Commit 1419482

Browse files
committed
Move WASM builds to NPM packages
1 parent 6857cb5 commit 1419482

11 files changed

Lines changed: 493 additions & 2 deletions

File tree

.github/actions/prepare-playground/action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ runs:
3939
- name: Set NX_HEAD
4040
shell: bash
4141
run: echo "NX_HEAD=$(git rev-parse HEAD)" >> $GITHUB_ENV
42+
- name: Download PHP WASM binaries
43+
shell: bash
44+
run: npm run prepare-wasm
4245
- name: Reset NX cache
4346
shell: bash
4447
run: npx nx reset
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
name: Compile PHP WASM
2+
3+
on:
4+
push:
5+
paths:
6+
- 'packages/php-wasm/.recompile-request.json'
7+
8+
permissions:
9+
id-token: write
10+
contents: write
11+
pull-requests: write
12+
13+
jobs:
14+
read-matrix:
15+
# Only allow Playground maintainers to trigger npm publishes.
16+
if: >
17+
(
18+
github.actor == 'adamziel' ||
19+
github.actor == 'dmsnell' ||
20+
github.actor == 'bgrgicak' ||
21+
github.actor == 'brandonpayton' ||
22+
github.actor == 'zaerl' ||
23+
github.actor == 'janjakes' ||
24+
github.actor == 'mho22' ||
25+
github.actor == 'ashfame'
26+
)
27+
runs-on: ubuntu-latest
28+
outputs:
29+
matrix: ${{ steps.set-matrix.outputs.matrix }}
30+
steps:
31+
- uses: actions/checkout@v4
32+
33+
- name: Build job matrix from .recompile-request.json
34+
id: set-matrix
35+
run: |
36+
MATRIX=$(node -e "
37+
const data = JSON.parse(require('fs').readFileSync('packages/php-wasm/.recompile-request.json', 'utf8'));
38+
const items = data.compilations.map(c => ({
39+
platform: c.platform,
40+
phpVersion: c.phpVersion,
41+
packageSuffix: c.platform + '-' + c.phpVersion.replace('.', '-')
42+
}));
43+
console.log(JSON.stringify({ include: items }));
44+
")
45+
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
46+
47+
compile:
48+
needs: read-matrix
49+
runs-on: ubuntu-latest
50+
strategy:
51+
fail-fast: false
52+
matrix: ${{ fromJSON(needs.read-matrix.outputs.matrix) }}
53+
steps:
54+
- uses: actions/checkout@v4
55+
with:
56+
submodules: true
57+
58+
- uses: actions/setup-node@v4
59+
with:
60+
node-version: '20'
61+
62+
- name: Install dependencies
63+
run: npm ci
64+
65+
- name: Free up runner disk space
66+
run: |
67+
set -euo pipefail
68+
sudo rm -rf /usr/local/lib/android
69+
sudo rm -rf /usr/share/dotnet
70+
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
71+
sudo rm -rf /opt/ghc
72+
sudo rm -rf /opt/hostedtoolcache/CodeQL
73+
74+
- name: Compile JSPI variant
75+
run: >
76+
node packages/php-wasm/compile/build.js
77+
--PLATFORM=${{ matrix.platform }}
78+
--PHP_VERSION=${{ matrix.phpVersion }}
79+
--JSPI=true
80+
81+
- name: Compile Asyncify variant
82+
run: >
83+
node packages/php-wasm/compile/build.js
84+
--PLATFORM=${{ matrix.platform }}
85+
--PHP_VERSION=${{ matrix.phpVersion }}
86+
--JSPI=false
87+
88+
- name: Upload compiled artifact
89+
uses: actions/upload-artifact@v4
90+
with:
91+
name: php-wasm-${{ matrix.packageSuffix }}
92+
path: packages/php-wasm/${{ matrix.platform }}-builds/${{ matrix.phpVersion.replace('.', '-') }}/
93+
retention-days: 3
94+
95+
publish:
96+
needs: [read-matrix, compile]
97+
runs-on: ubuntu-latest
98+
environment:
99+
name: npm
100+
strategy:
101+
fail-fast: false
102+
matrix: ${{ fromJSON(needs.read-matrix.outputs.matrix) }}
103+
steps:
104+
- uses: actions/checkout@v4
105+
with:
106+
submodules: true
107+
108+
- name: Set up Node.js with OIDC
109+
uses: actions/setup-node@v4
110+
with:
111+
node-version: '20'
112+
registry-url: 'https://registry.npmjs.org'
113+
114+
- name: Upgrade npm for trusted publishing
115+
run: npm install -g npm@latest
116+
117+
- name: Install dependencies
118+
run: npm ci
119+
120+
- name: Download compiled artifact
121+
uses: actions/download-artifact@v4
122+
with:
123+
name: php-wasm-${{ matrix.packageSuffix }}
124+
path: packages/php-wasm/${{ matrix.platform }}-builds/${{ matrix.phpVersion.replace('.', '-') }}/
125+
126+
- name: Build package
127+
run: >
128+
npx nx build
129+
php-wasm-${{ matrix.packageSuffix }}
130+
131+
- name: Determine PR version
132+
id: version
133+
run: |
134+
CURRENT=$(node -e "console.log(require('./lerna.json').version)")
135+
PR_NUMBER=${{ github.event.pull_request.number || '' }}
136+
RUN_NUMBER=${{ github.run_number }}
137+
if [ -n "$PR_NUMBER" ]; then
138+
VERSION="${CURRENT}-pr.${PR_NUMBER}.${RUN_NUMBER}"
139+
else
140+
VERSION="${CURRENT}-sha.$(git rev-parse --short HEAD).${RUN_NUMBER}"
141+
fi
142+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
143+
144+
- name: Inject PR version into dist package.json
145+
run: |
146+
node -e "
147+
const pkgPath = 'dist/packages/php-wasm/${{ matrix.platform }}-builds/${{ matrix.phpVersion.replace('.', '-') }}/package.json';
148+
const pkg = JSON.parse(require('fs').readFileSync(pkgPath, 'utf8'));
149+
pkg.version = '${{ steps.version.outputs.version }}';
150+
require('fs').writeFileSync(pkgPath, JSON.stringify(pkg, null, '\t') + '\n');
151+
"
152+
153+
- name: Publish to npm
154+
run: >
155+
npm publish
156+
--tag pr-${{ github.event.pull_request.number || github.run_number }}
157+
--access public
158+
working-directory: dist/packages/php-wasm/${{ matrix.platform }}-builds/${{ matrix.phpVersion.replace('.', '-') }}
159+
env:
160+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
161+
162+
- name: Output published version
163+
run: echo "Published @php-wasm/${{ matrix.packageSuffix }}@${{ steps.version.outputs.version }}"
164+
165+
update-versions:
166+
needs: [read-matrix, publish]
167+
runs-on: ubuntu-latest
168+
steps:
169+
- uses: actions/checkout@v4
170+
with:
171+
ref: ${{ github.head_ref || github.ref_name }}
172+
token: ${{ secrets.GH_TOKEN }}
173+
174+
- name: Configure git
175+
run: |
176+
git config user.name "deployment_bot"
177+
git config user.email "deployment_bot@users.noreply.github.com"
178+
179+
- uses: actions/setup-node@v4
180+
with:
181+
node-version: '20'
182+
183+
- name: Update wasm-versions.json
184+
run: |
185+
node -e "
186+
const fs = require('fs');
187+
const versionsPath = 'packages/php-wasm/wasm-versions.json';
188+
const versions = JSON.parse(fs.readFileSync(versionsPath, 'utf8'));
189+
const matrix = ${{ needs.read-matrix.outputs.matrix }};
190+
const currentVersion = require('./lerna.json').version;
191+
const pr = '${{ github.event.pull_request.number || '' }}';
192+
const run = '${{ github.run_number }}';
193+
for (const item of matrix.include) {
194+
const key = item.packageSuffix;
195+
const version = pr
196+
? \`\${currentVersion}-pr.\${pr}.\${run}\`
197+
: \`\${currentVersion}-sha.\${require('child_process').execSync('git rev-parse --short HEAD').toString().trim()}.\${run}\`;
198+
versions[key] = version;
199+
}
200+
fs.writeFileSync(versionsPath, JSON.stringify(versions, null, '\t') + '\n');
201+
"
202+
203+
- name: Commit updated wasm-versions.json
204+
run: |
205+
git add packages/php-wasm/wasm-versions.json
206+
git diff --staged --quiet || git commit -m "chore: update wasm-versions.json for PHP WASM recompile [skip ci]"
207+
git push
208+
209+
- name: Post PR comment
210+
if: github.event.pull_request.number != ''
211+
uses: actions/github-script@v7
212+
with:
213+
script: |
214+
const matrix = ${{ needs.read-matrix.outputs.matrix }};
215+
const currentVersion = require('./lerna.json').version;
216+
const pr = context.payload.pull_request?.number;
217+
const run = context.runNumber;
218+
const lines = matrix.include.map(item => {
219+
const version = `${currentVersion}-pr.${pr}.${run}`;
220+
return `- \`@php-wasm/${item.packageSuffix}@${version}\``;
221+
});
222+
const body = [
223+
'### PHP WASM packages published',
224+
'',
225+
'The following pre-release packages are now available on npm:',
226+
'',
227+
...lines,
228+
'',
229+
'These packages will be available until the PR is merged.',
230+
'The `wasm-versions.json` has been updated on this branch.',
231+
].join('\n');
232+
await github.rest.issues.createComment({
233+
owner: context.repo.owner,
234+
repo: context.repo.repo,
235+
issue_number: pr,
236+
body,
237+
});

.github/workflows/publish-npm-packages.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,20 @@ jobs:
9494
--yes --no-private --loglevel=verbose
9595
--dist-tag=${{ inputs.dist_tag || 'latest' }}
9696
${{ github.ref != 'refs/heads/trunk' && '--no-push --no-git-tag-version' || '' }}
97+
98+
- name: Update wasm-versions.json to new stable version
99+
if: github.ref == 'refs/heads/trunk'
100+
run: |
101+
node -e "
102+
const fs = require('fs');
103+
const versionsPath = 'packages/php-wasm/wasm-versions.json';
104+
const newVersion = require('./lerna.json').version;
105+
const versions = JSON.parse(fs.readFileSync(versionsPath, 'utf8'));
106+
for (const key of Object.keys(versions)) {
107+
versions[key] = newVersion;
108+
}
109+
fs.writeFileSync(versionsPath, JSON.stringify(versions, null, '\t') + '\n');
110+
"
111+
git add packages/php-wasm/wasm-versions.json
112+
git diff --staged --quiet || git commit -m "chore: update wasm-versions.json to $(node -e \"console.log(require('./lerna.json').version)\")"
113+
git push origin trunk

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,10 @@ packages/php-wasm/**/php.wasm.map
8282
# File generated by emscripten 4.0.19 during PHP.wasm compilation since
8383
default.profraw
8484

85+
# PHP WASM compiled outputs (managed via npm, see packages/php-wasm/wasm-versions.json)
86+
packages/php-wasm/*-builds/**/*.wasm
87+
packages/php-wasm/*-builds/**/*.so
88+
packages/php-wasm/*-builds/**/php_*.js
89+
8590
# Indexing by Serena for MCP
8691
.serena

.husky/post-checkout

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env sh
2+
3+
# $3 is 1 for a branch checkout, 0 for a file checkout — skip file checkouts.
4+
if [ "$3" != "1" ]; then
5+
exit 0
6+
fi
7+
8+
# Only act when a recompile has been requested on this branch.
9+
if [ ! -f "packages/php-wasm/.recompile-request.json" ]; then
10+
exit 0
11+
fi
12+
13+
npm run prepare-wasm

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"prepare": "husky install",
2626
"reset": "nx reset",
2727
"local-package-repository": "./tools/scripts/local-package-repository.sh",
28+
"prepare-wasm": "node tools/scripts/download-wasm.mjs",
2829
"recompile:php": "npm run recompile:php:web && npm run recompile:php:node",
2930
"recompile:php:web": "nx recompile-php:all php-wasm-web ",
3031
"recompile:php:web:jspi:all": "nx recompile-php:jspi:all php-wasm-web",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"requestedAt": "",
3+
"compilations": []
4+
}

packages/php-wasm/compile/build.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,10 +386,50 @@ await asyncSpawn(
386386
{ cwd: sourceDir, stdio: 'inherit' }
387387
);
388388

389+
// Write .recompile-request.json so CI knows what to compile
390+
const repoRoot = path.resolve(sourceDir, '../../..');
391+
const triggerFilePath = path.join(
392+
repoRoot,
393+
'packages/php-wasm/.recompile-request.json'
394+
);
395+
const resolvedPHPVersion = fullyQualifiedPHPVersion(args.PHP_VERSION || '8.3');
396+
const [phpMajor, phpMinor] = resolvedPHPVersion.split('.');
397+
const phpVersionShort = `${phpMajor}.${phpMinor}`;
398+
399+
let triggerData = { requestedAt: '', compilations: [] };
400+
401+
if (fs.existsSync(triggerFilePath)) {
402+
try {
403+
triggerData = JSON.parse(fs.readFileSync(triggerFilePath, 'utf8'));
404+
} catch {
405+
// Ignore parse errors — start fresh
406+
}
407+
}
408+
409+
const entry = { platform: args.PLATFORM || 'web', phpVersion: phpVersionShort };
410+
const alreadyPresent = triggerData.compilations.some(
411+
(c) => c.platform === entry.platform && c.phpVersion === entry.phpVersion
412+
);
413+
414+
if (!alreadyPresent) {
415+
triggerData.compilations.push(entry);
416+
}
417+
418+
triggerData.requestedAt = new Date().toISOString();
419+
fs.writeFileSync(
420+
triggerFilePath,
421+
JSON.stringify(triggerData, null, '\t') + '\n'
422+
);
423+
console.log(
424+
`Updated packages/php-wasm/.recompile-request.json for ${entry.platform} PHP ${phpVersionShort}`
425+
);
426+
389427
function asyncSpawn(...args) {
390428
console.log('Running', args[0], args[1].join(' '), '...');
429+
391430
return new Promise((resolve, reject) => {
392431
const child = spawn(...args);
432+
393433
child.on('close', (code) => {
394434
if (code === 0) resolve(code);
395435
else reject(new Error(`Process exited with code ${code}`));
@@ -403,5 +443,6 @@ function fullyQualifiedPHPVersion(requestedVersion) {
403443
return lastRelease;
404444
}
405445
}
446+
406447
return requestedVersion;
407448
}

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

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

9-
export const lastRefreshed = "2026-03-02T17:19:10.584Z";
9+
export const lastRefreshed = '2026-03-04T16:20:47.565Z';
1010

1111
/**
1212
* @type {PhpVersion[]}
@@ -54,5 +54,5 @@ export const phpVersions = [
5454
loaderFilename: 'php_7_4.js',
5555
wasmFilename: 'php_7_4.wasm',
5656
lastRelease: '7.4.33',
57-
}
57+
},
5858
];
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"web-7-4": "3.1.4",
3+
"web-8-0": "3.1.4",
4+
"web-8-1": "3.1.4",
5+
"web-8-2": "3.1.4",
6+
"web-8-3": "3.1.4",
7+
"web-8-4": "3.1.4",
8+
"web-8-5": "3.1.4",
9+
"node-7-4": "3.1.4",
10+
"node-8-0": "3.1.4",
11+
"node-8-1": "3.1.4",
12+
"node-8-2": "3.1.4",
13+
"node-8-3": "3.1.4",
14+
"node-8-4": "3.1.4",
15+
"node-8-5": "3.1.4"
16+
}

0 commit comments

Comments
 (0)