@php-wasm/compile-extension 3.1.25 → 3.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -5
- package/cli.js +2 -2
- package/cli.js.map +1 -1
- package/compile.d.ts +4 -1
- package/extra-files.d.ts +26 -0
- package/manifest.d.ts +28 -4
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -12,12 +12,67 @@ npx @php-wasm/compile-extension \
|
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
The command writes one JSPI `.so` per PHP version and a `manifest.json` that
|
|
15
|
-
can be consumed by PHP.wasm extension-loading helpers.
|
|
15
|
+
can be consumed by PHP.wasm extension-loading helpers. The manifest matches
|
|
16
|
+
the `PHPExtensionManifest` shape from `@php-wasm/universal`:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"name": "wp_mysql_parser",
|
|
21
|
+
"version": "0.1.0",
|
|
22
|
+
"artifacts": [
|
|
23
|
+
{
|
|
24
|
+
"phpVersion": "8.4",
|
|
25
|
+
"sourcePath": "wp_mysql_parser-php8.4-jspi.so"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
To stage sidecar files (data directories, web UI assets, ICU data, etc.) under
|
|
32
|
+
an absolute VFS prefix, pass `--extra-files <hostDir>:<vfsRoot>`. The host
|
|
33
|
+
directory is copied next to the manifest and recorded under `extraFiles.nodes`:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx @php-wasm/compile-extension \
|
|
37
|
+
--source ./spx-src \
|
|
38
|
+
--name spx \
|
|
39
|
+
--php-versions 8.2 \
|
|
40
|
+
--extra-files ./web-ui:/internal/shared/spx \
|
|
41
|
+
--out ./dist
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Empty directories are recorded as `type: "directory"` nodes so the loader
|
|
45
|
+
creates them before PHP starts.
|
|
46
|
+
|
|
47
|
+
The supported `--php-versions` are `7.4` and `8.0` through `8.5`.
|
|
16
48
|
|
|
17
49
|
Docker is required. The build reuses the `packages/php-wasm/compile` base image
|
|
18
50
|
and its PHP patch set, then runs `phpize`, `emconfigure`, and `emmake` inside
|
|
19
51
|
the container.
|
|
20
52
|
|
|
53
|
+
## Running in CI
|
|
54
|
+
|
|
55
|
+
The package only needs Docker and Node. A typical GitHub Actions job:
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
- uses: actions/checkout@v4
|
|
59
|
+
|
|
60
|
+
- uses: actions/setup-node@v4
|
|
61
|
+
with:
|
|
62
|
+
node-version: '24'
|
|
63
|
+
|
|
64
|
+
- run: |
|
|
65
|
+
npx --yes @php-wasm/compile-extension \
|
|
66
|
+
--source ./my-extension \
|
|
67
|
+
--name my_extension \
|
|
68
|
+
--php-versions 8.0,8.1,8.2,8.3,8.4,8.5 \
|
|
69
|
+
--out ./dist/my-extension
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
In a matrix workflow, set `strategy.max-parallel: 1` on the WASM job —
|
|
73
|
+
parallel Docker builds on hosted runners often hit apt-mirror flakes during
|
|
74
|
+
the base image build.
|
|
75
|
+
|
|
21
76
|
## Loading the result
|
|
22
77
|
|
|
23
78
|
Host the entire output directory somewhere static and pass the manifest URL to
|
|
@@ -42,9 +97,9 @@ const php = new PHP(
|
|
|
42
97
|
```
|
|
43
98
|
|
|
44
99
|
The loader chooses the artifact whose `phpVersion` matches the running
|
|
45
|
-
PHP.wasm runtime, downloads it,
|
|
46
|
-
|
|
47
|
-
before PHP starts.
|
|
100
|
+
PHP.wasm runtime, downloads it, stages the `.so`, writes a startup `.ini`
|
|
101
|
+
file, copies any `extraFiles` declared in the manifest, and registers the
|
|
102
|
+
extension scan directory before PHP starts.
|
|
48
103
|
|
|
49
104
|
In Node.js, `manifestUrl` may also be a local path:
|
|
50
105
|
|
|
@@ -75,7 +130,6 @@ const php = new PHP(
|
|
|
75
130
|
source: {
|
|
76
131
|
format: 'url',
|
|
77
132
|
url: 'https://example.com/extensions/wp_mysql_parser-php8.4-jspi.so',
|
|
78
|
-
sha256: '...',
|
|
79
133
|
},
|
|
80
134
|
},
|
|
81
135
|
],
|
|
@@ -218,6 +272,12 @@ RUSTFLAGS="-C panic=abort" cargo +nightly build \
|
|
|
218
272
|
Keep dependencies aligned with the custom extension target. Custom extensions
|
|
219
273
|
are JSPI-only, so link `jspi` dependency archives.
|
|
220
274
|
|
|
275
|
+
`ext-php-rs` `0.15` depends on PHP 8 Zend APIs and does not compile against
|
|
276
|
+
PHP `7.4` headers, so Rust extensions built on top of `ext-php-rs` `0.15`
|
|
277
|
+
should restrict `--php-versions` to `8.0` through `8.5`. The helper itself
|
|
278
|
+
still supports PHP `7.4` for non-Rust extensions and for Rust extensions that
|
|
279
|
+
bind Zend directly through `bindgen`.
|
|
280
|
+
|
|
221
281
|
`--extra-cflags` is visible during `./configure`. `--extra-ldflags` is applied
|
|
222
282
|
to the final side-module link so dependency archives do not break Autoconf's
|
|
223
283
|
compiler smoke tests. If an extension's `config.m4` insists on link-probing a
|
package/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{
|
|
3
|
-
`),t}const
|
|
2
|
+
import{realpathSync as w,existsSync as v}from"node:fs";import i from"node:path";import{fileURLToPath as x}from"node:url";import A from"yargs";import{hideBin as D}from"yargs/helpers";import{mkdir as g,writeFile as N,readFile as E,cp as F,readdir as P,stat as b}from"node:fs/promises";import y from"node:os";import{spawn as O}from"node:child_process";function _(e){const t=i.join(e,"packages/php-wasm");return{workspaceRoot:e,phpWasmRoot:t,compileRoot:i.join(t,"compile")}}async function I(){try{await p("docker",["--version"],{stdio:"ignore"})}catch(e){throw new Error("Docker is required to compile PHP.wasm extensions, but the docker command was not found.",{cause:e})}}async function k(e){await p("make",["base-image"],{cwd:e.compileRoot})}async function j(e){const t=$(e);return await p("docker",["build","-f","compile-extension/docker/Dockerfile.ext",".",`--tag=${t}`,"--progress=plain","--build-arg",`PHP_VERSION=${e.phpRelease}`,"--build-arg","JSPI=yes"],{cwd:e.phpWasmRoot}),t}async function C(e){const t=$(e),r=["run","--rm","-v",`${e.sourceDir}:/src:ro`,"-v",`${e.outDir}:/out`,"-v",`${e.compileRoot}:/php-wasm-compile:ro`,"--env",`EXTENSION_NAME=${e.name}`,"--env",`PHP_VERSION_SHORT=${e.phpVersion}`,"--env",`ASYNC_MODE=${e.asyncMode}`,"--env",`ARTIFACT_FILENAME=${e.artifactFile}`,"--env",`OPTIMIZE=${e.optimize}`,"--env",`CONFIG_ARGS_COUNT=${e.configArgs.length}`];e.extraCflags&&r.push("--env",`EXTRA_CFLAGS=${e.extraCflags}`),e.extraLdflags&&r.push("--env",`EXTRA_LDFLAGS=${e.extraLdflags}`),e.configArgs.forEach((n,a)=>{r.push("--env",`CONFIG_ARG_${a}=${n}`)}),r.push(t),await p("docker",r,{cwd:e.workspaceRoot})}function $(e){return`playground-php-wasm:compile-extension-php${e.phpVersion.replaceAll(".","-")}-${e.asyncMode}`}async function p(e,t,r={}){console.log(`Running ${e} ${t.join(" ")}`),await new Promise((n,a)=>{const s=O(e,t,{cwd:r.cwd,stdio:r.stdio??"inherit",env:process.env});s.on("error",a),s.on("close",c=>{if(c===0){n();return}a(new Error(`${e} exited with code ${c}`))})})}const T="jspi";async function L(e){const t={name:e.name,version:e.version,artifacts:e.artifacts.map(r=>({phpVersion:r.phpVersion,sourcePath:r.sourcePath}))};return e.extraFiles&&(t.extraFiles=e.extraFiles),t}async function M(e){await g(e.outDir,{recursive:!0});const t=i.join(e.outDir,"manifest.json");return await N(t,`${JSON.stringify(e.manifest,null,2)}
|
|
3
|
+
`),t}const z=["8.5","8.4","8.3","8.2","8.1","8.0","7.4"],H={"8.5":"8.5.5","8.4":"8.4.20","8.3":"8.3.30","8.2":"8.2.30","8.1":"8.1.34","8.0":"8.0.30","7.4":"7.4.33"};async function V(e){const t=i.resolve(e.workspaceRoot,e.outDir),r=i.resolve(e.workspaceRoot,e.sourceDir),n=_(e.workspaceRoot),a=await G(r),s=e.phpVersions.map(l=>({phpVersion:l,asyncMode:T}));await g(t,{recursive:!0}),await I(),await k(n);const c=await J(s,B(e.jobs,s.length),async({phpVersion:l,asyncMode:o})=>{const h=W(l),m=`${e.name}-php${l}-${o}.so`;return await j({...n,phpVersion:l,phpRelease:h,asyncMode:o}),await C({...n,sourceDir:r,outDir:t,name:e.name,phpVersion:l,asyncMode:o,artifactFile:m,optimize:e.optimize,extraCflags:e.extraCflags,extraLdflags:e.extraLdflags,configArgs:e.configArgs}),{phpVersion:l,sourcePath:m,path:i.join(t,m)}}),f=await L({name:e.name,version:a,artifacts:c,extraFiles:e.extraFiles});return{manifestPath:await M({outDir:t,manifest:f}),artifacts:c,manifest:f}}function W(e){return H[e]??e}async function G(e){try{const t=JSON.parse(await E(i.join(e,"package.json"),"utf8"));if(typeof t.version=="string")return t.version}catch{}return"0.0.0"}async function J(e,t,r){const n=new Array(e.length);let a=0;const s=Array.from({length:Math.min(t,e.length)},async()=>{for(;a<e.length;){const c=a++;n[c]=await r(e[c])}});return await Promise.all(s),n}function B(e,t){return t===0?1:e&&e>0?Math.min(e,t):Math.min(y.availableParallelism?.()??y.cpus().length,t)}const X=[/\bPHP_ARG_ENABLE\(\s*\[?([A-Za-z0-9_]+)\]?/m,/\bPHP_ARG_WITH\(\s*\[?([A-Za-z0-9_]+)\]?/m,/\bPHP_NEW_EXTENSION\(\s*\[?([A-Za-z0-9_]+)\]?/m];async function U(e){const t=i.join(e,"config.m4");let r;try{r=await E(t,"utf8")}catch(a){throw new Error(`Could not read ${t}. Pass --name or provide a config.m4 file.`,{cause:a})}const n=Z(r);if(!n)throw new Error(`Could not detect the extension name from ${t}. Pass --name explicitly.`);return n}function Z(e){for(const t of X){const r=t.exec(e);if(r?.[1])return r[1]}return null}function q(e){const t=e.lastIndexOf(":");if(t<0)throw new Error(`Invalid --extra-files value ${JSON.stringify(e)}. Expected "<hostDir>:<vfsRoot>".`);const r=e.slice(0,t),n=e.slice(t+1);if(!r||!n)throw new Error(`Invalid --extra-files value ${JSON.stringify(e)}. Expected "<hostDir>:<vfsRoot>".`);if(!n.startsWith("/"))throw new Error(`--extra-files vfsRoot must be an absolute VFS path. Received ${JSON.stringify(n)}.`);return{hostDir:r,vfsRoot:n}}async function Y(e,t,r){if(!e.length)return;const n=e[0].vfsRoot;for(const f of e)if(f.vfsRoot!==n)throw new Error("All --extra-files entries must share the same vfsRoot. Received "+JSON.stringify(e.map(u=>u.vfsRoot)));const a=[],s=new Set,c=new Set;for(const f of e){const u=i.resolve(r,f.hostDir),l=i.basename(u),o=i.join(t,l);if(c.has(o))throw new Error(`--extra-files destination collides on disk: ${o}. Two host directories share the same basename, so one would overwrite the other.`);c.add(o),await g(i.dirname(o),{recursive:!0}),await F(u,o,{recursive:!0}),await R(o,async(h,m)=>{const d=i.relative(o,h).split(i.sep).join("/");if(!d)return;const S=`${l}/${d}`;if(m){if(!((await P(h)).length===0))return;if(s.has(d))throw new Error(`--extra-files vfsPath collides across specs: ${d}.`);s.add(d),a.push({vfsPath:d,type:"directory"});return}if(s.has(d))throw new Error(`--extra-files vfsPath collides across specs: ${d}.`);s.add(d),a.push({vfsPath:d,sourcePath:S})})}return a.sort((f,u)=>f.vfsPath<u.vfsPath?-1:f.vfsPath>u.vfsPath?1:0),{vfsRoot:n,nodes:a}}async function R(e,t){for(const r of await P(e)){const n=i.join(e,r),a=await b(n);await t(n,a.isDirectory()),a.isDirectory()&&await R(n,t)}}const K=new Set(["--config-args","--extra-cflags","--extra-ldflags"]);async function Q(e=D(process.argv)){const t=await A(ee(e)).scriptName("@php-wasm/compile-extension").usage("Usage: $0 --source <dir> [options]").options({source:{type:"string",demandOption:!0,description:"Extension source directory containing config.m4"},name:{type:"string",description:"Extension name. Defaults to parsing config.m4."},"php-versions":{type:"string",default:z.join(","),description:"Comma-separated PHP major.minor versions."},out:{type:"string",default:"./dist",description:"Output directory."},"extra-cflags":{type:"string",description:"Extra CFLAGS appended to the side-module build."},"extra-ldflags":{type:"string",description:"Extra LDFLAGS appended to the side-module build."},"config-args":{type:"string",default:"",description:"Extra ./configure arguments, parsed as shell words."},optimize:{type:"string",default:"2",description:"Optimization level passed as -O<level>."},jobs:{type:"number",description:"Maximum concurrent docker builds."},"extra-files":{type:"string",array:!0,default:[],description:"Stage a host directory under an absolute VFS root. Format: <hostDir>:<vfsRoot>. Files are copied next to the manifest and recorded in extraFiles."}}).strict().help().parse(),r=ne(process.cwd()),n=i.resolve(r,t.source),a=t.name??await U(n),s=te(t["php-versions"],"php-versions"),c=re(t["config-args"]||""),f=t["extra-files"].map(q),u=i.resolve(r,t.out),l=await Y(f,u,r),o=await V({workspaceRoot:r,sourceDir:n,outDir:t.out,name:a,phpVersions:s,extraCflags:t["extra-cflags"],extraLdflags:t["extra-ldflags"],configArgs:c,optimize:t.optimize,jobs:t.jobs,extraFiles:l});console.log(`Wrote ${o.artifacts.length} artifacts.`),console.log(`Wrote ${o.manifestPath}.`)}ae(import.meta.url)&&Q().catch(e=>{console.error(e instanceof Error?e.message:e),process.exit(1)});function ee(e){const t=[];for(let r=0;r<e.length;r++){const n=e[r];if(K.has(n)&&r+1<e.length&&!e[r+1].startsWith(`${n}=`)){t.push(`${n}=${e[r+1]}`),r++;continue}t.push(n)}return t}function te(e,t){const r=e.split(",").map(n=>n.trim()).filter(Boolean);if(r.length===0)throw new Error(`--${t} must contain at least one value.`);return r}function re(e){const t=[];let r="",n=null,a=!1;for(const s of e){if(a){r+=s,a=!1;continue}if(s==="\\"){a=!0;continue}if(n){s===n?n=null:r+=s;continue}if(s==='"'||s==="'"){n=s;continue}if(/\s/.test(s)){r.length>0&&(t.push(r),r="");continue}r+=s}if(a&&(r+="\\"),n)throw new Error("Unterminated quote in --config-args.");return r.length>0&&t.push(r),t}function ne(e){let t=i.resolve(e);for(;t!==i.dirname(t);){if(v(i.join(t,"packages/php-wasm/compile"))&&v(i.join(t,"nx.json")))return t;t=i.dirname(t)}return process.cwd()}function ae(e){const t=process.argv[1];if(!t)return!1;try{return w(t)===w(x(e))}catch{return i.resolve(t)===x(e)}}
|
|
4
4
|
//# sourceMappingURL=cli.js.map
|
package/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sources":["../../../../packages/php-wasm/compile-extension/src/docker.ts","../../../../packages/php-wasm/compile-extension/src/manifest.ts","../../../../packages/php-wasm/compile-extension/src/compile.ts","../../../../packages/php-wasm/compile-extension/src/detect.ts","../../../../packages/php-wasm/compile-extension/src/cli.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport path from 'node:path';\n\nimport type { AsyncMode } from './manifest';\n\nexport interface DockerBuildContext {\n\tworkspaceRoot: string;\n\tphpWasmRoot: string;\n\tcompileRoot: string;\n}\n\nexport interface DockerImageOptions extends DockerBuildContext {\n\tphpVersion: string;\n\tphpRelease: string;\n\tasyncMode: AsyncMode;\n}\n\nexport interface DockerRunOptions extends DockerImageOptions {\n\tsourceDir: string;\n\toutDir: string;\n\tname: string;\n\tartifactFile: string;\n\toptimize: string;\n\textraCflags?: string;\n\textraLdflags?: string;\n\tconfigArgs: string[];\n}\n\nexport function createDockerContext(workspaceRoot: string): DockerBuildContext {\n\tconst phpWasmRoot = path.join(workspaceRoot, 'packages/php-wasm');\n\treturn {\n\t\tworkspaceRoot,\n\t\tphpWasmRoot,\n\t\tcompileRoot: path.join(phpWasmRoot, 'compile'),\n\t};\n}\n\nexport async function assertDockerIsAvailable(): Promise<void> {\n\ttry {\n\t\tawait runCommand('docker', ['--version'], {\n\t\t\tstdio: 'ignore',\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t'Docker is required to compile PHP.wasm extensions, but the docker command was not found.',\n\t\t\t{ cause: error }\n\t\t);\n\t}\n}\n\nexport async function buildBaseImage(context: DockerBuildContext) {\n\tawait runCommand('make', ['base-image'], {\n\t\tcwd: context.compileRoot,\n\t});\n}\n\nexport async function buildExtensionImage(\n\toptions: DockerImageOptions\n): Promise<string> {\n\tconst imageTag = getExtensionImageTag(options);\n\tawait runCommand(\n\t\t'docker',\n\t\t[\n\t\t\t'build',\n\t\t\t'-f',\n\t\t\t'compile-extension/docker/Dockerfile.ext',\n\t\t\t'.',\n\t\t\t`--tag=${imageTag}`,\n\t\t\t'--progress=plain',\n\t\t\t'--build-arg',\n\t\t\t`PHP_VERSION=${options.phpRelease}`,\n\t\t\t'--build-arg',\n\t\t\t'JSPI=yes',\n\t\t],\n\t\t{\n\t\t\tcwd: options.phpWasmRoot,\n\t\t}\n\t);\n\treturn imageTag;\n}\n\nexport async function runExtensionBuild(options: DockerRunOptions) {\n\tconst imageTag = getExtensionImageTag(options);\n\tconst runArgs = [\n\t\t'run',\n\t\t'--rm',\n\t\t'-v',\n\t\t`${options.sourceDir}:/src:ro`,\n\t\t'-v',\n\t\t`${options.outDir}:/out`,\n\t\t'-v',\n\t\t`${options.compileRoot}:/php-wasm-compile:ro`,\n\t\t'--env',\n\t\t`EXTENSION_NAME=${options.name}`,\n\t\t'--env',\n\t\t`PHP_VERSION_SHORT=${options.phpVersion}`,\n\t\t'--env',\n\t\t`ASYNC_MODE=${options.asyncMode}`,\n\t\t'--env',\n\t\t`ARTIFACT_FILENAME=${options.artifactFile}`,\n\t\t'--env',\n\t\t`OPTIMIZE=${options.optimize}`,\n\t\t'--env',\n\t\t`CONFIG_ARGS_COUNT=${options.configArgs.length}`,\n\t];\n\n\tif (options.extraCflags) {\n\t\trunArgs.push('--env', `EXTRA_CFLAGS=${options.extraCflags}`);\n\t}\n\tif (options.extraLdflags) {\n\t\trunArgs.push('--env', `EXTRA_LDFLAGS=${options.extraLdflags}`);\n\t}\n\n\toptions.configArgs.forEach((arg, index) => {\n\t\trunArgs.push('--env', `CONFIG_ARG_${index}=${arg}`);\n\t});\n\n\trunArgs.push(imageTag);\n\n\tawait runCommand('docker', runArgs, {\n\t\tcwd: options.workspaceRoot,\n\t});\n}\n\nfunction getExtensionImageTag(\n\toptions: Pick<DockerImageOptions, 'phpVersion' | 'asyncMode'>\n) {\n\tconst phpVersion = options.phpVersion.replaceAll('.', '-');\n\treturn `playground-php-wasm:compile-extension-php${phpVersion}-${options.asyncMode}`;\n}\n\ninterface RunCommandOptions {\n\tcwd?: string;\n\tstdio?: 'inherit' | 'ignore';\n}\n\nasync function runCommand(\n\tcommand: string,\n\targs: string[],\n\toptions: RunCommandOptions = {}\n): Promise<void> {\n\tconsole.log(`Running ${command} ${args.join(' ')}`);\n\tawait new Promise<void>((resolve, reject) => {\n\t\tconst child = spawn(command, args, {\n\t\t\tcwd: options.cwd,\n\t\t\tstdio: options.stdio ?? 'inherit',\n\t\t\tenv: process.env,\n\t\t});\n\t\tchild.on('error', reject);\n\t\tchild.on('close', (code) => {\n\t\t\tif (code === 0) {\n\t\t\t\tresolve();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treject(new Error(`${command} exited with code ${code}`));\n\t\t});\n\t});\n}\n","import { createHash } from 'node:crypto';\nimport { createReadStream } from 'node:fs';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nexport const ExtensionAsyncMode = 'jspi';\nexport type AsyncMode = typeof ExtensionAsyncMode;\n\nexport interface ExtensionArtifact {\n\tphpVersion: string;\n\tfile: string;\n\tsha256: string;\n}\n\nexport interface ExtensionManifest {\n\tname: string;\n\tversion: string;\n\tartifacts: ExtensionArtifact[];\n}\n\nexport interface BuiltArtifact {\n\tphpVersion: string;\n\tfile: string;\n\tpath: string;\n}\n\nexport async function sha256File(filePath: string): Promise<string> {\n\tconst hash = createHash('sha256');\n\tawait new Promise<void>((resolve, reject) => {\n\t\tconst stream = createReadStream(filePath);\n\t\tstream.on('data', (chunk) => hash.update(chunk));\n\t\tstream.on('error', reject);\n\t\tstream.on('end', resolve);\n\t});\n\treturn hash.digest('hex');\n}\n\nexport async function createManifest(options: {\n\tname: string;\n\tversion: string;\n\tartifacts: BuiltArtifact[];\n}): Promise<ExtensionManifest> {\n\treturn {\n\t\tname: options.name,\n\t\tversion: options.version,\n\t\tartifacts: await Promise.all(\n\t\t\toptions.artifacts.map(async (artifact) => ({\n\t\t\t\tphpVersion: artifact.phpVersion,\n\t\t\t\tfile: artifact.file,\n\t\t\t\tsha256: await sha256File(artifact.path),\n\t\t\t}))\n\t\t),\n\t};\n}\n\nexport async function writeManifest(options: {\n\toutDir: string;\n\tmanifest: ExtensionManifest;\n}): Promise<string> {\n\tawait mkdir(options.outDir, { recursive: true });\n\tconst manifestPath = path.join(options.outDir, 'manifest.json');\n\tawait writeFile(\n\t\tmanifestPath,\n\t\t`${JSON.stringify(options.manifest, null, 2)}\\n`\n\t);\n\treturn manifestPath;\n}\n\nexport function findExtensionArtifact(\n\tmanifest: ExtensionManifest,\n\tphpVersion: string\n): ExtensionArtifact | undefined {\n\treturn manifest.artifacts.find(\n\t\t(artifact) => artifact.phpVersion === phpVersion\n\t);\n}\n","import { mkdir, readFile } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport {\n\tassertDockerIsAvailable,\n\tbuildBaseImage,\n\tbuildExtensionImage,\n\tcreateDockerContext,\n\trunExtensionBuild,\n} from './docker';\nimport type { AsyncMode, BuiltArtifact } from './manifest';\nimport { createManifest, ExtensionAsyncMode, writeManifest } from './manifest';\n\nexport const SupportedExtensionPHPVersions = [\n\t'8.5',\n\t'8.4',\n\t'8.3',\n\t'8.2',\n\t'8.1',\n\t'8.0',\n\t'7.4',\n] as const;\n\nconst PHP_RELEASE_BY_MINOR: Record<string, string> = {\n\t'8.5': '8.5.5',\n\t'8.4': '8.4.20',\n\t'8.3': '8.3.30',\n\t'8.2': '8.2.30',\n\t'8.1': '8.1.34',\n\t'8.0': '8.0.30',\n\t'7.4': '7.4.33',\n};\n\nexport interface CompileExtensionOptions {\n\tworkspaceRoot: string;\n\tsourceDir: string;\n\toutDir: string;\n\tname: string;\n\tphpVersions: string[];\n\textraCflags?: string;\n\textraLdflags?: string;\n\tconfigArgs: string[];\n\toptimize: string;\n\tjobs?: number;\n}\n\nexport async function compileExtensionMatrix(options: CompileExtensionOptions) {\n\tconst outDir = path.resolve(options.workspaceRoot, options.outDir);\n\tconst sourceDir = path.resolve(options.workspaceRoot, options.sourceDir);\n\tconst context = createDockerContext(options.workspaceRoot);\n\tconst version = await detectManifestVersion(sourceDir);\n\tconst matrix: Array<{ phpVersion: string; asyncMode: AsyncMode }> =\n\t\toptions.phpVersions.map((phpVersion) => ({\n\t\t\tphpVersion,\n\t\t\tasyncMode: ExtensionAsyncMode,\n\t\t}));\n\n\tawait mkdir(outDir, { recursive: true });\n\tawait assertDockerIsAvailable();\n\tawait buildBaseImage(context);\n\n\tconst artifacts = await mapLimit(\n\t\tmatrix,\n\t\tnormalizeJobCount(options.jobs, matrix.length),\n\t\tasync ({ phpVersion, asyncMode }) => {\n\t\t\tconst phpRelease = resolvePHPRelease(phpVersion);\n\t\t\tconst artifactFile = `${options.name}-php${phpVersion}-${asyncMode}.so`;\n\t\t\tawait buildExtensionImage({\n\t\t\t\t...context,\n\t\t\t\tphpVersion,\n\t\t\t\tphpRelease,\n\t\t\t\tasyncMode,\n\t\t\t});\n\t\t\tawait runExtensionBuild({\n\t\t\t\t...context,\n\t\t\t\tsourceDir,\n\t\t\t\toutDir,\n\t\t\t\tname: options.name,\n\t\t\t\tphpVersion,\n\t\t\t\tphpRelease,\n\t\t\t\tasyncMode,\n\t\t\t\tartifactFile,\n\t\t\t\toptimize: options.optimize,\n\t\t\t\textraCflags: options.extraCflags,\n\t\t\t\textraLdflags: options.extraLdflags,\n\t\t\t\tconfigArgs: options.configArgs,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tphpVersion,\n\t\t\t\tfile: artifactFile,\n\t\t\t\tpath: path.join(outDir, artifactFile),\n\t\t\t} satisfies BuiltArtifact;\n\t\t}\n\t);\n\n\tconst manifest = await createManifest({\n\t\tname: options.name,\n\t\tversion,\n\t\tartifacts,\n\t});\n\tconst manifestPath = await writeManifest({ outDir, manifest });\n\treturn { manifestPath, artifacts, manifest };\n}\n\nexport function resolvePHPRelease(phpVersion: string): string {\n\treturn PHP_RELEASE_BY_MINOR[phpVersion] ?? phpVersion;\n}\n\nasync function detectManifestVersion(sourceDir: string): Promise<string> {\n\ttry {\n\t\tconst packageJson = JSON.parse(\n\t\t\tawait readFile(path.join(sourceDir, 'package.json'), 'utf8')\n\t\t) as { version?: unknown };\n\t\tif (typeof packageJson.version === 'string') {\n\t\t\treturn packageJson.version;\n\t\t}\n\t} catch {\n\t\t// Native PHP extension sources usually do not contain package.json.\n\t}\n\treturn '0.0.0';\n}\n\nasync function mapLimit<T, R>(\n\titems: T[],\n\tlimit: number,\n\tworker: (item: T) => Promise<R>\n): Promise<R[]> {\n\tconst results: R[] = new Array(items.length) as R[];\n\tlet nextIndex = 0;\n\tconst workers = Array.from(\n\t\t{ length: Math.min(limit, items.length) },\n\t\tasync () => {\n\t\t\twhile (nextIndex < items.length) {\n\t\t\t\tconst index = nextIndex++;\n\t\t\t\tresults[index] = await worker(items[index]);\n\t\t\t}\n\t\t}\n\t);\n\tawait Promise.all(workers);\n\treturn results;\n}\n\nfunction normalizeJobCount(\n\tjobs: number | undefined,\n\ttaskCount: number\n): number {\n\tif (taskCount === 0) {\n\t\treturn 1;\n\t}\n\tif (jobs && jobs > 0) {\n\t\treturn Math.min(jobs, taskCount);\n\t}\n\treturn Math.min(os.availableParallelism?.() ?? os.cpus().length, taskCount);\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nconst EXTENSION_NAME_PATTERNS = [\n\t/\\bPHP_ARG_ENABLE\\(\\s*\\[?([A-Za-z0-9_]+)\\]?/m,\n\t/\\bPHP_ARG_WITH\\(\\s*\\[?([A-Za-z0-9_]+)\\]?/m,\n\t/\\bPHP_NEW_EXTENSION\\(\\s*\\[?([A-Za-z0-9_]+)\\]?/m,\n];\n\nexport async function detectExtensionName(sourceDir: string): Promise<string> {\n\tconst configPath = path.join(sourceDir, 'config.m4');\n\tlet config: string;\n\ttry {\n\t\tconfig = await readFile(configPath, 'utf8');\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Could not read ${configPath}. Pass --name or provide a config.m4 file.`,\n\t\t\t{ cause: error }\n\t\t);\n\t}\n\n\tconst detected = detectExtensionNameFromConfig(config);\n\tif (!detected) {\n\t\tthrow new Error(\n\t\t\t`Could not detect the extension name from ${configPath}. Pass --name explicitly.`\n\t\t);\n\t}\n\treturn detected;\n}\n\nexport function detectExtensionNameFromConfig(config: string): string | null {\n\tfor (const pattern of EXTENSION_NAME_PATTERNS) {\n\t\tconst match = pattern.exec(config);\n\t\tif (match?.[1]) {\n\t\t\treturn match[1];\n\t\t}\n\t}\n\treturn null;\n}\n","#!/usr/bin/env node\nimport { existsSync, realpathSync } from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\n\nimport {\n\tcompileExtensionMatrix,\n\tSupportedExtensionPHPVersions,\n} from './compile';\nimport { detectExtensionName } from './detect';\n\nconst OptionsWithDashPrefixedValues = new Set([\n\t'--config-args',\n\t'--extra-cflags',\n\t'--extra-ldflags',\n]);\n\nexport async function main(args = hideBin(process.argv)) {\n\tconst argv = await yargs(normalizeDashPrefixedOptionValues(args))\n\t\t.scriptName('@php-wasm/compile-extension')\n\t\t.usage('Usage: $0 --source <dir> [options]')\n\t\t.options({\n\t\t\tsource: {\n\t\t\t\ttype: 'string',\n\t\t\t\tdemandOption: true,\n\t\t\t\tdescription: 'Extension source directory containing config.m4',\n\t\t\t},\n\t\t\tname: {\n\t\t\t\ttype: 'string',\n\t\t\t\tdescription: 'Extension name. Defaults to parsing config.m4.',\n\t\t\t},\n\t\t\t'php-versions': {\n\t\t\t\ttype: 'string',\n\t\t\t\tdefault: SupportedExtensionPHPVersions.join(','),\n\t\t\t\tdescription: 'Comma-separated PHP major.minor versions.',\n\t\t\t},\n\t\t\tout: {\n\t\t\t\ttype: 'string',\n\t\t\t\tdefault: './dist',\n\t\t\t\tdescription: 'Output directory.',\n\t\t\t},\n\t\t\t'extra-cflags': {\n\t\t\t\ttype: 'string',\n\t\t\t\tdescription: 'Extra CFLAGS appended to the side-module build.',\n\t\t\t},\n\t\t\t'extra-ldflags': {\n\t\t\t\ttype: 'string',\n\t\t\t\tdescription: 'Extra LDFLAGS appended to the side-module build.',\n\t\t\t},\n\t\t\t'config-args': {\n\t\t\t\ttype: 'string',\n\t\t\t\tdefault: '',\n\t\t\t\tdescription:\n\t\t\t\t\t'Extra ./configure arguments, parsed as shell words.',\n\t\t\t},\n\t\t\toptimize: {\n\t\t\t\ttype: 'string',\n\t\t\t\tdefault: '2',\n\t\t\t\tdescription: 'Optimization level passed as -O<level>.',\n\t\t\t},\n\t\t\tjobs: {\n\t\t\t\ttype: 'number',\n\t\t\t\tdescription: 'Maximum concurrent docker builds.',\n\t\t\t},\n\t\t})\n\t\t.strict()\n\t\t.help()\n\t\t.parse();\n\n\tconst workspaceRoot = findWorkspaceRoot(process.cwd());\n\tconst sourceDir = path.resolve(workspaceRoot, argv['source'] as string);\n\tconst name =\n\t\t(argv['name'] as string | undefined) ??\n\t\t(await detectExtensionName(sourceDir));\n\tconst phpVersions = parseCsv(\n\t\targv['php-versions'] as string,\n\t\t'php-versions'\n\t);\n\tconst configArgs = splitShellWords((argv['config-args'] as string) || '');\n\n\tconst result = await compileExtensionMatrix({\n\t\tworkspaceRoot,\n\t\tsourceDir,\n\t\toutDir: argv['out'] as string,\n\t\tname,\n\t\tphpVersions,\n\t\textraCflags: argv['extra-cflags'] as string | undefined,\n\t\textraLdflags: argv['extra-ldflags'] as string | undefined,\n\t\tconfigArgs,\n\t\toptimize: argv['optimize'] as string,\n\t\tjobs: argv['jobs'] as number | undefined,\n\t});\n\n\tconsole.log(`Wrote ${result.artifacts.length} artifacts.`);\n\tconsole.log(`Wrote ${result.manifestPath}.`);\n}\n\nif (isCliEntrypoint(import.meta.url)) {\n\tmain().catch((error) => {\n\t\tconsole.error(error instanceof Error ? error.message : error);\n\t\tprocess.exit(1);\n\t});\n}\n\nexport function normalizeDashPrefixedOptionValues(args: string[]): string[] {\n\tconst normalized: string[] = [];\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\t\tif (\n\t\t\tOptionsWithDashPrefixedValues.has(arg) &&\n\t\t\ti + 1 < args.length &&\n\t\t\t!args[i + 1].startsWith(`${arg}=`)\n\t\t) {\n\t\t\tnormalized.push(`${arg}=${args[i + 1]}`);\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\t\tnormalized.push(arg);\n\t}\n\treturn normalized;\n}\n\nfunction parseCsv(value: string, name: string): string[] {\n\tconst values = value\n\t\t.split(',')\n\t\t.map((entry) => entry.trim())\n\t\t.filter(Boolean);\n\tif (values.length === 0) {\n\t\tthrow new Error(`--${name} must contain at least one value.`);\n\t}\n\treturn values;\n}\n\nexport function splitShellWords(value: string): string[] {\n\tconst words: string[] = [];\n\tlet current = '';\n\tlet quote: '\"' | \"'\" | null = null;\n\tlet escaping = false;\n\n\tfor (const character of value) {\n\t\tif (escaping) {\n\t\t\tcurrent += character;\n\t\t\tescaping = false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (character === '\\\\') {\n\t\t\tescaping = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (quote) {\n\t\t\tif (character === quote) {\n\t\t\t\tquote = null;\n\t\t\t} else {\n\t\t\t\tcurrent += character;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (character === '\"' || character === \"'\") {\n\t\t\tquote = character;\n\t\t\tcontinue;\n\t\t}\n\t\tif (/\\s/.test(character)) {\n\t\t\tif (current.length > 0) {\n\t\t\t\twords.push(current);\n\t\t\t\tcurrent = '';\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tcurrent += character;\n\t}\n\n\tif (escaping) {\n\t\tcurrent += '\\\\';\n\t}\n\tif (quote) {\n\t\tthrow new Error('Unterminated quote in --config-args.');\n\t}\n\tif (current.length > 0) {\n\t\twords.push(current);\n\t}\n\treturn words;\n}\n\nfunction findWorkspaceRoot(startDirectory: string): string {\n\tlet directory = path.resolve(startDirectory);\n\twhile (directory !== path.dirname(directory)) {\n\t\tif (\n\t\t\texistsSync(path.join(directory, 'packages/php-wasm/compile')) &&\n\t\t\texistsSync(path.join(directory, 'nx.json'))\n\t\t) {\n\t\t\treturn directory;\n\t\t}\n\t\tdirectory = path.dirname(directory);\n\t}\n\treturn process.cwd();\n}\n\nfunction isCliEntrypoint(metaUrl: string): boolean {\n\tconst entrypoint = process.argv[1];\n\tif (!entrypoint) {\n\t\treturn false;\n\t}\n\ttry {\n\t\treturn (\n\t\t\trealpathSync(entrypoint) === realpathSync(fileURLToPath(metaUrl))\n\t\t);\n\t} catch {\n\t\treturn path.resolve(entrypoint) === fileURLToPath(metaUrl);\n\t}\n}\n"],"names":["createDockerContext","workspaceRoot","phpWasmRoot","path","assertDockerIsAvailable","runCommand","error","buildBaseImage","context","buildExtensionImage","options","imageTag","getExtensionImageTag","runExtensionBuild","runArgs","arg","index","command","args","resolve","reject","child","spawn","code","ExtensionAsyncMode","sha256File","filePath","hash","createHash","stream","createReadStream","chunk","createManifest","artifact","writeManifest","mkdir","manifestPath","writeFile","SupportedExtensionPHPVersions","PHP_RELEASE_BY_MINOR","compileExtensionMatrix","outDir","sourceDir","version","detectManifestVersion","matrix","phpVersion","artifacts","mapLimit","normalizeJobCount","asyncMode","phpRelease","resolvePHPRelease","artifactFile","manifest","packageJson","readFile","items","limit","worker","results","nextIndex","workers","jobs","taskCount","os","EXTENSION_NAME_PATTERNS","detectExtensionName","configPath","config","detected","detectExtensionNameFromConfig","pattern","match","OptionsWithDashPrefixedValues","main","hideBin","argv","yargs","normalizeDashPrefixedOptionValues","findWorkspaceRoot","name","phpVersions","parseCsv","configArgs","splitShellWords","result","isCliEntrypoint","normalized","i","value","values","entry","words","current","quote","escaping","character","startDirectory","directory","existsSync","metaUrl","entrypoint","realpathSync","fileURLToPath"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","sources":["../../../../packages/php-wasm/compile-extension/src/docker.ts","../../../../packages/php-wasm/compile-extension/src/manifest.ts","../../../../packages/php-wasm/compile-extension/src/compile.ts","../../../../packages/php-wasm/compile-extension/src/detect.ts","../../../../packages/php-wasm/compile-extension/src/extra-files.ts","../../../../packages/php-wasm/compile-extension/src/cli.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport path from 'node:path';\n\nimport type { AsyncMode } from './manifest';\n\nexport interface DockerBuildContext {\n\tworkspaceRoot: string;\n\tphpWasmRoot: string;\n\tcompileRoot: string;\n}\n\nexport interface DockerImageOptions extends DockerBuildContext {\n\tphpVersion: string;\n\tphpRelease: string;\n\tasyncMode: AsyncMode;\n}\n\nexport interface DockerRunOptions extends DockerImageOptions {\n\tsourceDir: string;\n\toutDir: string;\n\tname: string;\n\tartifactFile: string;\n\toptimize: string;\n\textraCflags?: string;\n\textraLdflags?: string;\n\tconfigArgs: string[];\n}\n\nexport function createDockerContext(workspaceRoot: string): DockerBuildContext {\n\tconst phpWasmRoot = path.join(workspaceRoot, 'packages/php-wasm');\n\treturn {\n\t\tworkspaceRoot,\n\t\tphpWasmRoot,\n\t\tcompileRoot: path.join(phpWasmRoot, 'compile'),\n\t};\n}\n\nexport async function assertDockerIsAvailable(): Promise<void> {\n\ttry {\n\t\tawait runCommand('docker', ['--version'], {\n\t\t\tstdio: 'ignore',\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t'Docker is required to compile PHP.wasm extensions, but the docker command was not found.',\n\t\t\t{ cause: error }\n\t\t);\n\t}\n}\n\nexport async function buildBaseImage(context: DockerBuildContext) {\n\tawait runCommand('make', ['base-image'], {\n\t\tcwd: context.compileRoot,\n\t});\n}\n\nexport async function buildExtensionImage(\n\toptions: DockerImageOptions\n): Promise<string> {\n\tconst imageTag = getExtensionImageTag(options);\n\tawait runCommand(\n\t\t'docker',\n\t\t[\n\t\t\t'build',\n\t\t\t'-f',\n\t\t\t'compile-extension/docker/Dockerfile.ext',\n\t\t\t'.',\n\t\t\t`--tag=${imageTag}`,\n\t\t\t'--progress=plain',\n\t\t\t'--build-arg',\n\t\t\t`PHP_VERSION=${options.phpRelease}`,\n\t\t\t'--build-arg',\n\t\t\t'JSPI=yes',\n\t\t],\n\t\t{\n\t\t\tcwd: options.phpWasmRoot,\n\t\t}\n\t);\n\treturn imageTag;\n}\n\nexport async function runExtensionBuild(options: DockerRunOptions) {\n\tconst imageTag = getExtensionImageTag(options);\n\tconst runArgs = [\n\t\t'run',\n\t\t'--rm',\n\t\t'-v',\n\t\t`${options.sourceDir}:/src:ro`,\n\t\t'-v',\n\t\t`${options.outDir}:/out`,\n\t\t'-v',\n\t\t`${options.compileRoot}:/php-wasm-compile:ro`,\n\t\t'--env',\n\t\t`EXTENSION_NAME=${options.name}`,\n\t\t'--env',\n\t\t`PHP_VERSION_SHORT=${options.phpVersion}`,\n\t\t'--env',\n\t\t`ASYNC_MODE=${options.asyncMode}`,\n\t\t'--env',\n\t\t`ARTIFACT_FILENAME=${options.artifactFile}`,\n\t\t'--env',\n\t\t`OPTIMIZE=${options.optimize}`,\n\t\t'--env',\n\t\t`CONFIG_ARGS_COUNT=${options.configArgs.length}`,\n\t];\n\n\tif (options.extraCflags) {\n\t\trunArgs.push('--env', `EXTRA_CFLAGS=${options.extraCflags}`);\n\t}\n\tif (options.extraLdflags) {\n\t\trunArgs.push('--env', `EXTRA_LDFLAGS=${options.extraLdflags}`);\n\t}\n\n\toptions.configArgs.forEach((arg, index) => {\n\t\trunArgs.push('--env', `CONFIG_ARG_${index}=${arg}`);\n\t});\n\n\trunArgs.push(imageTag);\n\n\tawait runCommand('docker', runArgs, {\n\t\tcwd: options.workspaceRoot,\n\t});\n}\n\nfunction getExtensionImageTag(\n\toptions: Pick<DockerImageOptions, 'phpVersion' | 'asyncMode'>\n) {\n\tconst phpVersion = options.phpVersion.replaceAll('.', '-');\n\treturn `playground-php-wasm:compile-extension-php${phpVersion}-${options.asyncMode}`;\n}\n\ninterface RunCommandOptions {\n\tcwd?: string;\n\tstdio?: 'inherit' | 'ignore';\n}\n\nasync function runCommand(\n\tcommand: string,\n\targs: string[],\n\toptions: RunCommandOptions = {}\n): Promise<void> {\n\tconsole.log(`Running ${command} ${args.join(' ')}`);\n\tawait new Promise<void>((resolve, reject) => {\n\t\tconst child = spawn(command, args, {\n\t\t\tcwd: options.cwd,\n\t\t\tstdio: options.stdio ?? 'inherit',\n\t\t\tenv: process.env,\n\t\t});\n\t\tchild.on('error', reject);\n\t\tchild.on('close', (code) => {\n\t\t\tif (code === 0) {\n\t\t\t\tresolve();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treject(new Error(`${command} exited with code ${code}`));\n\t\t});\n\t});\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nexport const ExtensionAsyncMode = 'jspi';\nexport type AsyncMode = typeof ExtensionAsyncMode;\n\n/**\n * One sidecar file or empty directory staged alongside an artifact.\n *\n * Mirrors `PHPExtensionManifestExtraFile` in `@php-wasm/universal`.\n */\nexport interface ExtensionManifestExtraFile {\n\t/** Joined with the group's `vfsRoot` to form the final VFS path. */\n\tvfsPath: string;\n\t/** Defaults to \"file\". Only file nodes need a `sourcePath`. */\n\ttype?: 'file' | 'directory';\n\t/** Relative to the manifest URL/base URL, or an absolute URL. */\n\tsourcePath?: string;\n}\n\nexport interface ExtensionManifestExtraFiles {\n\t/** Absolute VFS prefix joined with each node's `vfsPath`. */\n\tvfsRoot?: string;\n\tnodes?: ExtensionManifestExtraFile[];\n}\n\nexport interface ExtensionArtifact {\n\tphpVersion: string;\n\t/** Path to the `.so` file, relative to the manifest URL or absolute. */\n\tsourcePath: string;\n\t/** URL-backed files needed only by this artifact. */\n\textraFiles?: ExtensionManifestExtraFiles;\n}\n\nexport interface ExtensionManifest {\n\tname: string;\n\tversion: string;\n\tartifacts: ExtensionArtifact[];\n\t/** URL-backed files shared by every artifact in this manifest. */\n\textraFiles?: ExtensionManifestExtraFiles;\n}\n\nexport interface BuiltArtifact {\n\tphpVersion: string;\n\t/** Path to the `.so` file, relative to the manifest URL. */\n\tsourcePath: string;\n\t/** Absolute path on disk where the `.so` was written. */\n\tpath: string;\n}\n\nexport async function createManifest(options: {\n\tname: string;\n\tversion: string;\n\tartifacts: BuiltArtifact[];\n\textraFiles?: ExtensionManifestExtraFiles;\n}): Promise<ExtensionManifest> {\n\tconst manifest: ExtensionManifest = {\n\t\tname: options.name,\n\t\tversion: options.version,\n\t\tartifacts: options.artifacts.map((artifact) => ({\n\t\t\tphpVersion: artifact.phpVersion,\n\t\t\tsourcePath: artifact.sourcePath,\n\t\t})),\n\t};\n\tif (options.extraFiles) {\n\t\tmanifest.extraFiles = options.extraFiles;\n\t}\n\treturn manifest;\n}\n\nexport async function writeManifest(options: {\n\toutDir: string;\n\tmanifest: ExtensionManifest;\n}): Promise<string> {\n\tawait mkdir(options.outDir, { recursive: true });\n\tconst manifestPath = path.join(options.outDir, 'manifest.json');\n\tawait writeFile(\n\t\tmanifestPath,\n\t\t`${JSON.stringify(options.manifest, null, 2)}\\n`\n\t);\n\treturn manifestPath;\n}\n\nexport function findExtensionArtifact(\n\tmanifest: ExtensionManifest,\n\tphpVersion: string\n): ExtensionArtifact | undefined {\n\treturn manifest.artifacts.find(\n\t\t(artifact) => artifact.phpVersion === phpVersion\n\t);\n}\n","import { mkdir, readFile } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport {\n\tassertDockerIsAvailable,\n\tbuildBaseImage,\n\tbuildExtensionImage,\n\tcreateDockerContext,\n\trunExtensionBuild,\n} from './docker';\nimport type {\n\tAsyncMode,\n\tBuiltArtifact,\n\tExtensionManifestExtraFiles,\n} from './manifest';\nimport { createManifest, ExtensionAsyncMode, writeManifest } from './manifest';\n\nexport const SupportedExtensionPHPVersions = [\n\t'8.5',\n\t'8.4',\n\t'8.3',\n\t'8.2',\n\t'8.1',\n\t'8.0',\n\t'7.4',\n] as const;\n\nconst PHP_RELEASE_BY_MINOR: Record<string, string> = {\n\t'8.5': '8.5.5',\n\t'8.4': '8.4.20',\n\t'8.3': '8.3.30',\n\t'8.2': '8.2.30',\n\t'8.1': '8.1.34',\n\t'8.0': '8.0.30',\n\t'7.4': '7.4.33',\n};\n\nexport interface CompileExtensionOptions {\n\tworkspaceRoot: string;\n\tsourceDir: string;\n\toutDir: string;\n\tname: string;\n\tphpVersions: string[];\n\textraCflags?: string;\n\textraLdflags?: string;\n\tconfigArgs: string[];\n\toptimize: string;\n\tjobs?: number;\n\t/** Sidecar files to record at the manifest level. */\n\textraFiles?: ExtensionManifestExtraFiles;\n}\n\nexport async function compileExtensionMatrix(options: CompileExtensionOptions) {\n\tconst outDir = path.resolve(options.workspaceRoot, options.outDir);\n\tconst sourceDir = path.resolve(options.workspaceRoot, options.sourceDir);\n\tconst context = createDockerContext(options.workspaceRoot);\n\tconst version = await detectManifestVersion(sourceDir);\n\tconst matrix: Array<{ phpVersion: string; asyncMode: AsyncMode }> =\n\t\toptions.phpVersions.map((phpVersion) => ({\n\t\t\tphpVersion,\n\t\t\tasyncMode: ExtensionAsyncMode,\n\t\t}));\n\n\tawait mkdir(outDir, { recursive: true });\n\tawait assertDockerIsAvailable();\n\tawait buildBaseImage(context);\n\n\tconst artifacts = await mapLimit(\n\t\tmatrix,\n\t\tnormalizeJobCount(options.jobs, matrix.length),\n\t\tasync ({ phpVersion, asyncMode }) => {\n\t\t\tconst phpRelease = resolvePHPRelease(phpVersion);\n\t\t\tconst artifactFile = `${options.name}-php${phpVersion}-${asyncMode}.so`;\n\t\t\tawait buildExtensionImage({\n\t\t\t\t...context,\n\t\t\t\tphpVersion,\n\t\t\t\tphpRelease,\n\t\t\t\tasyncMode,\n\t\t\t});\n\t\t\tawait runExtensionBuild({\n\t\t\t\t...context,\n\t\t\t\tsourceDir,\n\t\t\t\toutDir,\n\t\t\t\tname: options.name,\n\t\t\t\tphpVersion,\n\t\t\t\tphpRelease,\n\t\t\t\tasyncMode,\n\t\t\t\tartifactFile,\n\t\t\t\toptimize: options.optimize,\n\t\t\t\textraCflags: options.extraCflags,\n\t\t\t\textraLdflags: options.extraLdflags,\n\t\t\t\tconfigArgs: options.configArgs,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tphpVersion,\n\t\t\t\tsourcePath: artifactFile,\n\t\t\t\tpath: path.join(outDir, artifactFile),\n\t\t\t} satisfies BuiltArtifact;\n\t\t}\n\t);\n\n\tconst manifest = await createManifest({\n\t\tname: options.name,\n\t\tversion,\n\t\tartifacts,\n\t\textraFiles: options.extraFiles,\n\t});\n\tconst manifestPath = await writeManifest({ outDir, manifest });\n\treturn { manifestPath, artifacts, manifest };\n}\n\nexport function resolvePHPRelease(phpVersion: string): string {\n\treturn PHP_RELEASE_BY_MINOR[phpVersion] ?? phpVersion;\n}\n\nasync function detectManifestVersion(sourceDir: string): Promise<string> {\n\ttry {\n\t\tconst packageJson = JSON.parse(\n\t\t\tawait readFile(path.join(sourceDir, 'package.json'), 'utf8')\n\t\t) as { version?: unknown };\n\t\tif (typeof packageJson.version === 'string') {\n\t\t\treturn packageJson.version;\n\t\t}\n\t} catch {\n\t\t// Native PHP extension sources usually do not contain package.json.\n\t}\n\treturn '0.0.0';\n}\n\nasync function mapLimit<T, R>(\n\titems: T[],\n\tlimit: number,\n\tworker: (item: T) => Promise<R>\n): Promise<R[]> {\n\tconst results: R[] = new Array(items.length) as R[];\n\tlet nextIndex = 0;\n\tconst workers = Array.from(\n\t\t{ length: Math.min(limit, items.length) },\n\t\tasync () => {\n\t\t\twhile (nextIndex < items.length) {\n\t\t\t\tconst index = nextIndex++;\n\t\t\t\tresults[index] = await worker(items[index]);\n\t\t\t}\n\t\t}\n\t);\n\tawait Promise.all(workers);\n\treturn results;\n}\n\nfunction normalizeJobCount(\n\tjobs: number | undefined,\n\ttaskCount: number\n): number {\n\tif (taskCount === 0) {\n\t\treturn 1;\n\t}\n\tif (jobs && jobs > 0) {\n\t\treturn Math.min(jobs, taskCount);\n\t}\n\treturn Math.min(os.availableParallelism?.() ?? os.cpus().length, taskCount);\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nconst EXTENSION_NAME_PATTERNS = [\n\t/\\bPHP_ARG_ENABLE\\(\\s*\\[?([A-Za-z0-9_]+)\\]?/m,\n\t/\\bPHP_ARG_WITH\\(\\s*\\[?([A-Za-z0-9_]+)\\]?/m,\n\t/\\bPHP_NEW_EXTENSION\\(\\s*\\[?([A-Za-z0-9_]+)\\]?/m,\n];\n\nexport async function detectExtensionName(sourceDir: string): Promise<string> {\n\tconst configPath = path.join(sourceDir, 'config.m4');\n\tlet config: string;\n\ttry {\n\t\tconfig = await readFile(configPath, 'utf8');\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Could not read ${configPath}. Pass --name or provide a config.m4 file.`,\n\t\t\t{ cause: error }\n\t\t);\n\t}\n\n\tconst detected = detectExtensionNameFromConfig(config);\n\tif (!detected) {\n\t\tthrow new Error(\n\t\t\t`Could not detect the extension name from ${configPath}. Pass --name explicitly.`\n\t\t);\n\t}\n\treturn detected;\n}\n\nexport function detectExtensionNameFromConfig(config: string): string | null {\n\tfor (const pattern of EXTENSION_NAME_PATTERNS) {\n\t\tconst match = pattern.exec(config);\n\t\tif (match?.[1]) {\n\t\t\treturn match[1];\n\t\t}\n\t}\n\treturn null;\n}\n","import { cp, mkdir, readdir, stat } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport type {\n\tExtensionManifestExtraFile,\n\tExtensionManifestExtraFiles,\n} from './manifest';\n\n/**\n * Parses a `--extra-files` CLI argument of the form `<hostDir>:<vfsRoot>`.\n *\n * `vfsRoot` must be an absolute VFS path so the loader can stage files\n * without making up a default root. Splitting on the *last* colon lets\n * Windows host paths like `C:\\dir:/internal/root` round-trip cleanly.\n */\nexport function parseExtraFilesSpec(spec: string): {\n\thostDir: string;\n\tvfsRoot: string;\n} {\n\tconst separator = spec.lastIndexOf(':');\n\tif (separator < 0) {\n\t\tthrow new Error(\n\t\t\t`Invalid --extra-files value ${JSON.stringify(\n\t\t\t\tspec\n\t\t\t)}. Expected \"<hostDir>:<vfsRoot>\".`\n\t\t);\n\t}\n\tconst hostDir = spec.slice(0, separator);\n\tconst vfsRoot = spec.slice(separator + 1);\n\tif (!hostDir || !vfsRoot) {\n\t\tthrow new Error(\n\t\t\t`Invalid --extra-files value ${JSON.stringify(\n\t\t\t\tspec\n\t\t\t)}. Expected \"<hostDir>:<vfsRoot>\".`\n\t\t);\n\t}\n\tif (!vfsRoot.startsWith('/')) {\n\t\tthrow new Error(\n\t\t\t`--extra-files vfsRoot must be an absolute VFS path. Received ${JSON.stringify(\n\t\t\t\tvfsRoot\n\t\t\t)}.`\n\t\t);\n\t}\n\treturn { hostDir, vfsRoot };\n}\n\n/**\n * Copies one or more host directories into `outDir` and returns a manifest\n * `extraFiles` group whose `sourcePath` entries are relative to `outDir`.\n *\n * Each spec is `<hostDir>:<vfsRoot>`. All specs must agree on `vfsRoot`\n * because the manifest format only stores a single `vfsRoot` per group.\n *\n * Files keep their relative path under `vfsRoot`. Empty directories are\n * recorded as `type: 'directory'` nodes so the loader creates them.\n */\nexport async function stageExtraFilesIntoOutDir(\n\tspecs: Array<{ hostDir: string; vfsRoot: string }>,\n\toutDir: string,\n\tworkspaceRoot: string\n): Promise<ExtensionManifestExtraFiles | undefined> {\n\tif (!specs.length) {\n\t\treturn undefined;\n\t}\n\tconst vfsRoot = specs[0].vfsRoot;\n\tfor (const spec of specs) {\n\t\tif (spec.vfsRoot !== vfsRoot) {\n\t\t\tthrow new Error(\n\t\t\t\t'All --extra-files entries must share the same vfsRoot. Received ' +\n\t\t\t\t\tJSON.stringify(specs.map((entry) => entry.vfsRoot))\n\t\t\t);\n\t\t}\n\t}\n\n\tconst nodes: ExtensionManifestExtraFile[] = [];\n\tconst claimedVfsPaths = new Set<string>();\n\tconst claimedDestinations = new Set<string>();\n\tfor (const spec of specs) {\n\t\tconst absoluteHostDir = path.resolve(workspaceRoot, spec.hostDir);\n\t\tconst targetSubdir = path.basename(absoluteHostDir);\n\t\tconst destinationDir = path.join(outDir, targetSubdir);\n\t\tif (claimedDestinations.has(destinationDir)) {\n\t\t\tthrow new Error(\n\t\t\t\t`--extra-files destination collides on disk: ${destinationDir}. ` +\n\t\t\t\t\t`Two host directories share the same basename, so one would overwrite the other.`\n\t\t\t);\n\t\t}\n\t\tclaimedDestinations.add(destinationDir);\n\t\tawait mkdir(path.dirname(destinationDir), { recursive: true });\n\t\tawait cp(absoluteHostDir, destinationDir, { recursive: true });\n\n\t\tawait walk(destinationDir, async (entryPath, isDirectory) => {\n\t\t\tconst relativeUnderRoot = path\n\t\t\t\t.relative(destinationDir, entryPath)\n\t\t\t\t.split(path.sep)\n\t\t\t\t.join('/');\n\t\t\tif (!relativeUnderRoot) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst sourcePath = `${targetSubdir}/${relativeUnderRoot}`;\n\t\t\tif (isDirectory) {\n\t\t\t\tconst empty = (await readdir(entryPath)).length === 0;\n\t\t\t\tif (!empty) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (claimedVfsPaths.has(relativeUnderRoot)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`--extra-files vfsPath collides across specs: ${relativeUnderRoot}.`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tclaimedVfsPaths.add(relativeUnderRoot);\n\t\t\t\tnodes.push({\n\t\t\t\t\tvfsPath: relativeUnderRoot,\n\t\t\t\t\ttype: 'directory',\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (claimedVfsPaths.has(relativeUnderRoot)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`--extra-files vfsPath collides across specs: ${relativeUnderRoot}.`\n\t\t\t\t);\n\t\t\t}\n\t\t\tclaimedVfsPaths.add(relativeUnderRoot);\n\t\t\tnodes.push({\n\t\t\t\tvfsPath: relativeUnderRoot,\n\t\t\t\tsourcePath,\n\t\t\t});\n\t\t});\n\t}\n\t// Sort with a locale-independent comparator so the manifest is byte-stable\n\t// across machines (`localeCompare` honors the host locale).\n\tnodes.sort((a, b) =>\n\t\ta.vfsPath < b.vfsPath ? -1 : a.vfsPath > b.vfsPath ? 1 : 0\n\t);\n\n\treturn {\n\t\tvfsRoot,\n\t\tnodes,\n\t};\n}\n\nasync function walk(\n\tdir: string,\n\tvisitor: (entryPath: string, isDirectory: boolean) => Promise<void>\n): Promise<void> {\n\tfor (const entry of await readdir(dir)) {\n\t\tconst entryPath = path.join(dir, entry);\n\t\tconst entryStat = await stat(entryPath);\n\t\tawait visitor(entryPath, entryStat.isDirectory());\n\t\tif (entryStat.isDirectory()) {\n\t\t\tawait walk(entryPath, visitor);\n\t\t}\n\t}\n}\n","#!/usr/bin/env node\nimport { existsSync, realpathSync } from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\n\nimport {\n\tcompileExtensionMatrix,\n\tSupportedExtensionPHPVersions,\n} from './compile';\nimport { detectExtensionName } from './detect';\nimport { parseExtraFilesSpec, stageExtraFilesIntoOutDir } from './extra-files';\n\nconst OptionsWithDashPrefixedValues = new Set([\n\t'--config-args',\n\t'--extra-cflags',\n\t'--extra-ldflags',\n]);\n\nexport async function main(args = hideBin(process.argv)) {\n\tconst argv = await yargs(normalizeDashPrefixedOptionValues(args))\n\t\t.scriptName('@php-wasm/compile-extension')\n\t\t.usage('Usage: $0 --source <dir> [options]')\n\t\t.options({\n\t\t\tsource: {\n\t\t\t\ttype: 'string',\n\t\t\t\tdemandOption: true,\n\t\t\t\tdescription: 'Extension source directory containing config.m4',\n\t\t\t},\n\t\t\tname: {\n\t\t\t\ttype: 'string',\n\t\t\t\tdescription: 'Extension name. Defaults to parsing config.m4.',\n\t\t\t},\n\t\t\t'php-versions': {\n\t\t\t\ttype: 'string',\n\t\t\t\tdefault: SupportedExtensionPHPVersions.join(','),\n\t\t\t\tdescription: 'Comma-separated PHP major.minor versions.',\n\t\t\t},\n\t\t\tout: {\n\t\t\t\ttype: 'string',\n\t\t\t\tdefault: './dist',\n\t\t\t\tdescription: 'Output directory.',\n\t\t\t},\n\t\t\t'extra-cflags': {\n\t\t\t\ttype: 'string',\n\t\t\t\tdescription: 'Extra CFLAGS appended to the side-module build.',\n\t\t\t},\n\t\t\t'extra-ldflags': {\n\t\t\t\ttype: 'string',\n\t\t\t\tdescription: 'Extra LDFLAGS appended to the side-module build.',\n\t\t\t},\n\t\t\t'config-args': {\n\t\t\t\ttype: 'string',\n\t\t\t\tdefault: '',\n\t\t\t\tdescription:\n\t\t\t\t\t'Extra ./configure arguments, parsed as shell words.',\n\t\t\t},\n\t\t\toptimize: {\n\t\t\t\ttype: 'string',\n\t\t\t\tdefault: '2',\n\t\t\t\tdescription: 'Optimization level passed as -O<level>.',\n\t\t\t},\n\t\t\tjobs: {\n\t\t\t\ttype: 'number',\n\t\t\t\tdescription: 'Maximum concurrent docker builds.',\n\t\t\t},\n\t\t\t'extra-files': {\n\t\t\t\ttype: 'string',\n\t\t\t\tarray: true,\n\t\t\t\tdefault: [] as string[],\n\t\t\t\tdescription:\n\t\t\t\t\t'Stage a host directory under an absolute VFS root. Format: <hostDir>:<vfsRoot>. Files are copied next to the manifest and recorded in extraFiles.',\n\t\t\t},\n\t\t})\n\t\t.strict()\n\t\t.help()\n\t\t.parse();\n\n\tconst workspaceRoot = findWorkspaceRoot(process.cwd());\n\tconst sourceDir = path.resolve(workspaceRoot, argv['source'] as string);\n\tconst name =\n\t\t(argv['name'] as string | undefined) ??\n\t\t(await detectExtensionName(sourceDir));\n\tconst phpVersions = parseCsv(\n\t\targv['php-versions'] as string,\n\t\t'php-versions'\n\t);\n\tconst configArgs = splitShellWords((argv['config-args'] as string) || '');\n\tconst extraFilesSpecs = (argv['extra-files'] as string[]).map(\n\t\tparseExtraFilesSpec\n\t);\n\tconst outDir = path.resolve(workspaceRoot, argv['out'] as string);\n\tconst extraFiles = await stageExtraFilesIntoOutDir(\n\t\textraFilesSpecs,\n\t\toutDir,\n\t\tworkspaceRoot\n\t);\n\n\tconst result = await compileExtensionMatrix({\n\t\tworkspaceRoot,\n\t\tsourceDir,\n\t\toutDir: argv['out'] as string,\n\t\tname,\n\t\tphpVersions,\n\t\textraCflags: argv['extra-cflags'] as string | undefined,\n\t\textraLdflags: argv['extra-ldflags'] as string | undefined,\n\t\tconfigArgs,\n\t\toptimize: argv['optimize'] as string,\n\t\tjobs: argv['jobs'] as number | undefined,\n\t\textraFiles,\n\t});\n\n\tconsole.log(`Wrote ${result.artifacts.length} artifacts.`);\n\tconsole.log(`Wrote ${result.manifestPath}.`);\n}\n\nif (isCliEntrypoint(import.meta.url)) {\n\tmain().catch((error) => {\n\t\tconsole.error(error instanceof Error ? error.message : error);\n\t\tprocess.exit(1);\n\t});\n}\n\nexport function normalizeDashPrefixedOptionValues(args: string[]): string[] {\n\tconst normalized: string[] = [];\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\t\tif (\n\t\t\tOptionsWithDashPrefixedValues.has(arg) &&\n\t\t\ti + 1 < args.length &&\n\t\t\t!args[i + 1].startsWith(`${arg}=`)\n\t\t) {\n\t\t\tnormalized.push(`${arg}=${args[i + 1]}`);\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\t\tnormalized.push(arg);\n\t}\n\treturn normalized;\n}\n\nfunction parseCsv(value: string, name: string): string[] {\n\tconst values = value\n\t\t.split(',')\n\t\t.map((entry) => entry.trim())\n\t\t.filter(Boolean);\n\tif (values.length === 0) {\n\t\tthrow new Error(`--${name} must contain at least one value.`);\n\t}\n\treturn values;\n}\n\nexport function splitShellWords(value: string): string[] {\n\tconst words: string[] = [];\n\tlet current = '';\n\tlet quote: '\"' | \"'\" | null = null;\n\tlet escaping = false;\n\n\tfor (const character of value) {\n\t\tif (escaping) {\n\t\t\tcurrent += character;\n\t\t\tescaping = false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (character === '\\\\') {\n\t\t\tescaping = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (quote) {\n\t\t\tif (character === quote) {\n\t\t\t\tquote = null;\n\t\t\t} else {\n\t\t\t\tcurrent += character;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (character === '\"' || character === \"'\") {\n\t\t\tquote = character;\n\t\t\tcontinue;\n\t\t}\n\t\tif (/\\s/.test(character)) {\n\t\t\tif (current.length > 0) {\n\t\t\t\twords.push(current);\n\t\t\t\tcurrent = '';\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tcurrent += character;\n\t}\n\n\tif (escaping) {\n\t\tcurrent += '\\\\';\n\t}\n\tif (quote) {\n\t\tthrow new Error('Unterminated quote in --config-args.');\n\t}\n\tif (current.length > 0) {\n\t\twords.push(current);\n\t}\n\treturn words;\n}\n\nfunction findWorkspaceRoot(startDirectory: string): string {\n\tlet directory = path.resolve(startDirectory);\n\twhile (directory !== path.dirname(directory)) {\n\t\tif (\n\t\t\texistsSync(path.join(directory, 'packages/php-wasm/compile')) &&\n\t\t\texistsSync(path.join(directory, 'nx.json'))\n\t\t) {\n\t\t\treturn directory;\n\t\t}\n\t\tdirectory = path.dirname(directory);\n\t}\n\treturn process.cwd();\n}\n\nfunction isCliEntrypoint(metaUrl: string): boolean {\n\tconst entrypoint = process.argv[1];\n\tif (!entrypoint) {\n\t\treturn false;\n\t}\n\ttry {\n\t\treturn (\n\t\t\trealpathSync(entrypoint) === realpathSync(fileURLToPath(metaUrl))\n\t\t);\n\t} catch {\n\t\treturn path.resolve(entrypoint) === fileURLToPath(metaUrl);\n\t}\n}\n"],"names":["createDockerContext","workspaceRoot","phpWasmRoot","path","assertDockerIsAvailable","runCommand","error","buildBaseImage","context","buildExtensionImage","options","imageTag","getExtensionImageTag","runExtensionBuild","runArgs","arg","index","command","args","resolve","reject","child","spawn","code","ExtensionAsyncMode","createManifest","manifest","artifact","writeManifest","mkdir","manifestPath","writeFile","SupportedExtensionPHPVersions","PHP_RELEASE_BY_MINOR","compileExtensionMatrix","outDir","sourceDir","version","detectManifestVersion","matrix","phpVersion","artifacts","mapLimit","normalizeJobCount","asyncMode","phpRelease","resolvePHPRelease","artifactFile","packageJson","readFile","items","limit","worker","results","nextIndex","workers","jobs","taskCount","os","EXTENSION_NAME_PATTERNS","detectExtensionName","configPath","config","detected","detectExtensionNameFromConfig","pattern","match","parseExtraFilesSpec","spec","separator","hostDir","vfsRoot","stageExtraFilesIntoOutDir","specs","entry","nodes","claimedVfsPaths","claimedDestinations","absoluteHostDir","targetSubdir","destinationDir","cp","walk","entryPath","isDirectory","relativeUnderRoot","sourcePath","readdir","a","b","dir","visitor","entryStat","stat","OptionsWithDashPrefixedValues","main","hideBin","argv","yargs","normalizeDashPrefixedOptionValues","findWorkspaceRoot","name","phpVersions","parseCsv","configArgs","splitShellWords","extraFilesSpecs","extraFiles","result","isCliEntrypoint","normalized","i","value","values","words","current","quote","escaping","character","startDirectory","directory","existsSync","metaUrl","entrypoint","realpathSync","fileURLToPath"],"mappings":"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"}
|
package/compile.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ExtensionManifestExtraFiles } from './manifest';
|
|
1
2
|
export declare const SupportedExtensionPHPVersions: readonly ["8.5", "8.4", "8.3", "8.2", "8.1", "8.0", "7.4"];
|
|
2
3
|
export interface CompileExtensionOptions {
|
|
3
4
|
workspaceRoot: string;
|
|
@@ -10,12 +11,14 @@ export interface CompileExtensionOptions {
|
|
|
10
11
|
configArgs: string[];
|
|
11
12
|
optimize: string;
|
|
12
13
|
jobs?: number;
|
|
14
|
+
/** Sidecar files to record at the manifest level. */
|
|
15
|
+
extraFiles?: ExtensionManifestExtraFiles;
|
|
13
16
|
}
|
|
14
17
|
export declare function compileExtensionMatrix(options: CompileExtensionOptions): Promise<{
|
|
15
18
|
manifestPath: string;
|
|
16
19
|
artifacts: {
|
|
17
20
|
phpVersion: string;
|
|
18
|
-
|
|
21
|
+
sourcePath: string;
|
|
19
22
|
path: string;
|
|
20
23
|
}[];
|
|
21
24
|
manifest: import("./manifest").ExtensionManifest;
|
package/extra-files.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ExtensionManifestExtraFiles } from './manifest';
|
|
2
|
+
/**
|
|
3
|
+
* Parses a `--extra-files` CLI argument of the form `<hostDir>:<vfsRoot>`.
|
|
4
|
+
*
|
|
5
|
+
* `vfsRoot` must be an absolute VFS path so the loader can stage files
|
|
6
|
+
* without making up a default root. Splitting on the *last* colon lets
|
|
7
|
+
* Windows host paths like `C:\dir:/internal/root` round-trip cleanly.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseExtraFilesSpec(spec: string): {
|
|
10
|
+
hostDir: string;
|
|
11
|
+
vfsRoot: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Copies one or more host directories into `outDir` and returns a manifest
|
|
15
|
+
* `extraFiles` group whose `sourcePath` entries are relative to `outDir`.
|
|
16
|
+
*
|
|
17
|
+
* Each spec is `<hostDir>:<vfsRoot>`. All specs must agree on `vfsRoot`
|
|
18
|
+
* because the manifest format only stores a single `vfsRoot` per group.
|
|
19
|
+
*
|
|
20
|
+
* Files keep their relative path under `vfsRoot`. Empty directories are
|
|
21
|
+
* recorded as `type: 'directory'` nodes so the loader creates them.
|
|
22
|
+
*/
|
|
23
|
+
export declare function stageExtraFilesIntoOutDir(specs: Array<{
|
|
24
|
+
hostDir: string;
|
|
25
|
+
vfsRoot: string;
|
|
26
|
+
}>, outDir: string, workspaceRoot: string): Promise<ExtensionManifestExtraFiles | undefined>;
|
package/manifest.d.ts
CHANGED
|
@@ -1,25 +1,49 @@
|
|
|
1
1
|
export declare const ExtensionAsyncMode = "jspi";
|
|
2
2
|
export type AsyncMode = typeof ExtensionAsyncMode;
|
|
3
|
+
/**
|
|
4
|
+
* One sidecar file or empty directory staged alongside an artifact.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors `PHPExtensionManifestExtraFile` in `@php-wasm/universal`.
|
|
7
|
+
*/
|
|
8
|
+
export interface ExtensionManifestExtraFile {
|
|
9
|
+
/** Joined with the group's `vfsRoot` to form the final VFS path. */
|
|
10
|
+
vfsPath: string;
|
|
11
|
+
/** Defaults to "file". Only file nodes need a `sourcePath`. */
|
|
12
|
+
type?: 'file' | 'directory';
|
|
13
|
+
/** Relative to the manifest URL/base URL, or an absolute URL. */
|
|
14
|
+
sourcePath?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ExtensionManifestExtraFiles {
|
|
17
|
+
/** Absolute VFS prefix joined with each node's `vfsPath`. */
|
|
18
|
+
vfsRoot?: string;
|
|
19
|
+
nodes?: ExtensionManifestExtraFile[];
|
|
20
|
+
}
|
|
3
21
|
export interface ExtensionArtifact {
|
|
4
22
|
phpVersion: string;
|
|
5
|
-
file
|
|
6
|
-
|
|
23
|
+
/** Path to the `.so` file, relative to the manifest URL or absolute. */
|
|
24
|
+
sourcePath: string;
|
|
25
|
+
/** URL-backed files needed only by this artifact. */
|
|
26
|
+
extraFiles?: ExtensionManifestExtraFiles;
|
|
7
27
|
}
|
|
8
28
|
export interface ExtensionManifest {
|
|
9
29
|
name: string;
|
|
10
30
|
version: string;
|
|
11
31
|
artifacts: ExtensionArtifact[];
|
|
32
|
+
/** URL-backed files shared by every artifact in this manifest. */
|
|
33
|
+
extraFiles?: ExtensionManifestExtraFiles;
|
|
12
34
|
}
|
|
13
35
|
export interface BuiltArtifact {
|
|
14
36
|
phpVersion: string;
|
|
15
|
-
file
|
|
37
|
+
/** Path to the `.so` file, relative to the manifest URL. */
|
|
38
|
+
sourcePath: string;
|
|
39
|
+
/** Absolute path on disk where the `.so` was written. */
|
|
16
40
|
path: string;
|
|
17
41
|
}
|
|
18
|
-
export declare function sha256File(filePath: string): Promise<string>;
|
|
19
42
|
export declare function createManifest(options: {
|
|
20
43
|
name: string;
|
|
21
44
|
version: string;
|
|
22
45
|
artifacts: BuiltArtifact[];
|
|
46
|
+
extraFiles?: ExtensionManifestExtraFiles;
|
|
23
47
|
}): Promise<ExtensionManifest>;
|
|
24
48
|
export declare function writeManifest(options: {
|
|
25
49
|
outDir: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@php-wasm/compile-extension",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.27",
|
|
4
4
|
"description": "Build PHP.wasm extension side modules across PHP versions",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"php-wasm-compile-extension": "./cli.js"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
+
"ajv": "8.12.0",
|
|
24
25
|
"express": "4.22.0",
|
|
25
26
|
"fast-xml-parser": "^5.5.1",
|
|
26
27
|
"fs-ext-extra-prebuilt": "2.2.7",
|
|
@@ -29,14 +30,14 @@
|
|
|
29
30
|
"wasm-feature-detect": "1.8.0",
|
|
30
31
|
"ws": "8.18.0",
|
|
31
32
|
"yargs": "17.7.2",
|
|
32
|
-
"@php-wasm/node": "3.1.
|
|
33
|
-
"@php-wasm/universal": "3.1.
|
|
33
|
+
"@php-wasm/node": "3.1.27",
|
|
34
|
+
"@php-wasm/universal": "3.1.27"
|
|
34
35
|
},
|
|
35
36
|
"engines": {
|
|
36
37
|
"node": ">=20.10.0",
|
|
37
38
|
"npm": ">=10.2.3"
|
|
38
39
|
},
|
|
39
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "fd598fe315657fd276d7e14d5251c1127d495766",
|
|
40
41
|
"packageManager": "npm@10.9.2",
|
|
41
42
|
"overrides": {
|
|
42
43
|
"rollup": "^4.34.6",
|