parent
fcf478d1f6
commit
f113d8e1ea
@ -1,10 +1,24 @@
|
||||
.PHONY: install build test-unit
|
||||
.PHONY: install build test publish canary
|
||||
|
||||
install:
|
||||
npx lerna exec npm install
|
||||
npm install
|
||||
|
||||
build:
|
||||
npx lerna exec npm run build
|
||||
npm run build
|
||||
|
||||
test-unit:
|
||||
npm run test-unit
|
||||
build-watch:
|
||||
npm run watch
|
||||
|
||||
test:
|
||||
npm run test
|
||||
|
||||
test-watch:
|
||||
npm run test-watch
|
||||
|
||||
publish:
|
||||
rm -rf ./dist
|
||||
npm publish --access public --tag latest
|
||||
|
||||
canary:
|
||||
rm -rf ./dist
|
||||
npm publish --access public --tag canary
|
||||
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"version": "0.0.0",
|
||||
"npmClient": "npm",
|
||||
"hoist": true,
|
||||
"packages": [
|
||||
"packages/*"
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,18 +1,42 @@
|
||||
{
|
||||
"name": "vercel-php-monorepo",
|
||||
"name": "vercel-php",
|
||||
"description": "Vercel PHP runtime",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"homepage": "https://github.com/juicyfx/vercel-php",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/juicyfx/vercel-php.git"
|
||||
},
|
||||
"keywords": [
|
||||
"vercel",
|
||||
"zeit",
|
||||
"now",
|
||||
"php",
|
||||
"builder",
|
||||
"runtime",
|
||||
"serverless",
|
||||
"deployment"
|
||||
],
|
||||
"scripts": {
|
||||
"test-unit": "jest --config unit.jest.config.js"
|
||||
"watch": "tsc --watch",
|
||||
"build": "tsc",
|
||||
"test": "jest",
|
||||
"test-watch": "jest --watch",
|
||||
"prepublishOnly": "tsc"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@libphp/amazon-linux-2-v74": "^0.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@now/build-utils": "^2.2.0",
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/node": "^13.9.5",
|
||||
"async-retry": "^1.3.1",
|
||||
"buffer-replace": "^1.0.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"@vercel/build-utils": "^2.4.0",
|
||||
"@types/glob": "^7.1.2",
|
||||
"@types/node": "^12.12.47",
|
||||
"jest": "^26.0.1",
|
||||
"lerna": "^3.20.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
"typescript": "^3.8.3"
|
||||
"typescript": "^3.9.5"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
# TS
|
||||
/dist
|
||||
|
||||
# Node
|
||||
/node_modules
|
@ -1,16 +0,0 @@
|
||||
publish:
|
||||
rm -rf ./dist
|
||||
npm publish --access public --tag latest
|
||||
|
||||
publish-canary:
|
||||
rm -rf ./dist
|
||||
npm version --no-git-tag-version prerelease
|
||||
npm publish --access public --tag canary
|
||||
|
||||
publish-experimental:
|
||||
rm -rf ./dist
|
||||
npm version --no-git-tag-version prerelease
|
||||
npm publish --access public --tag experimental
|
||||
|
||||
test:
|
||||
yarn test
|
@ -1,9 +0,0 @@
|
||||
0.0.0.0:8000
|
||||
|
||||
# HTTPS
|
||||
tls off
|
||||
|
||||
# PHP
|
||||
fastcgi / 127.0.0.1:9000 php {
|
||||
root /var/task/user
|
||||
}
|
Binary file not shown.
@ -1,31 +0,0 @@
|
||||
{
|
||||
"name": "@now-php/caddy",
|
||||
"description": "Caddy is part of now-php",
|
||||
"version": "0.0.2-canary.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"homepage": "https://github.com/juicyfx/now-php",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/juicyfx/now-php.git"
|
||||
},
|
||||
"scripts": {
|
||||
"watch": "tsc --watch",
|
||||
"build": "tsc",
|
||||
"prepublishOnly": "tsc"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"native"
|
||||
],
|
||||
"dependencies": {
|
||||
"now-php": "0.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@now/build-utils": "^0.9.4",
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/node": "^10.0.0",
|
||||
"jest": "^26.0.1",
|
||||
"typescript": "^3.5.3"
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
import { parse as urlParse } from 'url';
|
||||
import { join as pathJoin } from 'path';
|
||||
|
||||
export const getUserDir = (): string => pathJoin(process.env.LAMBDA_TASK_ROOT || '/', 'user');
|
||||
export const getPhpDir = (): string => pathJoin(process.env.LAMBDA_TASK_ROOT || '/', 'php');
|
||||
export const isDev = (): boolean => process.env.NOW_PHP_DEV === '1';
|
||||
|
||||
export function normalizeEvent(event: Event): AwsRequest {
|
||||
if (event.Action === 'Invoke') {
|
||||
const invokeEvent = JSON.parse(event.body);
|
||||
|
||||
const {
|
||||
method, path, host, headers = {}, encoding,
|
||||
}: InvokedEvent = invokeEvent;
|
||||
|
||||
let { body } = invokeEvent;
|
||||
|
||||
if (body) {
|
||||
if (encoding === 'base64') {
|
||||
body = Buffer.from(body, encoding);
|
||||
} else if (encoding === undefined) {
|
||||
body = Buffer.from(body);
|
||||
} else {
|
||||
throw new Error(`Unsupported encoding: ${encoding}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
method,
|
||||
path,
|
||||
host,
|
||||
headers,
|
||||
body,
|
||||
};
|
||||
}
|
||||
|
||||
const {
|
||||
httpMethod: method, path, host, headers = {}, body,
|
||||
} = event;
|
||||
|
||||
return {
|
||||
method,
|
||||
path,
|
||||
host,
|
||||
headers,
|
||||
body,
|
||||
};
|
||||
}
|
||||
|
||||
export async function transformFromAwsRequest({
|
||||
method, path, host, headers, body,
|
||||
}: AwsRequest): Promise<PhpInput> {
|
||||
const { pathname, search } = urlParse(path);
|
||||
|
||||
const filename = pathJoin(
|
||||
getUserDir(),
|
||||
process.env.NOW_ENTRYPOINT || pathname || '',
|
||||
);
|
||||
|
||||
const uri = pathname + (search || '');
|
||||
|
||||
return { filename, path, uri, host, method, headers, body };
|
||||
}
|
||||
|
||||
export function transformToAwsResponse({ statusCode, headers, body }: PhpOutput): AwsResponse {
|
||||
return { statusCode, headers, body: body.toString('base64'), encoding: 'base64' };
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
import path from "path";
|
||||
import {
|
||||
createLambda,
|
||||
shouldServe,
|
||||
rename,
|
||||
FileFsRef,
|
||||
BuildOptions,
|
||||
FileBlob
|
||||
} from '@now/build-utils';
|
||||
|
||||
import {
|
||||
getPhpLibFiles,
|
||||
getIncludedFiles
|
||||
} from 'now-php/dist/utils';
|
||||
|
||||
// ###########################
|
||||
// EXPORTS
|
||||
// ###########################
|
||||
|
||||
export const version = 3;
|
||||
|
||||
export async function build({
|
||||
files,
|
||||
entrypoint,
|
||||
workPath,
|
||||
config = {},
|
||||
meta = {},
|
||||
}: BuildOptions) {
|
||||
const includedFiles = await getIncludedFiles({ files, entrypoint, workPath, config, meta });
|
||||
|
||||
const userFiles = rename(includedFiles, name => path.join('user', name));
|
||||
const bridgeFiles: Files = {
|
||||
...await getPhpLibFiles(),
|
||||
...{
|
||||
'launcher.js': new FileFsRef({
|
||||
fsPath: path.join(__dirname, 'launcher.js'),
|
||||
}),
|
||||
'helpers.js': new FileFsRef({
|
||||
fsPath: path.join(__dirname, 'helpers.js'),
|
||||
}),
|
||||
'caddy': new FileFsRef({
|
||||
mode: 0o755,
|
||||
fsPath: path.join(__dirname, '../native/caddy'),
|
||||
}),
|
||||
'Caddyfile': new FileFsRef({
|
||||
fsPath: path.join(__dirname, '../native/Caddyfile'),
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
||||
if (process.env.NOW_PHP_DEBUG === '1') {
|
||||
console.log('🐘 Entrypoint:', entrypoint);
|
||||
console.log('🐘 Config:', config);
|
||||
console.log('🐘 Work path:', workPath);
|
||||
console.log('🐘 Meta:', meta);
|
||||
console.log('🐘 User files:', Object.keys(userFiles));
|
||||
console.log('🐘 Bridge files:', Object.keys(bridgeFiles));
|
||||
console.log('🐘 PHP: php.ini', (bridgeFiles['php/php.ini'] as FileBlob).data.toString());
|
||||
}
|
||||
|
||||
const lambda = await createLambda({
|
||||
files: { ...userFiles, ...bridgeFiles },
|
||||
handler: 'launcher.launcher',
|
||||
runtime: 'nodejs12.x',
|
||||
environment: {
|
||||
NOW_ENTRYPOINT: entrypoint,
|
||||
NOW_PHP_DEV: meta.isDev ? '1' : '0'
|
||||
},
|
||||
});
|
||||
|
||||
return { output: lambda };
|
||||
};
|
||||
|
||||
export { shouldServe };
|
@ -1,177 +0,0 @@
|
||||
import { normalizeEvent, transformFromAwsRequest, transformToAwsResponse, isDev, getPhpDir, getUserDir } from "./helpers";
|
||||
import { ChildProcess, spawn, SpawnOptions } from "child_process";
|
||||
import http from 'http';
|
||||
import net from 'net';
|
||||
|
||||
let connFpm: ChildProcess;
|
||||
let connCaddy: ChildProcess;
|
||||
|
||||
async function startFpm() {
|
||||
if (connFpm) {
|
||||
console.log(`🐘 PHP FPM is already running`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`🐘 Spawning: PHP-FPM`);
|
||||
|
||||
// php spawn options
|
||||
const options: SpawnOptions = {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: process.env
|
||||
};
|
||||
|
||||
// now vs now-dev
|
||||
if (!isDev()) {
|
||||
options.env!.PATH = `${getPhpDir()}:${process.env.PATH}`;
|
||||
options.cwd = getPhpDir();
|
||||
} else {
|
||||
options.cwd = getUserDir();
|
||||
}
|
||||
|
||||
const fpm = spawn(
|
||||
'php-fpm',
|
||||
['-c', 'php.ini', '--fpm-config', 'php-fpm.ini', '--nodaemonize'],
|
||||
options
|
||||
);
|
||||
|
||||
fpm.on('close', function (code, signal) {
|
||||
console.log(`🐘 PHP-FPM process closed code ${code} and signal ${signal}`);
|
||||
});
|
||||
|
||||
fpm.on('error', function (err) {
|
||||
console.error(`🐘 PHP-FPM process errored ${err}`);
|
||||
});
|
||||
|
||||
process.on('exit', () => {
|
||||
fpm.kill();
|
||||
})
|
||||
|
||||
await whenPortOpens(9000, 400);
|
||||
|
||||
connFpm = fpm;
|
||||
}
|
||||
|
||||
async function startCaddy() {
|
||||
if (connCaddy) {
|
||||
console.log(`🚀 Caddy is already running`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`🚀 Spawning Caddy`);
|
||||
|
||||
const caddy = spawn(
|
||||
'./caddy',
|
||||
['-conf', 'Caddyfile', '-root', '/var/task/user'],
|
||||
{
|
||||
stdio: 'inherit',
|
||||
cwd: '/var/task',
|
||||
},
|
||||
);
|
||||
|
||||
caddy.on('close', function (code, signal) {
|
||||
console.log(`🚀 Caddy process closed code ${code} and signal ${signal}`);
|
||||
});
|
||||
|
||||
caddy.on('error', function (err) {
|
||||
console.error(`🚀 Caddy process errored ${err}`);
|
||||
});
|
||||
|
||||
process.on('exit', () => {
|
||||
caddy.kill();
|
||||
})
|
||||
|
||||
await whenPortOpens(8000, 400);
|
||||
|
||||
connCaddy = caddy;
|
||||
}
|
||||
|
||||
function whenPortOpensCallback(port: number, attempts: number, cb: (error?: string) => void) {
|
||||
const client = net.connect(port, '127.0.0.1');
|
||||
client.on('error', (error: string) => {
|
||||
if (!attempts) return cb(error);
|
||||
setTimeout(() => {
|
||||
whenPortOpensCallback(port, attempts - 1, cb);
|
||||
}, 50);
|
||||
});
|
||||
client.on('connect', () => {
|
||||
client.destroy();
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
function whenPortOpens(port: number, attempts: number): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
whenPortOpensCallback(port, attempts, (error?: string) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function query({ uri, headers, method, body }: PhpInput): Promise<PhpOutput> {
|
||||
if (!connFpm || !connCaddy) {
|
||||
await Promise.all([
|
||||
startFpm(),
|
||||
startCaddy()
|
||||
]);
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
const options = {
|
||||
hostname: '127.0.0.1',
|
||||
port: 8000,
|
||||
path: `${uri}`,
|
||||
method,
|
||||
headers,
|
||||
};
|
||||
|
||||
const req = http.request(options, (res) => {
|
||||
const chunks: Uint8Array[] = [];
|
||||
|
||||
res.on('data', (data) => {
|
||||
chunks.push(data);
|
||||
});
|
||||
res.on('end', () => {
|
||||
resolve({
|
||||
statusCode: res.statusCode || 200,
|
||||
headers: res.headers,
|
||||
body: Buffer.concat(chunks)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
console.error('🚀 Caddy HTTP errored', error);
|
||||
resolve({
|
||||
body: Buffer.from(`🚀 Caddy HTTP error: ${error}`),
|
||||
headers: {},
|
||||
statusCode: 500
|
||||
});
|
||||
});
|
||||
|
||||
if (body) {
|
||||
req.write(body);
|
||||
}
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function launcher(event: Event): Promise<AwsResponse> {
|
||||
const awsRequest = normalizeEvent(event);
|
||||
const input = await transformFromAwsRequest(awsRequest);
|
||||
const output = await query(input);
|
||||
return transformToAwsResponse(output);
|
||||
}
|
||||
|
||||
exports.launcher = launcher;
|
||||
|
||||
// (async function() {
|
||||
// console.log(await launcher({
|
||||
// httpMethod: 'GET',
|
||||
// path: '/index.php'
|
||||
// }));
|
||||
// })();
|
@ -1,62 +0,0 @@
|
||||
type Headers = { [k: string]: string | string[] | undefined };
|
||||
|
||||
interface Files {
|
||||
[filePath: string]: import('@now/build-utils').File;
|
||||
}
|
||||
|
||||
interface MetaOptions {
|
||||
meta: import('@now/build-utils').Meta;
|
||||
}
|
||||
|
||||
interface PhpIni {
|
||||
[k: string]: any,
|
||||
}
|
||||
|
||||
interface AwsRequest {
|
||||
method: string,
|
||||
path: string,
|
||||
host: string,
|
||||
headers: Headers,
|
||||
body: string,
|
||||
}
|
||||
|
||||
interface AwsResponse {
|
||||
statusCode: number,
|
||||
headers: Headers,
|
||||
body: string,
|
||||
encoding?: string
|
||||
}
|
||||
|
||||
interface Event {
|
||||
Action: string,
|
||||
body: string,
|
||||
httpMethod: string,
|
||||
path: string,
|
||||
host: string,
|
||||
headers: Headers,
|
||||
encoding: string | undefined | null,
|
||||
}
|
||||
|
||||
interface InvokedEvent {
|
||||
method: string,
|
||||
path: string,
|
||||
host: string,
|
||||
headers: Headers,
|
||||
encoding: string | undefined | null,
|
||||
}
|
||||
|
||||
interface PhpInput {
|
||||
filename: string,
|
||||
path: string,
|
||||
uri: string,
|
||||
host: string,
|
||||
method: string,
|
||||
headers: Headers,
|
||||
body: string,
|
||||
}
|
||||
|
||||
interface PhpOutput {
|
||||
statusCode: number,
|
||||
headers: Headers,
|
||||
body: Buffer,
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
const helpers = require('../../dist/helpers');
|
||||
|
||||
test('transform to AWS response', () => {
|
||||
const response = helpers.transformToAwsResponse({
|
||||
statusCode: 200,
|
||||
headers: [],
|
||||
body: "foo"
|
||||
});
|
||||
|
||||
expect(response.body).toBe("foo");
|
||||
});
|
@ -1,25 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": [
|
||||
"esnext"
|
||||
],
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"outDir": "dist",
|
||||
"sourceMap": false,
|
||||
"declaration": true,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules",
|
||||
"test"
|
||||
]
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
# TS
|
||||
/dist
|
||||
|
||||
# Node
|
||||
/node_modules
|
@ -1,12 +0,0 @@
|
||||
.PHONY: test
|
||||
|
||||
publish:
|
||||
rm -rf ./dist
|
||||
npm publish --access public --tag latest
|
||||
|
||||
publish-canary:
|
||||
rm -rf ./dist
|
||||
npm publish --access public --tag canary
|
||||
|
||||
test:
|
||||
yarn test
|
@ -1,40 +0,0 @@
|
||||
{
|
||||
"name": "vercel-php",
|
||||
"description": "Vercel PHP runtime",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"homepage": "https://github.com/juicyfx/vercel-php",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/juicyfx/vercel-php.git"
|
||||
},
|
||||
"keywords": [
|
||||
"vercel",
|
||||
"zeit",
|
||||
"now",
|
||||
"php",
|
||||
"builder",
|
||||
"runtime",
|
||||
"serverless",
|
||||
"deployment"
|
||||
],
|
||||
"scripts": {
|
||||
"watch": "tsc --watch",
|
||||
"build": "tsc",
|
||||
"prepublishOnly": "tsc"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@libphp/amazon-linux-2-v74": "^0.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vercel/build-utils": "^2.4.0",
|
||||
"@types/glob": "^7.1.2",
|
||||
"@types/node": "^12.12.47",
|
||||
"jest": "^26.0.1",
|
||||
"typescript": "^3.9.5"
|
||||
}
|
||||
}
|
Loading…
Reference in new issue