@php-wasm/cli 0.1.2 → 0.1.7

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 DELETED
@@ -1,11 +0,0 @@
1
- # php-wasm-cli
2
-
3
- This library was generated with [Nx](https://nx.dev).
4
-
5
- ## Building
6
-
7
- Run `nx build php-wasm-cli` to build the library.
8
-
9
- ## Running unit tests
10
-
11
- Run `nx test php-wasm-cli` to execute the unit tests via [Jest](https://jestjs.io).
package/jest.config.ts DELETED
@@ -1,13 +0,0 @@
1
- /* eslint-disable */
2
- export default {
3
- displayName: 'nx-extensions',
4
- preset: '../../../jest.preset.js',
5
- transform: {
6
- '^.+\\.[tj]s$': [
7
- 'ts-jest',
8
- { tsconfig: '<rootDir>/tsconfig.spec.json' },
9
- ],
10
- },
11
- moduleFileExtensions: ['ts', 'js', 'html'],
12
- coverageDirectory: '../../../coverage/packages/nx-extensions',
13
- };
package/project.json DELETED
@@ -1,78 +0,0 @@
1
- {
2
- "name": "php-wasm-cli",
3
- "$schema": "../../../node_modules/nx/schemas/project-schema.json",
4
- "sourceRoot": "packages/php-wasm/cli/src",
5
- "projectType": "library",
6
- "targets": {
7
- "build": {
8
- "executor": "@wp-playground/nx-extensions:package-json",
9
- "options": {
10
- "tsConfig": "packages/php-wasm/cli/tsconfig.lib.json",
11
- "outputPath": "dist/packages/php-wasm/cli",
12
- "buildTarget": "php-wasm-cli:build:bundle:production"
13
- }
14
- },
15
- "build:bundle": {
16
- "executor": "@nrwl/vite:build",
17
- "outputs": ["{options.outputPath}"],
18
- "options": {
19
- "main": "dist/packages/php-wasm/cli/main.js",
20
- "outputPath": "dist/packages/php-wasm/cli"
21
- },
22
- "defaultConfiguration": "production",
23
- "configurations": {
24
- "development": {
25
- "minify": false
26
- },
27
- "production": {
28
- "minify": true
29
- }
30
- }
31
- },
32
- "start": {
33
- "executor": "@wp-playground/nx-extensions:built-script",
34
- "options": {
35
- "scriptPath": "dist/packages/php-wasm/cli/main.js"
36
- },
37
- "dependsOn": ["build", "^build-for-cli-run"]
38
- },
39
- "publish": {
40
- "executor": "nx:run-commands",
41
- "options": {
42
- "command": "node tools/scripts/publish.mjs php-wasm-cli {args.ver} {args.tag}"
43
- },
44
- "dependsOn": ["build"]
45
- },
46
- "lint": {
47
- "executor": "@nrwl/linter:eslint",
48
- "outputs": ["{options.outputFile}"],
49
- "options": {
50
- "lintFilePatterns": ["packages/php-wasm/cli/**/*.ts"]
51
- }
52
- },
53
- "test": {
54
- "executor": "@nrwl/jest:jest",
55
- "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
56
- "options": {
57
- "jestConfig": "packages/php-wasm/cli/jest.config.ts",
58
- "passWithNoTests": true
59
- },
60
- "configurations": {
61
- "ci": {
62
- "ci": true,
63
- "codeCoverage": true
64
- }
65
- }
66
- },
67
- "typecheck": {
68
- "executor": "@nrwl/workspace:run-commands",
69
- "options": {
70
- "commands": [
71
- "yarn tsc -p packages/php-wasm/cli/tsconfig.lib.json --noEmit",
72
- "yarn tsc -p packages/php-wasm/cli/tsconfig.spec.json --noEmit"
73
- ]
74
- }
75
- }
76
- },
77
- "tags": ["scope:php-wasm-public"]
78
- }
@@ -1,89 +0,0 @@
1
- import { createServer } from 'net';
2
- import { WebSocketServer, WebSocket } from 'ws';
3
- import { debugLog } from './utils.js';
4
- function log(...args: any[]) {
5
- debugLog('[TCP Server]', ...args);
6
- }
7
-
8
- export function addTCPServerToWebSocketServerClass(
9
- wsListenPort: number,
10
- WSServer: typeof WebSocketServer
11
- ): any {
12
- return class PHPWasmWebSocketServer extends WSServer {
13
- constructor(options: any, callback: any) {
14
- const requestedPort = options.port;
15
- options.port = wsListenPort;
16
- listenTCPToWSProxy({
17
- tcpListenPort: requestedPort,
18
- wsConnectPort: wsListenPort,
19
- });
20
- super(options, callback);
21
- }
22
- };
23
- }
24
-
25
- export interface InboundTcpToWsProxyOptions {
26
- tcpListenPort: number;
27
- wsConnectHost?: string;
28
- wsConnectPort: number;
29
- }
30
- export function listenTCPToWSProxy(options: InboundTcpToWsProxyOptions) {
31
- options = {
32
- wsConnectHost: '127.0.0.1',
33
- ...options,
34
- };
35
- const { tcpListenPort, wsConnectHost, wsConnectPort } = options;
36
- const server = createServer();
37
- server.on('connection', function handleConnection(tcpSource) {
38
- const inBuffer: Buffer[] = [];
39
-
40
- const wsTarget = new WebSocket(
41
- `ws://${wsConnectHost}:${wsConnectPort}/`
42
- );
43
- wsTarget.binaryType = 'arraybuffer';
44
- function wsSend(data: Buffer) {
45
- wsTarget.send(new Uint8Array(data));
46
- }
47
-
48
- wsTarget.addEventListener('open', function () {
49
- log('Outbound WebSocket connection established');
50
- while (inBuffer.length > 0) {
51
- wsSend(inBuffer.shift()!);
52
- }
53
- });
54
- wsTarget.addEventListener('message', (e) => {
55
- log(
56
- 'WS->TCP message:',
57
- new TextDecoder().decode(e.data as ArrayBuffer)
58
- );
59
- tcpSource.write(Buffer.from(e.data as ArrayBuffer));
60
- });
61
- wsTarget.addEventListener('close', () => {
62
- log('WebSocket connection closed');
63
- tcpSource.end();
64
- });
65
-
66
- tcpSource.on('data', function (data) {
67
- log('TCP->WS message:', data);
68
- if (wsTarget.readyState === WebSocket.OPEN) {
69
- while (inBuffer.length > 0) {
70
- wsSend(inBuffer.shift()!);
71
- }
72
- wsSend(data);
73
- } else {
74
- inBuffer.push(data);
75
- }
76
- });
77
- tcpSource.once('close', function () {
78
- log('TCP connection closed');
79
- wsTarget.close();
80
- });
81
- tcpSource.on('error', function () {
82
- log('TCP connection error');
83
- wsTarget.close();
84
- });
85
- });
86
- server.listen(tcpListenPort, function () {
87
- log('TCP server listening');
88
- });
89
- }
@@ -1,231 +0,0 @@
1
- /**
2
- * This is a simple TCP proxy server that allows PHP to connect to a remote
3
- * server via WebSockets. This is necessary because WebAssembly has no access
4
- * to the network.
5
- *
6
- * This module was forked from the @maximegris/node-websockify npm package.
7
- */
8
- 'use strict';
9
-
10
- import * as dns from 'dns';
11
- import * as util from 'util';
12
- import * as net from 'net';
13
- import * as http from 'http';
14
- import { WebSocketServer } from 'ws';
15
- import { debugLog } from './utils.js';
16
-
17
- function log(...args: any[]) {
18
- debugLog('[WS Server]', ...args);
19
- }
20
-
21
- const lookup = util.promisify(dns.lookup);
22
-
23
- function prependByte(
24
- chunk: string | ArrayBuffer | ArrayLike<number>,
25
- byte: number
26
- ) {
27
- if (typeof chunk === 'string') {
28
- chunk = String.fromCharCode(byte) + chunk;
29
- } else if (chunk instanceof ArrayBuffer) {
30
- const buffer = new Uint8Array(chunk.byteLength + 1);
31
- buffer[0] = byte;
32
- buffer.set(new Uint8Array(chunk), 1);
33
- chunk = buffer.buffer;
34
- } else {
35
- throw new Error('Unsupported chunk type');
36
- }
37
- return chunk;
38
- }
39
-
40
- /**
41
- * Send a chunk of data to the remote server.
42
- */
43
- export const COMMAND_CHUNK = 0x01;
44
- /**
45
- * Set a TCP socket option.
46
- */
47
- export const COMMAND_SET_SOCKETOPT = 0x02;
48
-
49
- /**
50
- * Adds support for TCP socket options to WebSocket class.
51
- *
52
- * Socket options are implemented by adopting a specific data transmission
53
- * protocol between WS client and WS server The first byte
54
- * of every message is a command type, and the remaining bytes
55
- * are the actual data.
56
- *
57
- * @param WebSocketConstructor
58
- * @returns Decorated constructor
59
- */
60
- export function addSocketOptionsSupportToWebSocketClass(
61
- WebSocketConstructor: typeof WebSocket
62
- ) {
63
- return class PHPWasmWebSocketConstructor extends WebSocketConstructor {
64
- override CONNECTING = 0;
65
- override OPEN = 1;
66
- override CLOSING = 2;
67
- override CLOSED = 3;
68
-
69
- // @ts-ignore
70
- send(chunk: any, callback: any) {
71
- return this.sendCommand(COMMAND_CHUNK, chunk, callback);
72
- }
73
-
74
- setSocketOpt(
75
- optionClass: number,
76
- optionName: number,
77
- optionValue: number
78
- ) {
79
- return this.sendCommand(
80
- COMMAND_SET_SOCKETOPT,
81
- new Uint8Array([optionClass, optionName, optionValue]).buffer,
82
- () => undefined
83
- );
84
- }
85
- sendCommand(
86
- commandType: number,
87
- chunk: string | ArrayBuffer | ArrayLike<number>,
88
- callback: any
89
- ) {
90
- return (WebSocketConstructor.prototype.send as any).call(
91
- this,
92
- prependByte(chunk, commandType),
93
- callback
94
- );
95
- }
96
- };
97
- }
98
-
99
- export function initOutboundWebsocketProxyServer(
100
- listenPort: number,
101
- listenHost = '127.0.0.1'
102
- ): Promise<http.Server> {
103
- log(`Binding the WebSockets server to ${listenHost}:${listenPort}...`);
104
- const webServer = http.createServer((request, response) => {
105
- response.writeHead(403, { 'Content-Type': 'text/plain' });
106
- response.write(
107
- '403 Permission Denied\nOnly websockets are allowed here.\n'
108
- );
109
- response.end();
110
- });
111
- return new Promise((resolve) => {
112
- webServer.listen(listenPort, listenHost, function () {
113
- const wsServer = new WebSocketServer({ server: webServer });
114
- wsServer.on('connection', onWsConnect);
115
- resolve(webServer);
116
- });
117
- });
118
- }
119
-
120
- // Handle new WebSocket client
121
- async function onWsConnect(client: any, request: http.IncomingMessage) {
122
- const clientAddr = client._socket.remoteAddress;
123
- const clientLog = function (...args: any[]) {
124
- log(' ' + clientAddr + ': ', ...args);
125
- };
126
-
127
- clientLog(
128
- 'WebSocket connection from : ' +
129
- clientAddr +
130
- ' at URL ' +
131
- (request ? request.url : client.upgradeReq.url)
132
- );
133
- clientLog(
134
- 'Version ' +
135
- client.protocolVersion +
136
- ', subprotocol: ' +
137
- client.protocol
138
- );
139
-
140
- // Parse the search params (the host doesn't matter):
141
- const reqUrl = new URL(`ws://0.0.0.0` + request.url);
142
- const reqTargetPort = Number(reqUrl.searchParams.get('port'));
143
- const reqTargetHost = reqUrl.searchParams.get('host');
144
- if (!reqTargetPort || !reqTargetHost) {
145
- clientLog('Missing host or port information');
146
- client.close(3000);
147
- return;
148
- }
149
-
150
- // eslint-disable-next-line prefer-const
151
- let target: any;
152
- const recvQueue: Buffer[] = [];
153
- function flushMessagesQueue() {
154
- while (recvQueue.length > 0) {
155
- const msg = recvQueue.pop()! as Buffer;
156
- const commandType = msg[0];
157
- clientLog('flushing', { commandType }, msg);
158
- if (commandType === COMMAND_CHUNK) {
159
- target.write(msg.slice(1));
160
- } else if (commandType === COMMAND_SET_SOCKETOPT) {
161
- const SOL_SOCKET = 1;
162
- const SO_KEEPALIVE = 9;
163
-
164
- const IPPROTO_TCP = 6;
165
- const TCP_NODELAY = 1;
166
- if (msg[1] === SOL_SOCKET && msg[2] === SO_KEEPALIVE) {
167
- target.setKeepAlive(msg[3]);
168
- } else if (msg[1] === IPPROTO_TCP && msg[2] === TCP_NODELAY) {
169
- target.setNoDelay(msg[3]);
170
- }
171
- } else {
172
- clientLog('Unknown command type: ' + commandType);
173
- process.exit();
174
- }
175
- }
176
- }
177
-
178
- client.on('message', function (msg: Buffer) {
179
- // clientLog('PHP -> network buffer:', msg);
180
- recvQueue.unshift(msg);
181
- if (target) {
182
- flushMessagesQueue();
183
- }
184
- });
185
- client.on('close', function (code: any, reason: any) {
186
- clientLog(
187
- 'WebSocket client disconnected: ' + code + ' [' + reason + ']'
188
- );
189
- target.end();
190
- } as any);
191
- client.on('error', function (a: string | Buffer) {
192
- clientLog('WebSocket client error: ' + a);
193
- target.end();
194
- });
195
-
196
- // Resolve the target host to an IP address if it isn't one already
197
- let reqTargetIp;
198
- if (net.isIP(reqTargetHost) === 0) {
199
- clientLog('resolving ' + reqTargetHost + '... ');
200
- const resolution = await lookup(reqTargetHost);
201
- reqTargetIp = resolution.address;
202
- clientLog('resolved ' + reqTargetHost + ' -> ' + reqTargetIp);
203
- } else {
204
- reqTargetIp = reqTargetHost;
205
- }
206
- clientLog(
207
- 'Opening a socket connection to ' + reqTargetIp + ':' + reqTargetPort
208
- );
209
- target = net.createConnection(reqTargetPort, reqTargetIp, function () {
210
- clientLog('Connected to target');
211
- flushMessagesQueue();
212
- });
213
- target.on('data', function (data: any) {
214
- // clientLog('network -> PHP buffer:', [...data.slice(0, 100)].join(', ') + '...');
215
- try {
216
- client.send(data);
217
- } catch (e) {
218
- clientLog('Client closed, cleaning up target');
219
- target.end();
220
- }
221
- });
222
- target.on('end', function () {
223
- clientLog('target disconnected');
224
- client.close();
225
- });
226
- target.on('error', function (e: any) {
227
- clientLog('target connection error', e);
228
- target.end();
229
- client.close(3000);
230
- });
231
- }
package/src/lib/utils.ts DELETED
@@ -1,33 +0,0 @@
1
- import * as net from 'net';
2
-
3
- export function debugLog(...args: any[]) {
4
- if (process.env['DEV']) {
5
- console.log(...args);
6
- }
7
- }
8
-
9
- export async function findFreePorts(n: number) {
10
- const serversPromises: Promise<net.Server>[] = [];
11
- for (let i = 0; i < n; i++) {
12
- serversPromises.push(listenOnRandomPort());
13
- }
14
-
15
- const servers = await Promise.all(serversPromises);
16
- const ports: number[] = [];
17
- for (const server of servers) {
18
- const address = server.address()! as net.AddressInfo;
19
- ports.push(address.port);
20
- server.close();
21
- }
22
-
23
- return ports;
24
- }
25
-
26
- function listenOnRandomPort(): Promise<net.Server> {
27
- return new Promise((resolve) => {
28
- const server = net.createServer();
29
- server.listen(0, () => {
30
- resolve(server);
31
- });
32
- });
33
- }
package/src/main.ts DELETED
@@ -1,74 +0,0 @@
1
- /**
2
- * A CLI script that runs PHP CLI via the WebAssembly build.
3
- */
4
- import { writeFileSync, existsSync } from 'fs';
5
- import { rootCertificates } from 'tls';
6
-
7
- import {
8
- initOutboundWebsocketProxyServer,
9
- addSocketOptionsSupportToWebSocketClass,
10
- } from './lib/outbound-ws-to-tcp-proxy.js';
11
- import { addTCPServerToWebSocketServerClass } from './lib/inbound-tcp-to-ws-proxy.js';
12
- import { findFreePorts } from './lib/utils.js';
13
- import { PHP, loadPHPRuntime, getPHPLoaderModule } from '@php-wasm/node';
14
-
15
- let args = process.argv.slice(2);
16
- if (!args.length) {
17
- args = ['--help'];
18
- }
19
-
20
- // Write the ca-bundle.crt file to disk so that PHP can find it.
21
- const caBundlePath = new URL('ca-bundle.crt', (import.meta || {}).url).pathname;
22
- if (!existsSync(caBundlePath)) {
23
- writeFileSync(caBundlePath, rootCertificates.join('\n'));
24
- }
25
-
26
- async function main() {
27
- // @ts-ignore
28
- const defaultPhpIniPath = await import('./lib/php.ini');
29
-
30
- const phpVersion = process.env['PHP'] || '8.2';
31
-
32
- const [inboundProxyWsServerPort, outboundProxyWsServerPort] =
33
- await findFreePorts(2);
34
-
35
- await initOutboundWebsocketProxyServer(outboundProxyWsServerPort);
36
-
37
- // This dynamic import only works after the build step
38
- // when the PHP files are present in the same directory
39
- // as this script.
40
- const phpLoaderModule = await getPHPLoaderModule(phpVersion);
41
- const loaderId = await loadPHPRuntime(phpLoaderModule, {
42
- ENV: {
43
- ...process.env,
44
- TERM: 'xterm',
45
- },
46
- websocket: {
47
- url: (_: any, host: string, port: string) => {
48
- const query = new URLSearchParams({ host, port }).toString();
49
- return `ws://127.0.0.1:${outboundProxyWsServerPort}/?${query}`;
50
- },
51
- subprotocol: 'binary',
52
- decorator: addSocketOptionsSupportToWebSocketClass,
53
- serverDecorator: addTCPServerToWebSocketServerClass.bind(
54
- null,
55
- inboundProxyWsServerPort
56
- ),
57
- },
58
- });
59
- const hasMinusCOption = args.some((arg) => arg.startsWith('-c'));
60
- if (!hasMinusCOption) {
61
- args.unshift('-c', defaultPhpIniPath);
62
- }
63
- const php = new PHP(loaderId);
64
- php.writeFile(caBundlePath, rootCertificates.join('\n'));
65
- args.unshift('-d', `openssl.cafile=${caBundlePath}`);
66
- php.cli(['php', ...args]).catch((result) => {
67
- if (result.name === 'ExitStatus') {
68
- process.exit(result.status === undefined ? 1 : result.status);
69
- }
70
- throw result;
71
- });
72
- }
73
-
74
- main();
package/tsconfig.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "forceConsistentCasingInFileNames": true,
5
- "strict": true,
6
- "noImplicitOverride": true,
7
- "noPropertyAccessFromIndexSignature": true,
8
- "noImplicitReturns": true,
9
- "noFallthroughCasesInSwitch": true
10
- },
11
- "files": [],
12
- "include": [],
13
- "references": [
14
- {
15
- "path": "./tsconfig.lib.json"
16
- },
17
- {
18
- "path": "./tsconfig.spec.json"
19
- }
20
- ]
21
- }
package/tsconfig.lib.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "../../../dist/out-tsc",
5
- "declaration": true,
6
- "types": ["node"]
7
- },
8
- "include": ["src/**/*.ts"],
9
- "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
10
- }
@@ -1,14 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "../../../dist/out-tsc",
5
- "module": "commonjs",
6
- "types": ["jest", "node"]
7
- },
8
- "include": [
9
- "jest.config.ts",
10
- "src/**/*.test.ts",
11
- "src/**/*.spec.ts",
12
- "src/**/*.d.ts"
13
- ]
14
- }
package/vite.config.ts DELETED
@@ -1,50 +0,0 @@
1
- /// <reference types="vitest" />
2
- import { defineConfig } from 'vite';
3
- import viteTsConfigPaths from 'vite-tsconfig-paths';
4
-
5
- export default defineConfig(() => {
6
- return {
7
- assetsInclude: ['**/*.ini'],
8
- cacheDir: '../../../node_modules/.vite/php-cli',
9
-
10
- plugins: [
11
- viteTsConfigPaths({
12
- root: '../../../',
13
- }),
14
- ],
15
-
16
- // Configuration for building your library.
17
- // See: https://vitejs.dev/guide/build.html#library-mode
18
- build: {
19
- assetsInlineLimit: 0,
20
- target: 'es2020',
21
- rollupOptions: {
22
- external: [
23
- '@php-wasm/node',
24
- 'net',
25
- 'fs',
26
- 'path',
27
- 'http',
28
- 'tls',
29
- 'util',
30
- 'dns',
31
- 'ws',
32
- ],
33
- input: 'packages/php-wasm/cli/src/main.ts',
34
- output: {
35
- format: 'esm',
36
- entryFileNames: '[name].js',
37
- },
38
- },
39
- },
40
-
41
- test: {
42
- globals: true,
43
- cache: {
44
- dir: '../../../node_modules/.vitest',
45
- },
46
- environment: 'jsdom',
47
- include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
48
- },
49
- };
50
- });
File without changes
File without changes