From 681fac24f955193290cc93784757c45a35b9dfa4 Mon Sep 17 00:00:00 2001 From: GamerCleanVic Date: Mon, 29 Apr 2024 08:20:10 -0300 Subject: [PATCH] public build dir --- public/index.d.ts | 5 + public/index.js | 114 +++++++++++++++++++++ public/launchers/builtin.d.ts | 1 + public/launchers/builtin.js | 148 +++++++++++++++++++++++++++ public/launchers/cgi.d.ts | 1 + public/launchers/cgi.js | 170 +++++++++++++++++++++++++++++++ public/launchers/cli.d.ts | 1 + public/launchers/cli.js | 90 ++++++++++++++++ public/launchers/helpers.d.ts | 6 ++ public/launchers/helpers.js | 57 +++++++++++ public/utils.d.ts | 8 ++ public/utils.js | 186 ++++++++++++++++++++++++++++++++++ tsconfig.json | 5 +- 13 files changed, 789 insertions(+), 3 deletions(-) create mode 100644 public/index.d.ts create mode 100644 public/index.js create mode 100644 public/launchers/builtin.d.ts create mode 100644 public/launchers/builtin.js create mode 100644 public/launchers/cgi.d.ts create mode 100644 public/launchers/cgi.js create mode 100644 public/launchers/cli.d.ts create mode 100644 public/launchers/cli.js create mode 100644 public/launchers/helpers.d.ts create mode 100644 public/launchers/helpers.js create mode 100644 public/utils.d.ts create mode 100644 public/utils.js diff --git a/public/index.d.ts b/public/index.d.ts new file mode 100644 index 0000000..aadb970 --- /dev/null +++ b/public/index.d.ts @@ -0,0 +1,5 @@ +import { shouldServe, BuildV3, PrepareCache } from '@vercel/build-utils'; +export declare const version = 3; +export declare const build: BuildV3; +export declare const prepareCache: PrepareCache; +export { shouldServe }; diff --git a/public/index.js b/public/index.js new file mode 100644 index 0000000..537483e --- /dev/null +++ b/public/index.js @@ -0,0 +1,114 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.shouldServe = exports.prepareCache = exports.build = exports.version = void 0; +const path_1 = __importDefault(require("path")); +const build_utils_1 = require("@vercel/build-utils"); +Object.defineProperty(exports, "shouldServe", { enumerable: true, get: function () { return build_utils_1.shouldServe; } }); +const utils_1 = require("./utils"); +const COMPOSER_FILE = process.env.COMPOSER || 'composer.json'; +// ########################### +// EXPORTS +// ########################### +exports.version = 3; +const build = async ({ files, entrypoint, workPath, config = {}, meta = {}, }) => { + // Check if now dev mode is used + if (meta.isDev) { + console.log(` + 🐘 vercel dev is not supported right now. + Please use PHP built-in development server. + + php -S localhost:8000 api/index.php + `); + process.exit(255); + } + console.log('🐘 Downloading user files'); + // Collect user provided files + const userFiles = await (0, build_utils_1.download)(files, workPath, meta); + console.log('🐘 Downloading PHP runtime files'); + // Collect runtime files containing PHP bins and libs + const runtimeFiles = { + // Append PHP files (bins + shared object) + ...await (0, utils_1.getPhpFiles)(), + // Append launcher files (builtin server, common helpers) + ...(0, utils_1.getLauncherFiles)(), + }; + // If composer.json is provided try to + // - install deps + // - run composer scripts + if (userFiles[COMPOSER_FILE]) { + // Install dependencies (vendor is collected bellow, see harvestedFiles) + await (0, utils_1.runComposerInstall)(workPath); + // Run composer scripts (created files are collected bellow, , see harvestedFiles) + await (0, utils_1.runComposerScripts)(userFiles[COMPOSER_FILE], workPath); + } + // Append PHP directives into php.ini + if (userFiles['api/php.ini']) { + const phpini = await (0, utils_1.modifyPhpIni)(userFiles, runtimeFiles); + if (phpini) { + runtimeFiles['php/php.ini'] = phpini; + } + } + // Collect user files, files creating during build (composer vendor) + // and other files and prefix them with "user" (/var/task/user folder). + const harverstedFiles = (0, build_utils_1.rename)(await (0, build_utils_1.glob)('**', { + cwd: workPath, + ignore: [ + '.vercel/**', + ...((config === null || config === void 0 ? void 0 : config.excludeFiles) + ? Array.isArray(config.excludeFiles) + ? config.excludeFiles + : [config.excludeFiles] + : [ + 'node_modules/**', + 'now.json', + '.nowignore', + 'vercel.json', + '.vercelignore', + ]), + ], + }), name => path_1.default.join('user', name)); + // Show some debug notes during build + 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(harverstedFiles)); + console.log('🐘 Runtime files:', Object.keys(runtimeFiles)); + console.log('🐘 PHP: php.ini', await (0, utils_1.readRuntimeFile)(runtimeFiles['php/php.ini'])); + } + console.log('🐘 Creating lambda'); + const nodeVersion = await (0, build_utils_1.getNodeVersion)(workPath); + const lambda = new build_utils_1.Lambda({ + files: { + // Located at /var/task/user + ...harverstedFiles, + // Located at /var/task/php (php bins + ini + modules) + // Located at /var/task/lib (shared libs) + ...runtimeFiles + }, + handler: 'launcher.launcher', + runtime: nodeVersion.runtime, + environment: { + NOW_ENTRYPOINT: entrypoint, + NOW_PHP_DEV: meta.isDev ? '1' : '0' + }, + }); + return { output: lambda }; +}; +exports.build = build; +const prepareCache = async ({ workPath }) => { + return { + // Composer + ...(await (0, build_utils_1.glob)('vendor/**', workPath)), + ...(await (0, build_utils_1.glob)('composer.lock', workPath)), + // NPM + ...(await (0, build_utils_1.glob)('node_modules/**', workPath)), + ...(await (0, build_utils_1.glob)('package-lock.json', workPath)), + ...(await (0, build_utils_1.glob)('yarn.lock', workPath)), + }; +}; +exports.prepareCache = prepareCache; diff --git a/public/launchers/builtin.d.ts b/public/launchers/builtin.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/public/launchers/builtin.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/launchers/builtin.js b/public/launchers/builtin.js new file mode 100644 index 0000000..92071e2 --- /dev/null +++ b/public/launchers/builtin.js @@ -0,0 +1,148 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const http_1 = __importDefault(require("http")); +const child_process_1 = require("child_process"); +const net_1 = __importDefault(require("net")); +const helpers_1 = require("./helpers"); +const path_1 = require("path"); +let server; +async function startServer(entrypoint) { + var _a, _b, _c; + // Resolve document root and router + const router = entrypoint; + const docroot = (0, path_1.join)((0, helpers_1.getUserDir)(), (_a = process.env.VERCEL_PHP_DOCROOT) !== null && _a !== void 0 ? _a : ''); + console.log(`🐘 Spawning: PHP Built-In Server at ${docroot} (document root) and ${router} (router)`); + // php spawn options + const options = { + stdio: ['pipe', 'pipe', 'pipe'], + env: { + ...process.env, + LD_LIBRARY_PATH: `/var/task/lib:${process.env.LD_LIBRARY_PATH}` + } + }; + // now vs now-dev + if (!(0, helpers_1.isDev)()) { + options.env.PATH = `${(0, helpers_1.getPhpDir)()}:${process.env.PATH}`; + options.cwd = (0, helpers_1.getPhpDir)(); + } + else { + options.cwd = (0, helpers_1.getUserDir)(); + } + // We need to start PHP built-in server with following setup: + // php -c php.ini -S ip:port -t /var/task/user /var/task/user/foo/bar.php + // + // Path to document root lambda task folder with user prefix, because we move all + // user files to this folder. + // + // Path to router is absolute path, because CWD is different. + // + server = (0, child_process_1.spawn)('php', ['-c', 'php.ini', '-S', '127.0.0.1:8000', '-t', docroot, router], options); + (_b = server.stdout) === null || _b === void 0 ? void 0 : _b.on('data', data => { + console.log(`🐘STDOUT: ${data.toString()}`); + }); + (_c = server.stderr) === null || _c === void 0 ? void 0 : _c.on('data', data => { + console.error(`🐘STDERR: ${data.toString()}`); + }); + server.on('close', function (code, signal) { + console.log(`🐘 PHP Built-In Server process closed code ${code} and signal ${signal}`); + }); + server.on('error', function (err) { + console.error(`🐘 PHP Built-In Server process errored ${err}`); + }); + await whenPortOpens(8000, 500); + process.on('exit', () => { + server.kill(); + }); + return server; +} +async function query({ entrypoint, uri, path, headers, method, body }) { + if (!server) { + await startServer(entrypoint); + } + return new Promise(resolve => { + const options = { + hostname: '127.0.0.1', + port: 8000, + path, + method, + headers, + }; + console.log(`🐘 Accessing ${uri}`); + console.log(`🐘 Querying ${path}`); + const req = http_1.default.request(options, (res) => { + const chunks = []; + 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('🐘 PHP Built-In Server HTTP errored', error); + resolve({ + body: Buffer.from(`PHP Built-In Server HTTP error: ${error}`), + headers: {}, + statusCode: 500 + }); + }); + if (body) { + req.write(body); + } + req.end(); + }); +} +function whenPortOpensCallback(port, attempts, cb) { + const client = net_1.default.connect(port, '127.0.0.1'); + client.on('error', (error) => { + if (!attempts) + return cb(error); + setTimeout(() => { + whenPortOpensCallback(port, attempts - 1, cb); + }, 10); + }); + client.on('connect', () => { + client.destroy(); + cb(); + }); +} +function whenPortOpens(port, attempts) { + return new Promise((resolve, reject) => { + whenPortOpensCallback(port, attempts, (error) => { + if (error) { + reject(error); + } + else { + resolve(); + } + }); + }); +} +async function launcher(event) { + const awsRequest = (0, helpers_1.normalizeEvent)(event); + const input = await (0, helpers_1.transformFromAwsRequest)(awsRequest); + const output = await query(input); + return (0, helpers_1.transformToAwsResponse)(output); +} +exports.launcher = launcher; +// (async function () { +// const response = await launcher({ +// Action: "test", +// httpMethod: "GET", +// body: "", +// path: "/", +// host: "https://vercel.com", +// headers: { +// 'HOST': 'vercel.com' +// }, +// encoding: null, +// }); +// console.log(response); +// })(); diff --git a/public/launchers/cgi.d.ts b/public/launchers/cgi.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/public/launchers/cgi.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/launchers/cgi.js b/public/launchers/cgi.js new file mode 100644 index 0000000..23fb498 --- /dev/null +++ b/public/launchers/cgi.js @@ -0,0 +1,170 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const child_process_1 = require("child_process"); +const url_1 = require("url"); +const helpers_1 = require("./helpers"); +function createCGIReq({ entrypoint, path, host, method, headers }) { + const { query } = (0, url_1.parse)(path); + const env = { + ...process.env, + SERVER_ROOT: (0, helpers_1.getUserDir)(), + DOCUMENT_ROOT: (0, helpers_1.getUserDir)(), + SERVER_NAME: host, + SERVER_PORT: 443, + HTTPS: "On", + REDIRECT_STATUS: 200, + SCRIPT_NAME: entrypoint, + REQUEST_URI: path, + SCRIPT_FILENAME: entrypoint, + PATH_TRANSLATED: entrypoint, + REQUEST_METHOD: method, + QUERY_STRING: query || '', + GATEWAY_INTERFACE: "CGI/1.1", + SERVER_PROTOCOL: "HTTP/1.1", + PATH: process.env.PATH, + SERVER_SOFTWARE: "Vercel PHP", + LD_LIBRARY_PATH: process.env.LD_LIBRARY_PATH + }; + if (headers["content-length"]) { + env.CONTENT_LENGTH = headers["content-length"]; + } + if (headers["content-type"]) { + env.CONTENT_TYPE = headers["content-type"]; + } + if (headers["x-real-ip"]) { + env.REMOTE_ADDR = headers["x-real-ip"]; + } + // expose request headers + Object.keys(headers).forEach(function (header) { + var name = "HTTP_" + header.toUpperCase().replace(/-/g, "_"); + env[name] = headers[header]; + }); + return { + env + }; +} +function parseCGIResponse(response) { + const headersPos = response.indexOf("\r\n\r\n"); + if (headersPos === -1) { + return { + headers: {}, + body: response, + statusCode: 200 + }; + } + let statusCode = 200; + const rawHeaders = response.slice(0, headersPos).toString(); + const rawBody = response.slice(headersPos + 4); + const headers = parseCGIHeaders(rawHeaders); + if (headers['status']) { + statusCode = parseInt(headers['status']) || 200; + } + return { + headers, + body: rawBody, + statusCode + }; +} +function parseCGIHeaders(headers) { + if (!headers) + return {}; + const result = {}; + for (let header of headers.split("\n")) { + const index = header.indexOf(':'); + const key = header.slice(0, index).trim().toLowerCase(); + const value = header.slice(index + 1).trim(); + // Be careful about header duplication + result[key] = value; + } + return result; +} +function query({ entrypoint, path, host, headers, method, body }) { + console.log(`🐘 Spawning: PHP CGI ${entrypoint}`); + // Transform lambda request to CGI variables + const { env } = createCGIReq({ entrypoint, path, host, headers, method }); + // php-cgi spawn options + const options = { + stdio: ['pipe', 'pipe', 'pipe'], + env: env + }; + // now vs now-dev + if (!(0, helpers_1.isDev)()) { + options.env.PATH = `${(0, helpers_1.getPhpDir)()}:${process.env.PATH}`; + options.cwd = (0, helpers_1.getPhpDir)(); + } + else { + options.cwd = (0, helpers_1.getUserDir)(); + } + return new Promise((resolve) => { + const chunks = []; + const php = (0, child_process_1.spawn)('php-cgi', [entrypoint], options); + // Validate pipes [stdin] + if (!php.stdin) { + console.error(`🐘 Fatal error. PHP CGI child process has no stdin.`); + process.exit(253); + } + // Validate pipes [stdout] + if (!php.stdout) { + console.error(`🐘 Fatal error. PHP CGI child process has no stdout.`); + process.exit(254); + } + // Validate pipes [stderr] + if (!php.stderr) { + console.error(`🐘 Fatal error. PHP CGI child process has no stderr.`); + process.exit(255); + } + // Output + php.stdout.on('data', data => { + chunks.push(data); + }); + // Logging + php.stderr.on('data', data => { + console.error(`🐘 PHP CGI stderr`, data.toString()); + }); + // PHP script execution end + php.on('close', (code, signal) => { + if (code !== 0) { + console.log(`🐘 PHP CGI process closed code ${code} and signal ${signal}`); + } + const { headers, body, statusCode } = parseCGIResponse(Buffer.concat(chunks)); + resolve({ + body, + headers, + statusCode + }); + }); + php.on('error', err => { + console.error('🐘 PHP CGI errored', err); + resolve({ + body: Buffer.from(`🐘 PHP CGI process errored ${err}`), + headers: {}, + statusCode: 500 + }); + }); + // Writes the body into the PHP stdin + php.stdin.write(body || ''); + php.stdin.end(); + }); +} +async function launcher(event) { + const awsRequest = (0, helpers_1.normalizeEvent)(event); + const input = await (0, helpers_1.transformFromAwsRequest)(awsRequest); + const output = await query(input); + return (0, helpers_1.transformToAwsResponse)(output); +} +exports.createCGIReq = createCGIReq; +exports.launcher = launcher; +// (async function () { +// const response = await launcher({ +// Action: "test", +// httpMethod: "GET", +// body: "", +// path: "/", +// host: "https://vercel.com", +// headers: { +// 'HOST': 'vercel.com' +// }, +// encoding: null, +// }); +// console.log(response); +// })(); diff --git a/public/launchers/cli.d.ts b/public/launchers/cli.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/public/launchers/cli.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/launchers/cli.js b/public/launchers/cli.js new file mode 100644 index 0000000..12a6038 --- /dev/null +++ b/public/launchers/cli.js @@ -0,0 +1,90 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const child_process_1 = require("child_process"); +const helpers_1 = require("./helpers"); +function query({ entrypoint, body }) { + console.log(`🐘 Spawning: PHP CLI ${entrypoint}`); + // php spawn options + const options = { + stdio: ['pipe', 'pipe', 'pipe'], + env: process.env + }; + // now vs now-dev + if (!(0, helpers_1.isDev)()) { + options.env.PATH = `${(0, helpers_1.getPhpDir)()}:${process.env.PATH}`; + options.cwd = (0, helpers_1.getPhpDir)(); + } + else { + options.cwd = (0, helpers_1.getUserDir)(); + } + return new Promise((resolve) => { + const chunks = []; + const php = (0, child_process_1.spawn)('php', ['-c', 'php.ini', entrypoint], options); + // Validate pipes [stdin] + if (!php.stdin) { + console.error(`🐘 Fatal error. PHP CLI child process has no stdin.`); + process.exit(253); + } + // Validate pipes [stdout] + if (!php.stdout) { + console.error(`🐘 Fatal error. PHP CLI child process has no stdout.`); + process.exit(254); + } + // Validate pipes [stderr] + if (!php.stderr) { + console.error(`🐘 Fatal error. PHP CLI child process has no stderr.`); + process.exit(255); + } + // Output + php.stdout.on('data', data => { + chunks.push(data); + }); + // Logging + php.stderr.on('data', data => { + console.error(`🐘 PHP CLI stderr`, data.toString()); + }); + // PHP script execution end + php.on('close', (code, signal) => { + if (code !== 0) { + console.log(`🐘 PHP CLI process closed code ${code} and signal ${signal}`); + } + resolve({ + statusCode: 200, + headers: {}, + body: Buffer.concat(chunks) + }); + }); + php.on('error', err => { + console.error('🐘 PHP CLI errored', err); + resolve({ + body: Buffer.from(`🐘 PHP CLI process errored ${err}`), + headers: {}, + statusCode: 500 + }); + }); + // Writes the body into the PHP stdin + php.stdin.write(body || ''); + php.stdin.end(); + }); +} +async function launcher(event) { + const awsRequest = (0, helpers_1.normalizeEvent)(event); + const input = await (0, helpers_1.transformFromAwsRequest)(awsRequest); + const output = await query(input); + return (0, helpers_1.transformToAwsResponse)(output); +} +exports.launcher = launcher; +// (async function () { +// const response = await launcher({ +// Action: "test", +// httpMethod: "GET", +// body: "", +// path: "/", +// host: "https://vercel.com", +// headers: { +// 'HOST': 'vercel.com' +// }, +// encoding: null, +// }); +// console.log(response); +// })(); diff --git a/public/launchers/helpers.d.ts b/public/launchers/helpers.d.ts new file mode 100644 index 0000000..47935af --- /dev/null +++ b/public/launchers/helpers.d.ts @@ -0,0 +1,6 @@ +export declare const getUserDir: () => string; +export declare const getPhpDir: () => string; +export declare const isDev: () => boolean; +export declare function normalizeEvent(event: Event): AwsRequest; +export declare function transformFromAwsRequest({ method, path, host, headers, body, }: AwsRequest): Promise; +export declare function transformToAwsResponse({ statusCode, headers, body }: PhpOutput): AwsResponse; diff --git a/public/launchers/helpers.js b/public/launchers/helpers.js new file mode 100644 index 0000000..43562b5 --- /dev/null +++ b/public/launchers/helpers.js @@ -0,0 +1,57 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.transformToAwsResponse = exports.transformFromAwsRequest = exports.normalizeEvent = exports.isDev = exports.getPhpDir = exports.getUserDir = void 0; +const path_1 = require("path"); +const getUserDir = () => (0, path_1.join)(process.env.LAMBDA_TASK_ROOT || '/', 'user'); +exports.getUserDir = getUserDir; +const getPhpDir = () => (0, path_1.join)(process.env.LAMBDA_TASK_ROOT || '/', 'php'); +exports.getPhpDir = getPhpDir; +const isDev = () => process.env.NOW_PHP_DEV === '1'; +exports.isDev = isDev; +function normalizeEvent(event) { + if (event.Action === 'Invoke') { + const invokeEvent = JSON.parse(event.body); + const { method, path, host, headers = {}, encoding, } = 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, + }; +} +exports.normalizeEvent = normalizeEvent; +async function transformFromAwsRequest({ method, path, host, headers, body, }) { + if (!process.env.NOW_ENTRYPOINT) { + console.error('Missing ENV NOW_ENTRYPOINT'); + } + const entrypoint = (0, path_1.join)((0, exports.getUserDir)(), process.env.NOW_ENTRYPOINT || 'index.php'); + const uri = host + path; + return { entrypoint, uri, path, host, method, headers, body }; +} +exports.transformFromAwsRequest = transformFromAwsRequest; +function transformToAwsResponse({ statusCode, headers, body }) { + return { statusCode, headers, body: body.toString('base64'), encoding: 'base64' }; +} +exports.transformToAwsResponse = transformToAwsResponse; diff --git a/public/utils.d.ts b/public/utils.d.ts new file mode 100644 index 0000000..bdc01a6 --- /dev/null +++ b/public/utils.d.ts @@ -0,0 +1,8 @@ +import { File, FileBlob } from '@vercel/build-utils'; +export declare function getPhpFiles(): Promise; +export declare function getLauncherFiles(): RuntimeFiles; +export declare function modifyPhpIni(userFiles: UserFiles, runtimeFiles: RuntimeFiles): Promise; +export declare function runComposerInstall(workPath: string): Promise; +export declare function runComposerScripts(composerFile: File, workPath: string): Promise; +export declare function ensureLocalPhp(): Promise; +export declare function readRuntimeFile(file: File): Promise; diff --git a/public/utils.js b/public/utils.js new file mode 100644 index 0000000..3d0044f --- /dev/null +++ b/public/utils.js @@ -0,0 +1,186 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.readRuntimeFile = exports.ensureLocalPhp = exports.runComposerScripts = exports.runComposerInstall = exports.modifyPhpIni = exports.getLauncherFiles = exports.getPhpFiles = void 0; +const path_1 = __importDefault(require("path")); +const child_process_1 = require("child_process"); +const build_utils_1 = require("@vercel/build-utils"); +const libphp = __importStar(require("@libphp/amazon-linux-2-v83")); +const PHP_PKG = path_1.default.dirname(require.resolve('@libphp/amazon-linux-2-v83/package.json')); +const PHP_BIN_DIR = path_1.default.join(PHP_PKG, "native/php"); +const PHP_MODULES_DIR = path_1.default.join(PHP_BIN_DIR, "modules"); +const PHP_LIB_DIR = path_1.default.join(PHP_PKG, "native/lib"); +const COMPOSER_BIN = path_1.default.join(PHP_BIN_DIR, "composer"); +async function getPhpFiles() { + const files = await libphp.getFiles(); + // Drop CGI + FPM from libphp, it's not needed for our case + delete files['php/php-cgi']; + delete files['php/php-fpm']; + delete files['php/php-fpm.ini']; + const runtimeFiles = {}; + // Map from @libphp to Vercel's File objects + for (const [filename, filepath] of Object.entries(files)) { + runtimeFiles[filename] = new build_utils_1.FileFsRef({ + fsPath: filepath + }); + } + // Set some bins executable + runtimeFiles['php/php'].mode = 33261; // 0755; + runtimeFiles['php/composer'].mode = 33261; // 0755; + return runtimeFiles; +} +exports.getPhpFiles = getPhpFiles; +function getLauncherFiles() { + const files = { + 'helpers.js': new build_utils_1.FileFsRef({ + fsPath: path_1.default.join(__dirname, 'launchers/helpers.js'), + }) + }; + files['launcher.js'] = new build_utils_1.FileFsRef({ + fsPath: path_1.default.join(__dirname, 'launchers/builtin.js'), + }); + return files; +} +exports.getLauncherFiles = getLauncherFiles; +async function modifyPhpIni(userFiles, runtimeFiles) { + // Validate user files contains php.ini + if (!userFiles['api/php.ini']) + return; + // Validate runtime contains php.ini + if (!runtimeFiles['php/php.ini']) + return; + const phpiniBlob = await build_utils_1.FileBlob.fromStream({ + stream: runtimeFiles['php/php.ini'].toStream(), + }); + const userPhpiniBlob = await build_utils_1.FileBlob.fromStream({ + stream: userFiles['api/php.ini'].toStream(), + }); + return new build_utils_1.FileBlob({ + data: phpiniBlob.data.toString() + .concat("; [User]\n") + .concat(userPhpiniBlob.data.toString()) + }); +} +exports.modifyPhpIni = modifyPhpIni; +async function runComposerInstall(workPath) { + console.log('🐘 Installing Composer dependencies [START]'); + // @todo PHP_COMPOSER_INSTALL env + await runPhp([ + COMPOSER_BIN, + 'install', + '--profile', + '--no-dev', + '--no-interaction', + '--no-scripts', + '--ignore-platform-reqs', + '--no-progress' + ], { + stdio: 'inherit', + cwd: workPath + }); + console.log('🐘 Installing Composer dependencies [DONE]'); +} +exports.runComposerInstall = runComposerInstall; +async function runComposerScripts(composerFile, workPath) { + var _a; + let composer; + try { + composer = JSON.parse(await readRuntimeFile(composerFile)); + } + catch (e) { + console.error('🐘 Composer file is not valid JSON'); + console.error(e); + return; + } + if ((_a = composer === null || composer === void 0 ? void 0 : composer.scripts) === null || _a === void 0 ? void 0 : _a.vercel) { + console.log('🐘 Running composer scripts [START]'); + await runPhp([COMPOSER_BIN, 'run', 'vercel'], { + stdio: 'inherit', + cwd: workPath + }); + console.log('🐘 Running composer scripts [DONE]'); + } +} +exports.runComposerScripts = runComposerScripts; +async function ensureLocalPhp() { + try { + await spawnAsync('which', ['php', 'php-cgi'], { stdio: 'pipe' }); + return true; + } + catch (e) { + return false; + } +} +exports.ensureLocalPhp = ensureLocalPhp; +async function readRuntimeFile(file) { + const blob = await build_utils_1.FileBlob.fromStream({ + stream: file.toStream(), + }); + return blob.data.toString(); +} +exports.readRuntimeFile = readRuntimeFile; +// ***************************************************************************** +// PRIVATE API ***************************************************************** +// ***************************************************************************** +async function runPhp(args, opts = {}) { + try { + await spawnAsync('php', args, { + ...opts, + env: { + ...process.env, + ...(opts.env || {}), + COMPOSER_HOME: '/tmp', + PATH: `${PHP_BIN_DIR}:${process.env.PATH}`, + PHP_INI_EXTENSION_DIR: PHP_MODULES_DIR, + PHP_INI_SCAN_DIR: `:${path_1.default.resolve(__dirname, '../conf')}`, + LD_LIBRARY_PATH: `${PHP_LIB_DIR}:/usr/lib64:/lib64:${process.env.LD_LIBRARY_PATH}` + } + }); + } + catch (e) { + console.error(e); + process.exit(1); + } +} +function spawnAsync(command, args, opts = {}) { + return new Promise((resolve, reject) => { + const child = (0, child_process_1.spawn)(command, args, { + stdio: "ignore", + ...opts + }); + child.on('error', reject); + child.on('exit', (code, signal) => { + if (code === 0) { + resolve(); + } + else { + reject(new Error(`Exited with ${code || signal}`)); + } + }); + }); +} diff --git a/tsconfig.json b/tsconfig.json index be3a934..46a9471 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,7 @@ ], "target": "ES2019", "module": "CommonJS", - "outDir": "dist", + "outDir": "public", "sourceMap": false, "declaration": true, "noImplicitAny": true, @@ -23,8 +23,7 @@ ] }, "include": [ - "src/**/*.ts", - "public/**/*.ts" + "src/**/*.ts" ], "exclude": [ "errors",