[
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: http://EditorConfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.{html}]\nindent_style = tab\nindent_size = tab\ntab_width = 4\n\n[*.{js,ts,json,yml,yaml,md}]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".github/.kodiak.toml",
    "content": "version = 1\n\n[merge]\nautomerge_label = \"automerge\"\nblacklist_title_regex = \"^WIP.*\"\nblacklist_labels = [\"WIP\"]\nmethod = \"rebase\"\ndelete_branch_on_merge = true\nnotify_on_conflict = true\noptimistic_updates = false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Bug.md",
    "content": "---\nname: Bug report 🐛\nabout: Something is not working as expected!\n---\n\n# Bug report\n\n- Version: x.y.z\n- URL: Yes (*.now.sh) / No\n- Repository: Yes / No\n\n## Description\n\n<!-- Describe it -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Feature.md",
    "content": "---\nname: Feature request 🚀\nabout: I would appreciate new feature or something!\n---\n\n# Feature Request\n\n<!-- Describe it -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Question.md",
    "content": "---\nname: Question ❓\nabout: Ask about anything!\n---\n\n# Question\n\n<!-- Describe it -->\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: npm\n  directory: \"/\"\n  schedule:\n    interval: daily\n    time: '11:00'\n  open-pull-requests-limit: 10\n- package-ecosystem: npm\n  directory: \"/packages/php\"\n  schedule:\n    interval: daily\n    time: '11:00'\n  open-pull-requests-limit: 10\n- package-ecosystem: npm\n  directory: \"/packages/caddy\"\n  schedule:\n    interval: daily\n    time: '11:00'\n  open-pull-requests-limit: 10\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: Main workflow\n\non: [push, pull_request]\n\njobs:\n  run:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n\n      - name: Setup Node\n        uses: actions/setup-node@v3\n        with:\n          node-version: '22.x'\n\n      - name: Dependencies\n        run: make install\n\n      - name: Build\n        run: make build\n\n      - name: Tests\n        run: make test\n"
  },
  {
    "path": ".gitignore",
    "content": "# Node\n/node_modules\n/package-lock.json\n\n# App\n/dist\n\n# Vercel\n.vercel\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n### [0.9.0] - 2026-01-15\n\n- PHP 8.5\n\n### [0.8.0] - 2026-01-15\n\n- PHP 8.4\n\n### [0.7.4] - 2025-07-17\n\n- Upgrade PHP 7.4-8-3 runtimes for Node 22\n\n### [0.7.3] - 2024-10-19\n\n- Upgrade PHP 8.3 runtime (fixes for curl, opcache)\n\n### [0.7.2] - 2024-09-30\n\n- Upgrade PHP 8.3 runtime\n\n### [0.7.1] - 2024-04-16\n\n- Fix autodetect runtime\n\n### [0.6.2] - 2024-04-16\n\n- Fix autodetect runtime\n\n### [0.5.5] - 2024-04-16\n\n- Fix autodetect runtime\n\n### [0.4.4] - 2024-04-16\n\n- Fix autodetect runtime\n\n### [0.3.6] - 2024-04-16\n\n- Fix autodetect runtime\n\n### [0.7.0] - 2024-02-22\n\n- PHP 8.3\n- Use `@libphp/amazon-linux-2-v83: latest`\n\n### [0.6.1] - 2024-01-24\n\n- Update LD_LIBRARY_PATH\n\n### [0.5.4] - 2024-01-24\n\n- Update LD_LIBRARY_PATH\n\n### [0.4.3] - 2024-01-24\n\n- Update LD_LIBRARY_PATH\n\n### [0.3.5] - 2024-01-24\n\n- Update LD_LIBRARY_PATH\n\n### [0.6.0] - 2023-03-27\n\n- PHP 8.2\n- Use `@libphp/amazon-linux-2-v82: latest`\n\n### [0.5.3] - 2023-03-27\n\n- Bump minimum node version from 14.x to 18.x\n- Upgrade dependencies\n\n### [0.5.2] - 2022-08-10\n\n- Bump minimum node version from 12.x to 14.x\n\n### [0.5.1] - 2022-05-05\n\n- Ignore .vercel folder during deployment\n\n### [0.5.0] - 2022-04-09\n\n- PHP 8.1\n  - Added extensions: geoip, zlib, zip\n  - Removed extensions: psr\n- Use `@libphp/amazon-linux-2-v81: latest`\n\n### [0.4.0] - 2021-01-02\n\n- PHP 8.0\n- Use `@libphp/amazon-linux-2-v80: latest`\n\n### [0.3.2] - 2021-01-02\n\n- Typos\n- More hints in FAQ\n- Fix `excludeFiles` option\n- Install PHP extensions mongodb\n- Use `@libphp/amazon-linux-2-v74: latest`\n\n### [0.3.1] - 2020-07-04\n\n- Install PHP extensions redis, msgpack, igbinary\n- Use `@libphp/amazon-linux-2-v74: latest`\n\n### [0.3.0] - 2020-06-29\n\n- Allow to execute composer script called `vercel`\n\n  ```json\n  {\n    \"scripts\": {\n      \"vercel\": [\n        \"@php -v\",\n        \"npm -v\"\n      ]\n    }\n  }\n  ```\n\n- Drop support of `config['php.ini']` use `api/php.ini` file instead\n- Support excludeFiles (default `['node_modules/**', 'now.json', '.nowignore']`)\n\n  ```json\n  {\n    \"functions\": {\n    \"api/**/*.php\": {\n      \"runtime\": \"vercel-php@0.3.0\",\n      \"excludeFiles\": [\"node_modules\", \"somedir\", \"foo/bar\"],\n    }\n  }\n  ```\n\n- Restructure test folder (merge fixtures + my examples)\n\n### [0.2.0] - 2020-06-26\n\n- Allow to override `php.ini`\n\n  ```sh\n  project\n  ├── api\n  │   ├── index.php\n  │   └── php.ini\n  └── now.json\n  ```\n\n- Extensive update of docs\n- Introduce FAQ questions\n- Move caddy package to [juicyfx/juicy](https://github.com/juicyfx/juicy)\n- Simplify repository structure\n\n### [0.1.0] - 2020-06-20\n\n- Rename repository from now-php to **vercel-php**\n- Rename NPM package from now-php to **vercel-php**\n- Upgrade PHP to 7.4.7 and recompile PHP extensions\n- Improve readme\n- Separate PHP libs to solo repository [juicyfx/libphp](https://github.com/juicyfx/libphp) (bigger plans)\n- Use [php.vercel.app](https://php.vercel.app) domain for official showtime\n- Use [phpshow.vercel.app](https://phpshow.vercel.app) domain for runtime showcase\n\n### [0.0.9] - 2020-03-28\n\n- Use PHP 7.4 for installing Composer dependencies\n- Upgrade PHP 7.4 and recompile PHP extensions\n\n### [0.0.9] - 2020-01-16\n\n- Use PHP 7.3 for installing Composer dependencies\n- Separate [examples](https://github.com/juicyfx/vercel-examples) to solo repository\n- Extensions\n  - Disabled ssh2\n  - Added psr\n  - Rebuild phalcon, swoole\n\n### [0.0.8] - 2020-01-07\n\n- Runtime v3\n- Upgrade to PHP 7.4.x\n- Node 8.x reached EOL on AWS\n- Used Amazon Linux 2\n- CGI launcher inherits process.env [#38]\n- Drop Circle CI\n- Rebuild all PHP libs\n\n### [0.0.7] - 2019-11-08\n\n- Rename builder to runtime\n- Runtime v3\n\n**Migration**\n\n```json\n{\n  \"version\": 2,\n  \"builds\": [\n    {\n      \"src\": \"index.php\",\n      \"use\": \"now-php\"\n    }\n  ]\n}\n```\n\n➡️\n\n```json\n{\n  \"functions\": {\n    \"api/*.php\": {\n      \"runtime\": \"now-php@0.0.7\"\n    }\n  },\n  // Optionally provide routes\n  \"routes\": [\n    { \"src\": \"/(.*)\",  \"dest\": \"/api/index.php\" }\n  ]\n}\n```\n\n### [0.0.6] - 2019-11-07\n\n- Change builds to functions\n\n### [0.0.5] - 2019-09-30\n\n- Added Lumen example\n- Bugfix deploying PHP files in folders under different names then index.php\n\n### [0.0.4] - 2019-09-30\n\n- Implement intermediate caching (vendor, composer.lock, yarn.locak, package-lock.json, node_modules)\n- Rewrite PHP built-in server document root\n\n### [0.0.3] - 2019-09-04\n\n- Bugfix passing query parameters and accessing $_GET\n\n### [0.0.2] - 2019-08-23\n\n- Bump now-php@latest\n\n### [0.0.1-canary.39] - 2019-08-23\n\n- Allow overriding php.ini\n- Bugfix resolving PHP bin\n- Bugfix deploying php files in subfolders\n\n### [0.0.2-canary.2] - 2019-08-16\n\n- Compile PHP 7.3.8\n\n### [0.0.1-canary.5] - 2019-08-16\n\n- First working copy of caddy server\n\n### [0.0.1-canary.30] - 2019-08-16\n\n- New exported method `getPhpLibFiles`\n- Repair tests\n\n### [0.0.1-canary.18] - 2019-08-02\n\n- Bump now-php@latest\n\n### [0.0.1-canary.18] - 2019-08-02\n\n- Working on change response from string to Buffer\n- Updated homepage\n\n### [0.0.1-canary.17] - 2019-08-02\n\n- Working on change response from string to Buffer\n\n### [0.0.1-canary.15] - 2019-08-02\n\n- CGI: REQUEST_URI contains only path, not host + path\n- CGI: QUERY_STRING contains string without leading ?\n\n### [0.0.1-canary.14] - 2019-07-29\n\n- Tests: more tests\n\n### [0.0.1-canary.13] - 2019-07-29\n\n- Tests: take tests from official old builder\n\n### [0.0.1-canary.12] - 2019-07-28\n\n- Rewritten to TypeScript\n\n### [0.0.1-canary.11] - 2019-07-28\n\n- Working on `now-dev`\n\n### [0.0.1-canary.8] - 2019-07-27\n\n- First working `now-php` builder\n\n### [0.0.1-canary.7] - 2019-07-27\n\n- Working on `now` with `now-php`\n\n### [0.0.1-canary.0] - 2019-07-27\n\n- History begins\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\n**vercel-php** is a PHP runtime for the Vercel platform enabling serverless PHP applications. It bundles PHP 8.3 with common extensions and supports multiple execution modes (built-in server, CGI, CLI).\n\n## Commands\n\n```bash\nmake install       # Install dependencies (npm install)\nmake build         # Compile TypeScript to dist/ (npm run build)\nmake build-watch   # Watch mode compilation (npm run watch)\nmake test          # Run Jest test suite (npm run test)\nmake test-watch    # Run tests in watch mode\nmake publish       # Publish to npm (latest tag)\nmake canary        # Publish to npm (canary tag)\n```\n\n## Architecture\n\n### Vercel BuildV3 Runtime\n\nThe package implements Vercel's BuildV3 specification, exporting:\n- `version = 3` - API version\n- `build()` - Main builder function\n- `prepareCache()` - Cache preparation\n- `shouldServe()` - File serving decisions\n\n### Build Flow (src/index.ts)\n\n1. Download user files and PHP runtime (`@libphp/amazon-linux-2-v83`)\n2. Run `composer install` if `composer.json` exists\n3. Execute `composer run vercel` script if defined\n4. Merge user `api/php.ini` with runtime defaults\n5. Package everything into AWS Lambda function\n\n### Runtime Execution Modes (src/launchers/)\n\n- **builtin.ts** - PHP's built-in server (`php -S 0.0.0.0:3000`), proxied via Node.js. Default mode.\n- **cgi.ts** - Spawns `php-cgi` per request with CGI environment variables. Stateless.\n- **cli.ts** - Direct PHP CLI execution for simple scripts.\n\n### Key Source Files\n\n- `src/index.ts` - Build entry point implementing BuildV3\n- `src/utils.ts` - Build utilities (Composer, PHP config, file collection)\n- `src/launchers/helpers.ts` - Request/response transformation between Vercel events and PHP\n\n### Type Definitions (src/types.d.ts)\n\nKey types: `UserFiles`, `RuntimeFiles`, `Event`, `InvokedEvent`, `AwsRequest`, `AwsResponse`, `PhpInput`, `PhpOutput`, `CgiInput`\n\n## Code Conventions\n\n- Logging uses 🐘 emoji prefix for console output\n- Exit codes: 253-255 for critical child process errors, 1 for general errors\n- User files are prefixed with `/user/` in Lambda task root\n- TypeScript strict mode with `noImplicitAny`, `noUnusedLocals`, `noUnusedParameters`\n- 2-space indentation for JS/TS/JSON/YAML/MD files\n\n## Testing\n\nTests are in `test/spec/` with example projects in `test/examples/`. Jest timeout is 10 seconds.\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 Milan Felix Sulc (f3l1x)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: install build test publish canary\n\ninstall:\n\tnpm install\n\nbuild:\n\tnpm run build\n\nbuild-watch:\n\tnpm run watch\n\ntest:\n\tnpm run test\n\ntest-watch:\n\tnpm run test-watch\n\npublish:\n\trm -rf ./dist\n\tnpm publish --access public --tag latest\n\ncanary:\n\trm -rf ./dist\n\tnpm publish --access public --tag canary\n"
  },
  {
    "path": "README.md",
    "content": "<h1 align=center>PHP Runtime for <a href=\"https://vercel.com\">Vercel</h1>\n\n<p align=center>\n  Enjoyable & powerful 🐘 PHP Runtime (<a href=\"https://php.vercel.app\">php.vercel.app</a>) for Vercel platform.\n</p>\n\n<p align=center>\n  <a href=\"https://www.npmjs.com/package/vercel-php\"><img src=\"https://badgen.net/npm/v/vercel-php\"></a>\n  <a href=\"https://www.npmjs.com/package/vercel-php\"><img src=\"https://badgen.net/npm/dt/vercel-php\"></a>\n  <a href=\"https://github.com/juicyfx/vercel-php/actions\"><img src=\"https://badgen.net/github/checks/juicyfx/vercel-php\"></a>\n\t<a href=\"https://bit.ly/f3l1xdis\"><img src=\"https://badgen.net/badge/support/discussions/yellow\"></a>\n\t<a href=\"http://bit.ly/f3l1xsponsor\"><img src=\"https://badgen.net/badge/sponsor/donations/F96854\"></a>\n</p>\n\n<p align=center>\n  <a href=\"https://github.com/nette\"><img src=\"https://github.com/nette.png\" width=\"128\"></a>\n  <a href=\"https://github.com/symfony\"><img src=\"https://github.com/symfony.png\" width=\"128\"></a>\n  <a href=\"https://github.com/illuminate\"><img src=\"https://github.com/illuminate.png\" width=\"128\"></a>\n  <a href=\"https://github.com/slimphp\"><img src=\"https://github.com/slimphp.png\" width=\"128\"></a>\n  <a href=\"https://github.com/phalcon\"><img src=\"https://github.com/phalcon.png\" width=\"128\"></a>\n</p>\n\n<p align=center><strong>🏋️‍♀️ It works with these frameworks and tools. Discover more at <a href=\"https://github.com/juicyfx/vercel-examples\">examples</a>.</strong></p>\n\n<p align=center>\nMade with  ❤️  by <a href=\"https://github.com/f3l1x\">@f3l1x</a> (<a href=\"https://f3l1x.io\">f3l1x.io</a>) • 🐦 <a href=\"https://twitter.com/xf3l1x\">@xf3l1x</a>\n</p>\n\n-----\n\n## 😎 Getting Started\n\nLet's picture you want to deploy your awesome microproject written in PHP and you don't know where. You have found [Vercel](https://vercel.com) it's awesome, but for static sites. Not anymore! I would like to introduce you your new best friend `vercel-php`, PHP runtime for Vercel platform.\n\nMost simple example project is this one, using following project structure.\n\n```sh\nproject\n├── api\n│   └── index.php\n└── vercel.json\n```\n\nFirst file `api/index.php` is entrypoint of our application. It should be placed in **api** folder, it's very standard location for Vercel.\n\n```php\n<?php\nphpinfo();\n```\n\nSecond file `vercel.json` is pure gold here. Setup your project with configuration like this and voila. That's all.\n\n```json\n{\n  \"functions\": {\n    \"api/*.php\": {\n      \"runtime\": \"vercel-php@0.9.0\"\n    }\n  }\n}\n```\n\nLast thing you have to do is call `vercel`. If you are more interested take a look at features and usage.\n\n```\n# Install it globally\nnpm i -g vercel\n\n# Log in\nvercel login\n\n# Let's fly\nvercel\n```\n\nAre you ready to deploy your first PHP project to Vercel? Click & Go!\n\n<a href=\"https://vercel.com/new/clone?repository-url=https://github.com/juicyfx/vercel-examples/tree/master/php\"><img src=\"https://vercel.com/button\"></a>\n\n## 🤗 Features\n\n- **Architecture**: PHP development server (🚀 fast enough)\n- **PHP version**: 8.5 (https://example-php-8-5.vercel.app)\n- **Extensions**: apcu, bcmath, brotli, bz2, calendar, Core, ctype, curl, date, dom, ds, exif, fileinfo, filter, ftp, geoip, gettext, hash, iconv, igbinary, imap, intl, json, libxml, lua, mbstring, mongodb, msgpack, mysqli, mysqlnd, openssl, pcntl, pcre, PDO, pdo_mysql, pdo_pgsql, pdo_sqlite, pgsql, phalcon, Phar, protobuf, readline, redis, Reflection, runkit7, session, SimpleXML, soap, sockets, sodium, SPL, sqlite3, standard, swoole, timecop, tokenizer, uuid, xml, xmlreader, xmlrpc, xmlwriter, xsl, Zend OPcache, zlib, zip\n- **Speed**: cold ~250ms / warm ~5ms\n- **Memory**: ~90mb\n- **Frameworks**: Nette, Symfony, Lumen, Slim, Phalcon\n- **Node.js**: 22.x\n\n> List of all installable extensions is on this page https://blog.remirepo.net/pages/PECL-extensions-RPM-status.\n\n## 💯 Versions\n\n- `vercel-php@0.9.0` - Node autodetect / PHP 8.5.x (https://example-php-8-5.vercel.app)\n- `vercel-php@0.8.0` - Node autodetect / PHP 8.4.x (https://example-php-8-4.vercel.app)\n- `vercel-php@0.7.4` - Node autodetect / PHP 8.3.x (https://example-php-8-3.vercel.app)\n- `vercel-php@0.6.2` - Node autodetect / PHP 8.2.x (https://example-php-8-2.vercel.app)\n- `vercel-php@0.5.5` - Node autodetect / PHP 8.1.x (https://example-php-8-1.vercel.app)\n- `vercel-php@0.4.5` - Node autodetect / PHP 8.0.x (https://example-php-8-0.vercel.app)\n- `vercel-php@0.3.8` - Node autodetect / PHP 7.4.x (https://example-php-7-4.vercel.app)\n\n## ⚙️  Usage\n\nBefore you can start using this runtime, you should learn about Vercel and [how runtimes](https://vercel.com/docs/runtimes?query=runtime#official-runtimes) works. Take a look at blogpost about [`Serverless Functions`](https://vercel.com/blog/customizing-serverless-functions).\n\nYou should define `functions` property in `vercel.json` and list PHP files directly or using wildcard (*).\nIf you need to route everything to index, use `routes` property.\n\n```json\n{\n  \"functions\": {\n    \"api/*.php\": {\n      \"runtime\": \"vercel-php@0.9.0\"\n    }\n  },\n  \"routes\": [\n    { \"src\": \"/(.*)\",  \"dest\": \"/api/index.php\" }\n  ]\n}\n```\n\nDo you have more questions (❓)? Let's move to [FAQ](#%EF%B8%8F-faq).\n\n## 👨‍💻 `vercel dev`\n\nFor running `vercel dev` properly, you need to have PHP installed on your computer, [learn more](errors/now-dev-no-local-php.md).\nBut it's PHP and as you know PHP has built-in development server. It works out of box.\n\n```\nphp -S localhost:8000 api/index.php\n```\n\n## 👀 Demo\n\n- official - https://php.vercel.app/\n- phpinfo - https://phpshow.vercel.app/\n- extensions - https://phpshow.vercel.app/ext/\n- ini - https://phpshow.vercel.app/ini/\n- JSON API - https://phpshow.vercel.app/api/users.php\n- test - https://phpshow.vercel.app/test.php\n\n![PHP](https://api.microlink.io?url=https://phpshow.vercel.app&screenshot&embed=screenshot.url)\n\n## 🎯Examples\n\n- [PHP - fast & simple](https://github.com/juicyfx/vercel-examples/tree/master/php/)\n- [Composer - install dependencies](https://github.com/juicyfx/vercel-examples/tree/master/php-composer/)\n- [Framework - Laravel](https://github.com/juicyfx/vercel-examples/blob/master/php-laravel)\n- [Framework - Lumen](https://github.com/juicyfx/vercel-examples/blob/master/php-lumen)\n- [Framework - Nette](https://github.com/juicyfx/vercel-examples/blob/master/php-nette-tracy)\n- [Framework - Phalcon](https://github.com/juicyfx/vercel-examples/blob/master/php-phalcon)\n- [Framework - Slim](https://github.com/juicyfx/vercel-examples/blob/master/php-slim)\n- [Framework - Symfony - Microservice](https://github.com/juicyfx/vercel-examples/blob/master/php-symfony-microservice)\n\nBrowse [more examples](https://github.com/juicyfx/vercel-examples). 👀\n\n## 📜 Resources\n\n- [2019/10/23 - Code Examples](https://github.com/trainit/2019-10-hubbr-zeit)\n- [2019/10/19 - ZEIT - Deploy Serverless Microservices Right Now](https://slides.com/f3l1x/2019-10-19-zeit-deploy-serverless-microservices-right-now-vol2)\n- [2019/08/23 - Code Examples](https://github.com/trainit/2019-08-serverless-zeit-now)\n- [2019/07/07 - Bleeding Edge PHP on ZEIT Now](https://dev.to/nx1/bleeding-edge-php-on-zeit-now-565g)\n- [2019/06/06 - Code Examples](https://github.com/trainit/2019-06-zeit-now)\n- [2019/06/05 - ZEIT - Deploy Serverless Microservices Right Now](https://slides.com/f3l1x/2019-06-05-zeit-deploy-serverless-microservices-right-now) ([VIDEO](https://www.youtube.com/watch?v=IwhEGNDx3aE))\n\n## 🚧 Roadmap\n\nSee [roadmap issue](https://github.com/juicyfx/vercel-php/issues/3). Help wanted.\n\n## ⁉️ FAQ\n\n<details>\n  <summary>1. How to use more then one endpoint (index.php)?</summary>\n\n```sh\nproject\n├── api\n│   ├── index.php\n│   ├── users.php\n│   └── books.php\n└── vercel.json\n```\n\n```\n{\n  \"functions\": {\n    \"api/*.php\": {\n      \"runtime\": \"vercel-php@0.9.0\"\n    },\n\n    // Can be list also directly\n\n    \"api/index.php\": {\n      \"runtime\": \"vercel-php@0.9.0\"\n    },\n    \"api/users.php\": {\n      \"runtime\": \"vercel-php@0.9.0\"\n    },\n    \"api/books.php\": {\n      \"runtime\": \"vercel-php@0.9.0\"\n    }\n  }\n}\n```\n\n</details>\n\n<details>\n  <summary>2. How to route everything to index?</summary>\n\n```json\n{\n  \"functions\": {\n    \"api/index.php\": {\n      \"runtime\": \"vercel-php@0.9.0\"\n    }\n  },\n  \"routes\": [\n    { \"src\": \"/(.*)\",  \"dest\": \"/api/index.php\" }\n  ]\n}\n```\n\n</details>\n\n<details>\n  <summary>3. How to update memory limit?</summary>\n\nAdditional function properties are `memory`, `maxDuration`. Learn more about [functions](https://vercel.com/docs/configuration#project/functions).\n\n```json\n{\n  \"functions\": {\n    \"api/*.php\": {\n      \"runtime\": \"vercel-php@0.9.0\",\n      \"memory\": 3008,\n      \"maxDuration\": 60\n    }\n  }\n}\n```\n\n</details>\n\n<details>\n  <summary>4. How to use it with <a href=\"https://getcomposer.org/\">Composer</a>?</summary>\n\nYes, [Composer](https://getcomposer.org/) is fully supported.\n\n```sh\nproject\n├── api\n│   └── index.php\n├── composer.json\n└── vercel.json\n```\n\n```json\n{\n  \"functions\": {\n    \"api/*.php\": {\n      \"runtime\": \"vercel-php@0.9.0\"\n    }\n  }\n}\n```\n\n```json\n{\n  \"require\": {\n    \"php\": \"^8.1\",\n    \"tracy/tracy\": \"^2.0\"\n  }\n}\n```\n\nIt's also good thing to create `.vercelignore` file and put `/vendor` folder to this file. It will not upload\n`/vendor` folder to Vercel platform.\n\n</details>\n\n<details>\n  <summary>5. How to override <a href=\"https://www.php.net/manual/en/ini.list.php\">php.ini</a> / <a href=\"https://www.php.net/manual/en/configuration.file.php\">php configuration</a> ?</summary>\n\nYes, you can override php configuration. Take a look at [default configuration](https://phpshow.vercel.app/) at first.\nCreate a new file `api/php.ini` and place there your configuration. Don't worry, this particulary file will be\nremoved during building phase on Vercel.\n\n```sh\nproject\n├── api\n│   ├── index.php\n│   └── php.ini\n└── vercel.json\n```\n\n```json\n{\n  \"functions\": {\n    \"api/*.php\": {\n      \"runtime\": \"vercel-php@0.9.0\"\n    }\n  }\n}\n```\n\n```json\n# Disable some functions\ndisable_functions = \"exec, system\"\n\n# Update memory limit\nmemory_limit=1024M\n```\n\n</details>\n\n<details>\n  <summary>6. How to exclude some files or folders ?</summary>\n\nRuntimes support excluding some files or folders, [take a look at doc](https://vercel.com/docs/configuration?query=excludeFiles#project/functions).\n\n```json\n{\n  \"functions\": {\n  \"api/**/*.php\": {\n    \"runtime\": \"vercel-php@0.9.0\",\n    \"excludeFiles\": \"{foo/**,bar/config/*.yaml}\",\n  }\n}\n```\n\nIf you want to exclude files before uploading them to Vercel, use `.vercelignore` file.\n\n</details>\n\n<details>\n  <summary>7. How to call composer script(s) ?</summary>\n\nCalling composer scripts during build phase on Vercel is supported via script named `vercel`. You can easilly call php, npm or node.\n\n```json\n{\n  \"require\": { ... },\n  \"require-dev\": { ... },\n  \"scripts\": {\n    \"vercel\": [\n      \"@php -v\",\n      \"npm -v\"\n    ]\n  }\n}\n```\n\nFiles created during `composer run vercel` script can be used (require/include) in your PHP lambdas, but can't be accessed from browser (like assets). If you still want to access them, create fake `assets.php` lambda and require them. [Example of PHP satis](https://github.com/juicyfx/vercel-examples/tree/master/php-satis).\n\n</details>\n\n<details>\n  <summary>8. How to include some files of folders?</summary>\n\nIf you are looking for [`config.includeFiles`](https://vercel.com/docs/configuration?query=includeFiles#project/functions) in runtime, unfortunately you can't include extra files.\nAll files in root folder are uploaded to Vercel, use `.vercelignore` to exclude them before upload.\n\n</details>\n\n<details>\n  <summary>9. How to develop locally?</summary>\n\nI think the best way at this moment is use [PHP Development Server](https://www.php.net/manual/en/features.commandline.webserver.php).\n\n```\nphp -S localhost:8000 api/index.php\n```\n\n</details>\n\n<details>\n  <summary>10. What Node.js runtime is supported?</summary>\n\nUse 22.x.\n\n</details>\n\n## 👨🏻‍💻CHANGELOG\n\nShow me [CHANGELOG](./CHANGELOG.md)\n\n## 🧙Contribution\n\n1. Clone this repository.\n   - `git clone git@github.com:juicyfx/vercel-php.git`\n2. Install NPM dependencies\n   - `make install`\n3. Make your changes\n4. Run TypeScript compiler\n   - `make build`\n5. Run tests\n   - `make test`\n6. Create a PR\n\n## 📝 License\n\nCopyright © 2019 [f3l1x](https://github.com/f3l1x).\nThis project is [MIT](LICENSE) licensed.\n"
  },
  {
    "path": "conf/build.ini",
    "content": "; Override for build phase on Vercel\nextension_dir = \"${PHP_INI_EXTENSION_DIR}\"\n"
  },
  {
    "path": "errors/now-dev-no-local-php.md",
    "content": "# It looks like you don't have PHP on your machine.\n\n**Why This Error Occurred**\n\nYou ran `now dev` on a machine where PHP is not installed.\nFor the time being, this runtime requires a local PHP installation to run the runtime locally.\n\n**Possible Ways to Fix It**\n\n1. Install PHP to your computer\n\n**OSX**\n\n```\nbrew install php@7.4\n```\n\n**Ubuntu**\n\n```\napt-get -y install apt-transport-https lsb-release ca-certificates\nwget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg\nsh -c 'echo \"deb https://packages.sury.org/php/ $(lsb_release -sc) main\" > /etc/apt/sources.list.d/php.list'\napt-get update\napt-get install php7.4-cli php7.4-cgi php7.4-json php7.4-curl php7.4-mbstring\n```\n\n**Fedora**\n\n```\nyum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm\nyum install https://rpms.remirepo.net/enterprise/remi-release-7.rpm\nyum install yum-utils\nyum-config-manager --enable remi-php74\nyum update\nyum install php74-cli php74-cgi php74-json php74-curl php74-mbstring\n```\n\n2. Start PHP built-in Development Server\n\n```sh\nphp -S localhost:8000 api/index.php\n```\n\n**Check that php is in the path**\n\nIf you do have installed PHP but still get this error, check that PHP executable is added to the PATH environment variable.\n"
  },
  {
    "path": "jest.config.js",
    "content": "module.exports = {\n  rootDir: \".\",\n  verbose: true,\n  testEnvironment: \"node\",\n  testMatch: [\n    \"**/test/spec/**/*.js\",\n  ],\n  testPathIgnorePatterns: [\n    \"/errors/\",\n    \"/dist/\",\n    \"/node_modules/\",\n  ],\n  testTimeout: 10000\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"vercel-php\",\n  \"description\": \"Vercel PHP runtime\",\n  \"version\": \"0.9.0\",\n  \"license\": \"MIT\",\n  \"main\": \"./dist/index.js\",\n  \"homepage\": \"https://github.com/vercel-community/php\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/vercel-community/php.git\"\n  },\n  \"keywords\": [\n    \"vercel\",\n    \"php\",\n    \"builder\",\n    \"runtime\",\n    \"serverless\",\n    \"deployment\"\n  ],\n  \"scripts\": {\n    \"watch\": \"tsc --watch\",\n    \"build\": \"tsc\",\n    \"test\": \"jest --silent\",\n    \"test-watch\": \"jest --watch\",\n    \"prepublishOnly\": \"tsc\"\n  },\n  \"files\": [\n    \"dist\",\n    \"conf\"\n  ],\n  \"dependencies\": {\n    \"@libphp/almalinux-9-v85\": \">=0.0.2\"\n  },\n  \"devDependencies\": {\n    \"@types/glob\": \"^9.0.0\",\n    \"@types/node\": \"^22.0.0\",\n    \"@vercel/build-utils\": \"^13.2.9\",\n    \"jest\": \"^30.2.0\",\n    \"typescript\": \"^5.9.3\"\n  }\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "import path from 'path';\nimport {\n  rename,\n  shouldServe,\n  glob,\n  download,\n  Lambda,\n  BuildV3,\n  PrepareCache,\n  getNodeVersion\n} from '@vercel/build-utils';\nimport {\n  getPhpFiles,\n  getLauncherFiles,\n  runComposerInstall,\n  runComposerScripts,\n  readRuntimeFile,\n  modifyPhpIni,\n} from './utils';\n\nconst COMPOSER_FILE = process.env.COMPOSER || 'composer.json';\n\n// ###########################\n// EXPORTS\n// ###########################\n\nexport const version = 3;\n\nexport const build: BuildV3 = async ({\n  files,\n  entrypoint,\n  workPath,\n  config = {},\n  meta = {},\n}) => {\n  // Check if now dev mode is used\n  if (meta.isDev) {\n    console.log(`\n      🐘 vercel dev is not supported right now.\n      Please use PHP built-in development server.\n\n      php -S localhost:8000 api/index.php\n    `);\n    process.exit(255);\n  }\n\n  console.log('🐘 Downloading user files');\n\n  // Collect user provided files\n  const userFiles: RuntimeFiles = await download(files, workPath, meta);\n\n  console.log('🐘 Downloading PHP runtime files');\n\n  // Collect runtime files containing PHP bins and libs\n  const runtimeFiles: RuntimeFiles = {\n    // Append PHP files (bins + shared object)\n    ...await getPhpFiles(),\n\n    // Append launcher files (builtin server, common helpers)\n    ...getLauncherFiles(),\n  };\n\n  // If composer.json is provided try to\n  // - install deps\n  // - run composer scripts\n  if (userFiles[COMPOSER_FILE]) {\n    // Install dependencies (vendor is collected bellow, see harvestedFiles)\n    await runComposerInstall(workPath);\n\n    // Run composer scripts (created files are collected bellow, , see harvestedFiles)\n    await runComposerScripts(userFiles[COMPOSER_FILE], workPath);\n  }\n\n  // Append PHP directives into php.ini\n  if (userFiles['api/php.ini']) {\n    const phpini = await modifyPhpIni(userFiles, runtimeFiles);\n    if (phpini) {\n      runtimeFiles['php/php.ini'] = phpini;\n    }\n  }\n\n  // Collect user files, files creating during build (composer vendor)\n  // and other files and prefix them with \"user\" (/var/task/user folder).\n  const harverstedFiles = rename(\n    await glob('**', {\n      cwd: workPath,\n      ignore: [\n        '.vercel/**',\n        ...(config?.excludeFiles\n          ? Array.isArray(config.excludeFiles)\n            ? config.excludeFiles\n            : [config.excludeFiles]\n          : [\n              'node_modules/**',\n              'now.json',\n              '.nowignore',\n              'vercel.json',\n              '.vercelignore',\n            ]),\n      ],\n    }),\n    name => path.join('user', name)\n  );\n\n  // Show some debug notes during build\n  if (process.env.NOW_PHP_DEBUG === '1') {\n    console.log('🐘 Entrypoint:', entrypoint);\n    console.log('🐘 Config:', config);\n    console.log('🐘 Work path:', workPath);\n    console.log('🐘 Meta:', meta);\n    console.log('🐘 User files:', Object.keys(harverstedFiles));\n    console.log('🐘 Runtime files:', Object.keys(runtimeFiles));\n    console.log('🐘 PHP: php.ini', await readRuntimeFile(runtimeFiles['php/php.ini']));\n  }\n\n  console.log('🐘 Creating lambda');\n  const nodeVersion = await getNodeVersion(workPath);\n\n  const lambda = new Lambda({\n    files: {\n      // Located at /var/task/user\n      ...harverstedFiles,\n      // Located at /var/task/php (php bins + ini + modules)\n      // Located at /var/task/lib (shared libs)\n      ...runtimeFiles\n    },\n    handler: 'launcher.launcher',\n    runtime: nodeVersion.runtime,\n    environment: {\n      NOW_ENTRYPOINT: entrypoint,\n      NOW_PHP_DEV: meta.isDev ? '1' : '0'\n    },\n  });\n\n  return { output: lambda };\n};\n\nexport const prepareCache: PrepareCache = async ({ workPath }) => {\n  return {\n    // Composer\n    ...(await glob('vendor/**', workPath)),\n    ...(await glob('composer.lock', workPath)),\n    // NPM\n    ...(await glob('node_modules/**', workPath)),\n    ...(await glob('package-lock.json', workPath)),\n    ...(await glob('yarn.lock', workPath)),\n    ...(await glob('pnpm-lock.yaml', workPath)),\n    // Bun\n    ...(await glob('bun.lock', workPath)),\n    /* in case still used */\n    ...(await glob('bun.lockb', workPath)),\n  };\n};\n\nexport { shouldServe };\n"
  },
  {
    "path": "src/launchers/builtin.ts",
    "content": "import http from 'http';\nimport { spawn, ChildProcess, SpawnOptions } from 'child_process';\nimport net from 'net';\nimport {\n  getPhpDir,\n  getUserDir,\n  normalizeEvent,\n  transformFromAwsRequest,\n  transformToAwsResponse,\n  isDev\n} from './helpers';\nimport { join as pathJoin } from 'path';\n\nlet server: ChildProcess;\n\nasync function startServer(entrypoint: string): Promise<ChildProcess> {\n  // Resolve document root and router\n  const router = entrypoint;\n  const docroot = pathJoin(getUserDir(), process.env.VERCEL_PHP_DOCROOT ?? '');\n\n  console.log(`🐘 Spawning: PHP Built-In Server at ${docroot} (document root) and ${router} (router)`);\n\n  // php spawn options\n  const options: SpawnOptions = {\n    stdio: ['pipe', 'pipe', 'pipe'],\n    env: {\n      ...process.env,\n      LD_LIBRARY_PATH: `/var/task/lib:${process.env.LD_LIBRARY_PATH}`\n    }\n  };\n\n  // now vs now-dev\n  if (!isDev()) {\n    options.env!.PATH = `${getPhpDir()}:${process.env.PATH}`;\n    options.cwd = getPhpDir();\n  } else {\n    options.cwd = getUserDir();\n  }\n\n  // We need to start PHP built-in server with following setup:\n  // php -c php.ini -S ip:port -t /var/task/user /var/task/user/foo/bar.php\n  //\n  // Path to document root lambda task folder with user prefix, because we move all\n  // user files to this folder.\n  //\n  // Path to router is absolute path, because CWD is different.\n  //\n  server = spawn(\n    'php',\n    ['-c', 'php.ini', '-S', '0.0.0.0:3000', '-t', docroot, router],\n    options,\n  );\n\n  server.stdout?.on('data', data => {\n    console.log(`🐘STDOUT: ${data.toString()}`);\n  });\n\n  server.stderr?.on('data', data => {\n    console.error(`🐘STDERR: ${data.toString()}`);\n  });\n\n  server.on('close', function (code, signal) {\n    console.log(`🐘 PHP Built-In Server process closed code ${code} and signal ${signal}`);\n  });\n\n  server.on('error', function (err) {\n    console.error(`🐘 PHP Built-In Server process errored ${err}`);\n  });\n\n  await whenPortOpens(3000, 500);\n\n  process.on('exit', () => {\n    server.kill();\n  })\n\n  return server;\n}\n\nasync function query({ entrypoint, uri, path, headers, method, body }: PhpInput): Promise<PhpOutput> {\n  if (!server) {\n    await startServer(entrypoint);\n  }\n\n  return new Promise(resolve => {\n    const options = {\n      hostname: 'localhost',\n      port: 3000,\n      path,\n      method,\n      headers,\n    };\n\n    console.log(`🐘 Accessing ${uri}`);\n    console.log(`🐘 Querying ${path}`);\n\n    const req = http.request(options, (res) => {\n      const chunks: Uint8Array[] = [];\n\n      res.on('data', (data) => {\n        chunks.push(data);\n      });\n      res.on('end', () => {\n        resolve({\n          statusCode: res.statusCode || 200,\n          headers: res.headers,\n          body: Buffer.concat(chunks)\n        });\n      });\n    });\n\n    req.on('error', (error) => {\n      console.error('🐘 PHP Built-In Server HTTP errored', error);\n      resolve({\n        body: Buffer.from(`PHP Built-In Server HTTP error: ${error}`),\n        headers: {},\n        statusCode: 500\n      });\n    });\n\n    if (body) {\n      req.write(body);\n    }\n\n    req.end();\n  });\n}\n\nfunction whenPortOpensCallback(port: number, attempts: number, cb: (error?: string) => void) {\n  const client = net.connect(port, '127.0.0.1');\n  client.on('error', (error: string) => {\n    if (!attempts) return cb(error);\n    setTimeout(() => {\n      whenPortOpensCallback(port, attempts - 1, cb);\n    }, 10);\n  });\n  client.on('connect', () => {\n    client.destroy();\n    cb();\n  });\n}\n\nfunction whenPortOpens(port: number, attempts: number): Promise<void> {\n  return new Promise((resolve, reject) => {\n    whenPortOpensCallback(port, attempts, (error?: string) => {\n      if (error) {\n        reject(error);\n      } else {\n        resolve();\n      }\n    });\n  });\n}\n\nasync function launcher(event: Event): Promise<AwsResponse> {\n  const awsRequest = normalizeEvent(event);\n  const input = await transformFromAwsRequest(awsRequest);\n  const output = await query(input);\n  return transformToAwsResponse(output);\n}\n\nexports.launcher = launcher;\n\n// (async function () {\n//   const response = await launcher({\n//     Action: \"test\",\n//     httpMethod: \"GET\",\n//     body: \"\",\n//     path: \"/\",\n//     host: \"https://vercel.com\",\n//     headers: {\n//       'HOST': 'vercel.com'\n//     },\n//     encoding: null,\n//   });\n\n//   console.log(response);\n// })();\n"
  },
  {
    "path": "src/launchers/cgi.ts",
    "content": "import { spawn, SpawnOptions } from 'child_process';\nimport { parse as urlParse } from 'url';\nimport {\n  getPhpDir,\n  getUserDir,\n  normalizeEvent,\n  transformFromAwsRequest,\n  transformToAwsResponse,\n  isDev\n} from './helpers';\n\nfunction createCGIReq({ entrypoint, path, host, method, headers }: CgiInput): CgiRequest {\n  const { query } = urlParse(path);\n\n  const env: Env = {\n    ...process.env,\n    SERVER_ROOT: getUserDir(),\n    DOCUMENT_ROOT: getUserDir(),\n    SERVER_NAME: host,\n    SERVER_PORT: 443,\n    HTTPS: \"On\",\n    REDIRECT_STATUS: 200,\n    SCRIPT_NAME: entrypoint,\n    REQUEST_URI: path,\n    SCRIPT_FILENAME: entrypoint,\n    PATH_TRANSLATED: entrypoint,\n    REQUEST_METHOD: method,\n    QUERY_STRING: query || '',\n    GATEWAY_INTERFACE: \"CGI/1.1\",\n    SERVER_PROTOCOL: \"HTTP/1.1\",\n    PATH: process.env.PATH,\n    SERVER_SOFTWARE: \"Vercel PHP\",\n    LD_LIBRARY_PATH: process.env.LD_LIBRARY_PATH\n  };\n\n  if (headers[\"content-length\"]) {\n    env.CONTENT_LENGTH = headers[\"content-length\"];\n  }\n\n  if (headers[\"content-type\"]) {\n    env.CONTENT_TYPE = headers[\"content-type\"];\n  }\n\n  if (headers[\"x-real-ip\"]) {\n    env.REMOTE_ADDR = headers[\"x-real-ip\"];\n  }\n\n  // expose request headers\n  Object.keys(headers).forEach(function (header) {\n    var name = \"HTTP_\" + header.toUpperCase().replace(/-/g, \"_\");\n    env[name] = headers[header];\n  });\n\n  return {\n    env\n  }\n}\n\nfunction parseCGIResponse(response: Buffer) {\n  const headersPos = response.indexOf(\"\\r\\n\\r\\n\");\n  if (headersPos === -1) {\n    return {\n      headers: {},\n      body: response,\n      statusCode: 200\n    }\n  }\n\n  let statusCode = 200;\n  const rawHeaders = response.slice(0, headersPos).toString();\n  const rawBody = response.slice(headersPos + 4);\n\n  const headers = parseCGIHeaders(rawHeaders);\n\n  if (headers['status']) {\n    statusCode = parseInt(headers['status']) || 200;\n  }\n\n  return {\n    headers,\n    body: rawBody,\n    statusCode\n  }\n}\n\nfunction parseCGIHeaders(headers: string): CgiHeaders {\n  if (!headers) return {}\n\n  const result: CgiHeaders = {}\n\n  for (let header of headers.split(\"\\n\")) {\n    const index = header.indexOf(':');\n    const key = header.slice(0, index).trim().toLowerCase();\n    const value = header.slice(index + 1).trim();\n\n    // Be careful about header duplication\n    result[key] = value;\n  }\n\n  return result\n}\n\nfunction query({ entrypoint, path, host, headers, method, body }: PhpInput): Promise<PhpOutput> {\n  console.log(`🐘 Spawning: PHP CGI ${entrypoint}`);\n\n  // Transform lambda request to CGI variables\n  const { env } = createCGIReq({ entrypoint, path, host, headers, method })\n\n  // php-cgi spawn options\n  const options: SpawnOptions = {\n    stdio: ['pipe', 'pipe', 'pipe'],\n    env: env\n  };\n\n  // now vs now-dev\n  if (!isDev()) {\n    options.env!.PATH = `${getPhpDir()}:${process.env.PATH}`;\n    options.cwd = getPhpDir();\n  } else {\n    options.cwd = getUserDir();\n  }\n\n  return new Promise((resolve) => {\n    const chunks: Uint8Array[] = [];\n\n    const php = spawn(\n      'php-cgi',\n      [entrypoint],\n      options,\n    );\n\n    // Validate pipes [stdin]\n    if (!php.stdin) {\n      console.error(`🐘 Fatal error. PHP CGI child process has no stdin.`);\n      process.exit(253);\n    }\n\n    // Validate pipes [stdout]\n    if (!php.stdout) {\n      console.error(`🐘 Fatal error. PHP CGI child process has no stdout.`);\n      process.exit(254);\n    }\n\n    // Validate pipes [stderr]\n    if (!php.stderr) {\n      console.error(`🐘 Fatal error. PHP CGI child process has no stderr.`);\n      process.exit(255);\n    }\n\n    // Output\n    php.stdout.on('data', data => {\n      chunks.push(data);\n    });\n\n    // Logging\n    php.stderr.on('data', data => {\n      console.error(`🐘 PHP CGI stderr`, data.toString());\n    });\n\n    // PHP script execution end\n    php.on('close', (code, signal) => {\n      if (code !== 0) {\n        console.log(`🐘 PHP CGI process closed code ${code} and signal ${signal}`);\n      }\n\n      const { headers, body, statusCode } = parseCGIResponse(Buffer.concat(chunks));\n\n      resolve({\n        body,\n        headers,\n        statusCode\n      });\n    });\n\n    php.on('error', err => {\n      console.error('🐘 PHP CGI errored', err);\n      resolve({\n        body: Buffer.from(`🐘 PHP CGI process errored ${err}`),\n        headers: {},\n        statusCode: 500\n      });\n    });\n\n    // Writes the body into the PHP stdin\n    php.stdin.write(body || '');\n    php.stdin.end();\n  })\n}\n\nasync function launcher(event: Event): Promise<AwsResponse> {\n  const awsRequest = normalizeEvent(event);\n  const input = await transformFromAwsRequest(awsRequest);\n  const output = await query(input);\n  return transformToAwsResponse(output);\n}\n\nexports.createCGIReq = createCGIReq;\nexports.launcher = launcher;\n\n// (async function () {\n//   const response = await launcher({\n//       Action: \"test\",\n//       httpMethod: \"GET\",\n//       body: \"\",\n//       path: \"/\",\n//       host: \"https://vercel.com\",\n//       headers: {\n//           'HOST': 'vercel.com'\n//       },\n//       encoding: null,\n//   });\n\n//   console.log(response);\n// })();\n"
  },
  {
    "path": "src/launchers/cli.ts",
    "content": "import { spawn, SpawnOptions } from 'child_process';\nimport {\n  getPhpDir,\n  normalizeEvent,\n  transformFromAwsRequest,\n  transformToAwsResponse,\n  isDev,\n  getUserDir\n} from './helpers';\n\nfunction query({ entrypoint, body }: PhpInput): Promise<PhpOutput> {\n  console.log(`🐘 Spawning: PHP CLI ${entrypoint}`);\n\n  // php spawn options\n  const options: SpawnOptions = {\n    stdio: ['pipe', 'pipe', 'pipe'],\n    env: process.env\n  };\n\n  // now vs now-dev\n  if (!isDev()) {\n    options.env!.PATH = `${getPhpDir()}:${process.env.PATH}`;\n    options.cwd = getPhpDir();\n  } else {\n    options.cwd = getUserDir();\n  }\n\n  return new Promise((resolve) => {\n    const chunks: Uint8Array[] = [];\n\n    const php = spawn(\n      'php',\n      ['-c', 'php.ini', entrypoint],\n      options,\n    );\n\n    // Validate pipes [stdin]\n    if (!php.stdin) {\n      console.error(`🐘 Fatal error. PHP CLI child process has no stdin.`);\n      process.exit(253);\n    }\n\n    // Validate pipes [stdout]\n    if (!php.stdout) {\n      console.error(`🐘 Fatal error. PHP CLI child process has no stdout.`);\n      process.exit(254);\n    }\n\n    // Validate pipes [stderr]\n    if (!php.stderr) {\n      console.error(`🐘 Fatal error. PHP CLI child process has no stderr.`);\n      process.exit(255);\n    }\n\n    // Output\n    php.stdout.on('data', data => {\n      chunks.push(data);\n    });\n\n    // Logging\n    php.stderr.on('data', data => {\n      console.error(`🐘 PHP CLI stderr`, data.toString());\n    });\n\n    // PHP script execution end\n    php.on('close', (code, signal) => {\n      if (code !== 0) {\n        console.log(`🐘 PHP CLI process closed code ${code} and signal ${signal}`);\n      }\n\n      resolve({\n        statusCode: 200,\n        headers: {},\n        body: Buffer.concat(chunks)\n      });\n    });\n\n    php.on('error', err => {\n      console.error('🐘 PHP CLI errored', err);\n      resolve({\n        body: Buffer.from(`🐘 PHP CLI process errored ${err}`),\n        headers: {},\n        statusCode: 500\n      });\n    });\n\n    // Writes the body into the PHP stdin\n    php.stdin.write(body || '');\n    php.stdin.end();\n  })\n}\n\nasync function launcher(event: Event): Promise<AwsResponse> {\n  const awsRequest = normalizeEvent(event);\n  const input = await transformFromAwsRequest(awsRequest);\n  const output = await query(input);\n  return transformToAwsResponse(output);\n}\n\nexports.launcher = launcher;\n\n// (async function () {\n//   const response = await launcher({\n//       Action: \"test\",\n//       httpMethod: \"GET\",\n//       body: \"\",\n//       path: \"/\",\n//       host: \"https://vercel.com\",\n//       headers: {\n//           'HOST': 'vercel.com'\n//       },\n//       encoding: null,\n//   });\n\n//   console.log(response);\n// })();\n"
  },
  {
    "path": "src/launchers/helpers.ts",
    "content": "import { join as pathJoin } from 'path';\n\nexport const getUserDir = (): string => pathJoin(process.env.LAMBDA_TASK_ROOT || '/', 'user');\nexport const getPhpDir = (): string => pathJoin(process.env.LAMBDA_TASK_ROOT || '/', 'php');\nexport const isDev = (): boolean => process.env.NOW_PHP_DEV === '1';\n\nexport function normalizeEvent(event: Event): AwsRequest {\n  if (event.Action === 'Invoke') {\n    const invokeEvent = JSON.parse(event.body);\n\n    const {\n      method, path, host, headers = {}, encoding,\n    }: InvokedEvent = invokeEvent;\n\n    let { body } = invokeEvent;\n\n    if (body) {\n      if (encoding === 'base64') {\n        body = Buffer.from(body, encoding);\n      } else if (encoding === undefined) {\n        body = Buffer.from(body);\n      } else {\n        throw new Error(`Unsupported encoding: ${encoding}`);\n      }\n    }\n\n    return {\n      method,\n      path,\n      host,\n      headers,\n      body,\n    };\n  }\n\n  const {\n    httpMethod: method, path, host, headers = {}, body,\n  } = event;\n\n  return {\n    method,\n    path,\n    host,\n    headers,\n    body,\n  };\n}\n\nexport async function transformFromAwsRequest({\n  method, path, host, headers, body,\n}: AwsRequest): Promise<PhpInput> {\n  if (!process.env.NOW_ENTRYPOINT) {\n    console.error('Missing ENV NOW_ENTRYPOINT');\n  }\n\n  const entrypoint = pathJoin(\n    getUserDir(),\n    process.env.NOW_ENTRYPOINT || 'index.php',\n  );\n\n  const uri = host + path;\n\n  return { entrypoint, uri, path, host, method, headers, body };\n}\n\nexport function transformToAwsResponse({ statusCode, headers, body }: PhpOutput): AwsResponse {\n  return { statusCode, headers, body: body.toString('base64'), encoding: 'base64' };\n}\n"
  },
  {
    "path": "src/types.d.ts",
    "content": "type Headers = { [k: string]: string | string[] | undefined };\n\ninterface UserFiles {\n  [filePath: string]: import('@vercel/build-utils').File;\n}\n\ninterface RuntimeFiles {\n  [filePath: string]: import('@vercel/build-utils').File;\n}\n\ninterface IncludedFiles {\n  [filePath: string]: import('@vercel/build-utils').File;\n}\n\ninterface MetaOptions {\n  meta: import('@vercel/build-utils').Meta;\n}\n\ninterface AwsRequest {\n  method: string,\n  path: string,\n  host: string,\n  headers: Headers,\n  body: string,\n}\n\ninterface AwsResponse {\n  statusCode: number,\n  headers: Headers,\n  body: string,\n  encoding?: string\n}\n\ninterface Event {\n  Action: string,\n  body: string,\n  httpMethod: string,\n  path: string,\n  host: string,\n  headers: Headers,\n  encoding: string | undefined | null,\n}\n\ninterface InvokedEvent {\n  method: string,\n  path: string,\n  host: string,\n  headers: Headers,\n  encoding: string | undefined | null,\n}\n\ninterface CgiInput {\n  entrypoint: string,\n  path: string,\n  host: string,\n  method: string,\n  headers: Headers,\n}\n\ninterface PhpInput {\n  entrypoint: string,\n  path: string,\n  uri: string,\n  host: string,\n  method: string,\n  headers: Headers,\n  body: string,\n}\n\ninterface PhpOutput {\n  statusCode: number,\n  headers: Headers,\n  body: Buffer,\n}\n\ninterface CgiHeaders {\n  [k: string]: string,\n}\n\ninterface CgiRequest {\n  env: Env,\n}\n\ninterface Env {\n  [k: string]: any,\n}\n\ninterface PhpIni {\n  [k: string]: any,\n}\n"
  },
  {
    "path": "src/utils.ts",
    "content": "import path from 'path';\nimport { spawn, SpawnOptions } from 'child_process';\nimport { File, FileFsRef, FileBlob } from '@vercel/build-utils';\nimport * as libphp from \"@libphp/almalinux-9-v85\";\n\nconst PHP_PKG = path.dirname(require.resolve('@libphp/almalinux-9-v85/package.json'));\nconst PHP_BIN_DIR = path.join(PHP_PKG, \"native/php\");\nconst PHP_MODULES_DIR = path.join(PHP_BIN_DIR, \"modules\");\nconst PHP_LIB_DIR = path.join(PHP_PKG, \"native/lib\");\nconst COMPOSER_BIN = path.join(PHP_BIN_DIR, \"composer\");\n\nexport async function getPhpFiles(): Promise<RuntimeFiles> {\n  const files = await libphp.getFiles();\n\n  // Drop CGI + FPM from libphp, it's not needed for our case\n  delete files['php/php-cgi'];\n  delete files['php/php-fpm'];\n  delete files['php/php-fpm.ini'];\n\n  const runtimeFiles: RuntimeFiles = {};\n\n  // Map from @libphp to Vercel's File objects\n  for (const [filename, filepath] of Object.entries(files)) {\n    runtimeFiles[filename] = new FileFsRef({\n      fsPath: filepath\n    })\n  }\n\n  // Set some bins executable\n  (runtimeFiles['php/php'] as FileFsRef).mode = 33261; // 0755;\n  (runtimeFiles['php/composer'] as FileFsRef).mode = 33261; // 0755;\n\n  return runtimeFiles;\n}\n\nexport function getLauncherFiles(): RuntimeFiles {\n  const files: RuntimeFiles = {\n    'helpers.js': new FileFsRef({\n      fsPath: path.join(__dirname, 'launchers/helpers.js'),\n    })\n  }\n\n  files['launcher.js'] = new FileFsRef({\n    fsPath: path.join(__dirname, 'launchers/builtin.js'),\n  });\n\n  return files;\n}\n\nexport async function modifyPhpIni(userFiles: UserFiles, runtimeFiles: RuntimeFiles): Promise<FileBlob | undefined> {\n  // Validate user files contains php.ini\n  if (!userFiles['api/php.ini']) return;\n\n  // Validate runtime contains php.ini\n  if (!runtimeFiles['php/php.ini']) return;\n\n  const phpiniBlob = await FileBlob.fromStream({\n    stream: runtimeFiles['php/php.ini'].toStream(),\n  });\n\n  const userPhpiniBlob = await FileBlob.fromStream({\n    stream: userFiles['api/php.ini'].toStream(),\n  });\n\n  return new FileBlob({\n    data: phpiniBlob.data.toString()\n      .concat(\"; [User]\\n\")\n      .concat(userPhpiniBlob.data.toString())\n  });\n}\n\nexport async function runComposerInstall(workPath: string): Promise<void> {\n  console.log('🐘 Installing Composer dependencies [START]');\n\n  // @todo PHP_COMPOSER_INSTALL env\n  await runPhp(\n    [\n      COMPOSER_BIN,\n      'install',\n      '--profile',\n      '--no-dev',\n      '--no-interaction',\n      '--no-scripts',\n      '--ignore-platform-reqs',\n      '--no-progress'\n    ],\n    {\n      stdio: 'inherit',\n      cwd: workPath\n    }\n  );\n\n  console.log('🐘 Installing Composer dependencies [DONE]');\n}\n\nexport async function runComposerScripts(composerFile: File, workPath: string): Promise<void> {\n  let composer;\n\n  try {\n    composer = JSON.parse(await readRuntimeFile(composerFile));\n  } catch (e) {\n    console.error('🐘 Composer file is not valid JSON');\n    console.error(e);\n    return;\n  }\n\n  if (composer?.scripts?.vercel) {\n    console.log('🐘 Running composer scripts [START]');\n\n    await runPhp(\n      [COMPOSER_BIN, 'run', 'vercel'],\n      {\n        stdio: 'inherit',\n        cwd: workPath\n      }\n    );\n\n    console.log('🐘 Running composer scripts [DONE]');\n  }\n}\n\nexport async function ensureLocalPhp(): Promise<boolean> {\n  try {\n    await spawnAsync('which', ['php', 'php-cgi'], { stdio: 'pipe' });\n    return true;\n  } catch (e) {\n    return false;\n  }\n}\n\nexport async function readRuntimeFile(file: File): Promise<string> {\n  const blob = await FileBlob.fromStream({\n    stream: file.toStream(),\n  });\n\n  return blob.data.toString();\n}\n\n// *****************************************************************************\n// PRIVATE API *****************************************************************\n// *****************************************************************************\n\nasync function runPhp(args: ReadonlyArray<string>, opts: SpawnOptions = {}) {\n  try {\n    await spawnAsync('php', args,\n      {\n        ...opts,\n        env: {\n          ...process.env,\n          ...(opts.env || {}),\n          COMPOSER_HOME: '/tmp',\n          PATH: `${PHP_BIN_DIR}:${process.env.PATH}`,\n          PHP_INI_EXTENSION_DIR: PHP_MODULES_DIR,\n          PHP_INI_SCAN_DIR: `:${path.resolve(__dirname, '../conf')}`,\n          LD_LIBRARY_PATH: `${PHP_LIB_DIR}:/usr/lib64:/lib64:${process.env.LD_LIBRARY_PATH}`\n        }\n      }\n    );\n  } catch (e) {\n    console.error(e);\n    process.exit(1);\n  }\n}\n\nfunction spawnAsync(command: string, args: ReadonlyArray<string>, opts: SpawnOptions = {}): Promise<void> {\n  return new Promise((resolve, reject) => {\n    const child = spawn(command, args, {\n      stdio: \"ignore\",\n      ...opts\n    });\n\n    child.on('error', reject);\n    child.on('exit', (code, signal) => {\n      if (code === 0) {\n        resolve();\n      } else {\n        reject(new Error(`Exited with ${code || signal}`));\n      }\n    });\n  })\n}\n"
  },
  {
    "path": "test/examples/00-php/.gitignore",
    "content": "# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/00-php/api/api/index.php",
    "content": "<?php\n\necho \"Hello from subfolder!\";\n"
  },
  {
    "path": "test/examples/00-php/api/api/users.php",
    "content": "<?php\n\n$data = [\n    ['id' => 1, 'name' => 'f3l1x'],\n    ['id' => 2, 'name' => 'chemix'],\n    ['id' => 3, 'name' => 'dg'],\n    ['id' => 4, 'name' => 'milo'],\n    ['id' => 5, 'name' => 'matej21'],\n    ['id' => 6, 'name' => 'merxes'],\n];\n\nheader('Content-Type: application/json');\n\necho json_encode($data);\n"
  },
  {
    "path": "test/examples/00-php/api/ext/ds.php",
    "content": "<?php\n$map = new \\Ds\\Map([\"a\" => 1, \"b\" => 2, \"c\" => 3]);\n$map->apply(function($key, $value) { return $value * 2; });\n\nprint_r($map);\n?>"
  },
  {
    "path": "test/examples/00-php/api/ext/gd.php",
    "content": "<?php header (\"Content-type: image/png\");\n$handle = ImageCreate (130, 50) or die (\"Cannot Create image\");\n$bg_color = ImageColorAllocate ($handle, 255, 0, 0);\n$txt_color = ImageColorAllocate ($handle, 255, 255, 255);\n$line_color = ImageColorAllocate ($handle, 0, 0, 0);\nImageLine($handle, 65, 0, 130, 50, $line_color);\nImageString ($handle, 5, 5, 18, \"PHP.About.com\", $txt_color);\nImagePng ($handle);\n?>"
  },
  {
    "path": "test/examples/00-php/api/ext/index.php",
    "content": "<?php\necho implode(',', get_loaded_extensions());\n?>\n"
  },
  {
    "path": "test/examples/00-php/api/ext/phalcon.php",
    "content": "<?php\n\nuse Phalcon\\Mvc\\Micro;\n\n$app = new Micro();\n\n$app->get(\n    \"/\",\n    function () {\n        echo \"<h1>Welcome!</h1>\";\n    }\n);\n\n$app->get(\n    \"/say/hello/{name}\",\n    function ($name) use ($app) {\n        echo \"<h1>Hello! $name</h1>\";\n        echo \"Your IP Address is \", $app->request->getClientAddress();\n    }\n);\n\n$app->post(\n    \"/store/something\",\n    function () use ($app) {\n        $name = $app->request->getPost(\"name\");\n        echo \"<h1>Hello! $name</h1>\";\n    }\n);\n\n$app->notFound(\n    function () use ($app) {\n        $app->response->setStatusCode(404, \"Not Found\");\n        $app->response->sendHeaders();\n        echo \"This is crazy, but this page was not found!\";\n    }\n);\n\n$app->handle();\n"
  },
  {
    "path": "test/examples/00-php/api/hello.php",
    "content": "<?php echo 'Hello from PHP on Now 2.0!';\n"
  },
  {
    "path": "test/examples/00-php/api/index.php",
    "content": "<?php\nphpinfo();\nvar_dump(opcache_get_status());"
  },
  {
    "path": "test/examples/00-php/api/ini/index.php",
    "content": "<h1>php.ini</h1>\n\n<table>\n<thead>\n    <tr>\n        <th>option</th>\n        <th>global_value</th>\n        <th>local_value</th>\n        <th>access</th>\n    </tr>\n</thead>\n<tbody>\n<?php\nforeach (ini_get_all() as $name => $group) {\n    echo \"<tr>\";\n    echo sprintf('<td>%s</td>', $name);\n    echo sprintf('<td>%s</td>', $group['global_value']);\n    echo sprintf('<td>%s</td>', $group['local_value']);\n    echo sprintf('<td>%s</td>', $group['access']);\n    echo \"</tr>\";\n}\n?>\n</tbody>\n</table>\n"
  },
  {
    "path": "test/examples/00-php/api/libs.php",
    "content": "<?php\n\n$path = getenv('LD_LIBRARY_PATH') ?? '';\n$paths = explode(':', $path);\n\nforeach ($paths as $path) {\n    if (!is_dir($path)) {\n        echo \"No folder: $path<br>\";\n    } else {\n        $files = scandir($path);\n        echo \"Scan folder: $path<br>\";\n        array_map(function($file) {\n            echo \"- $file <br>\";\n        }, $files);\n    }\n    echo \"<hr>\";\n}\n"
  },
  {
    "path": "test/examples/00-php/api/test.php",
    "content": "<?php\n\nheader('X-PHP: test');\nhttp_response_code($_GET['code'] ?? 200);\n\necho 'Test output';\n\nvar_dump(file_get_contents('php://input'));\nvar_dump($_GET);\nvar_dump($_POST);\nvar_dump($_SERVER);\nvar_dump($_ENV);\nvar_dump($_FILES);\nvar_dump(opcache_get_status());\n"
  },
  {
    "path": "test/examples/00-php/vercel.json",
    "content": "{\n  \"functions\": {\n    \"api/**/*.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"routes\": [\n    { \"src\": \"/api/(.*)\", \"dest\": \"/api/api/$1\" },\n    { \"src\": \"/ini/(.*)\", \"dest\": \"/api/ini/$1\" },\n    { \"src\": \"/ext/(.*)\", \"dest\": \"/api/ext/$1\" },\n    { \"src\": \"/(.*)\", \"dest\": \"/api/$1\" }\n  ],\n  \"env\": {\n    \"NOW_PHP_FOO\": \"bar3\"\n  },\n  \"build\": {\n    \"env\": {\n      \"NOW_PHP_DEBUG\": \"1\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/examples/00-test/.gitignore",
    "content": "# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/00-test/api/hey.txt",
    "content": "Test\n"
  },
  {
    "path": "test/examples/00-test/api/index.php",
    "content": "<?php\nphpinfo();\nvar_dump(opcache_get_status());\n"
  },
  {
    "path": "test/examples/00-test/api/php.ini",
    "content": "memory_limit=1024M\n"
  },
  {
    "path": "test/examples/00-test/api/test.php",
    "content": "<?php\n\n$pattern = $_GET['pattern'] ?? '*.*';\n\nforeach (glob($pattern) as $filename) {\n    echo \"$filename size \" . filesize($filename) . \"\\n\";\n}\n"
  },
  {
    "path": "test/examples/00-test/src/foo.txt",
    "content": "Foo\n"
  },
  {
    "path": "test/examples/00-test/vercel.json",
    "content": "{\n  \"functions\": {\n    \"api/*.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"routes\": [\n    { \"src\": \"/txt\", \"dest\": \"/api/php.txt\" },\n    { \"src\": \"/ini\", \"dest\": \"/api/php.ini\" },\n    { \"src\": \"/(.*)\", \"dest\": \"/api/$1\" }\n  ],\n  \"build\": {\n    \"env\": {\n      \"NOW_PHP_DEBUG\": \"1\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/examples/01-cowsay/index.php",
    "content": "<?php\nprint('cow:RANDOMNESS_PLACEHOLDER');\n"
  },
  {
    "path": "test/examples/01-cowsay/subdirectory/index.php",
    "content": "<?php\nprint('yoda:RANDOMNESS_PLACEHOLDER');\n"
  },
  {
    "path": "test/examples/01-cowsay/vercel.json",
    "content": "{\n  \"version\": 2,\n  \"builds\": [\n    { \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" },\n    { \"src\": \"subdirectory/index.php\", \"use\": \"vercel-php@0.7.3\" }\n  ]\n}\n"
  },
  {
    "path": "test/examples/02-extensions/index.php",
    "content": "<?php\necho implode(',', get_loaded_extensions());\n?>\n"
  },
  {
    "path": "test/examples/02-extensions/vercel.json",
    "content": "{\n  \"version\": 2,\n  \"builds\": [{ \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" }]\n}\n"
  },
  {
    "path": "test/examples/03-env-vars/env/index.php",
    "content": "<?php\nprint($_ENV['RANDOMNESS_ENV_VAR'] . ':env');\n"
  },
  {
    "path": "test/examples/03-env-vars/vercel.json",
    "content": "{\n  \"version\": 2,\n  \"builds\": [{ \"src\": \"env/index.php\", \"use\": \"vercel-php@0.7.3\" }]\n}\n"
  },
  {
    "path": "test/examples/04-include-files/excluded_file.php",
    "content": "<?php\n\necho 'Excluded!';\n"
  },
  {
    "path": "test/examples/04-include-files/included_file.php",
    "content": "<?php\n\necho 'included:RANDOMNESS_PLACEHOLDER';\n"
  },
  {
    "path": "test/examples/04-include-files/index.php",
    "content": "<?php\n\necho 'mainfile:';\nif (file_exists('included_file.php') && !file_exists('excluded_file.php')) {\n  require_once 'included_file.php';\n} else {\n  echo PHP_EOL;\n  print_r(array_diff(scandir('.'), array('..', '.')));\n}\n"
  },
  {
    "path": "test/examples/04-include-files/vercel.json",
    "content": "{\n  \"version\": 2,\n  \"builds\": [\n    {\n      \"src\": \"index.php\",\n      \"use\": \"vercel-php@0.7.3\",\n      \"config\": { \"includeFiles\": [\"included*.php\"] }\n    }\n  ]\n}\n"
  },
  {
    "path": "test/examples/05-globals/index.php",
    "content": "<?php\nheader('Content-Type: text/plain');\nprint($_SERVER['SCRIPT_FILENAME'] . PHP_EOL);\nprint($_SERVER['REQUEST_METHOD'] . PHP_EOL);\nprint($_SERVER['REQUEST_URI'] . PHP_EOL);\nprint($_SERVER['HTTP_HOST'] . PHP_EOL);\nprint($_SERVER['HTTP_X_SOME_HEADER'] . PHP_EOL);\nprint($_SERVER['SERVER_PROTOCOL'] . PHP_EOL);\nprint($_SERVER['SERVER_NAME'] . PHP_EOL);\nprint($_SERVER['SERVER_PORT'] . PHP_EOL);\nprint($_SERVER['HTTPS'] . PHP_EOL);\n\nprint($_GET['get1'] . PHP_EOL);\nvar_dump($_GET['get2']);\nprint($_POST['post1'] . PHP_EOL);\nvar_dump($_POST['post2']);\nprint($_COOKIE['cookie1'] . PHP_EOL);\nvar_dump($_COOKIE['cookie2']);\n\nprint($_REQUEST['get1'] . PHP_EOL);\nvar_dump($_REQUEST['get2']);\nprint($_REQUEST['post1'] . PHP_EOL);\nvar_dump($_REQUEST['post2']);\nprint($_REQUEST['cookie1'] . PHP_EOL);\nvar_dump($_REQUEST['cookie2']);\n\nprint(file_get_contents('php://input') . PHP_EOL);\nprint('end' . PHP_EOL);\n"
  },
  {
    "path": "test/examples/05-globals/vercel.json",
    "content": "{\n  \"version\": 2,\n  \"builds\": [{ \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" }]\n}\n"
  },
  {
    "path": "test/examples/06-setcookie/index.php",
    "content": "<?php\nheader('Content-Type: text/plain');\nheader('Content-Type: text/plain; charset=UTF-16');\nsetcookie('cookie1', 'cookie1value');\nsetcookie('cookie2', 'cookie2value');\n"
  },
  {
    "path": "test/examples/06-setcookie/vercel.json",
    "content": "{\n  \"version\": 2,\n  \"builds\": [{ \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" }]\n}\n"
  },
  {
    "path": "test/examples/07-function/index.php",
    "content": "<?php\n\n// regression test for go-php engine reusage. on failure prints\n// Fatal error: Cannot redeclare some_function() (previously declared in /var/task/user/index.php:7)\n\nfunction some_function() {\n  print(\"paskantamasaari\");\n}\n\nsome_function();\n"
  },
  {
    "path": "test/examples/07-function/vercel.json",
    "content": "{\n  \"version\": 2,\n  \"builds\": [{ \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" }]\n}\n"
  },
  {
    "path": "test/examples/08-opcache/index.php",
    "content": "<?php\nvar_dump(opcache_compile_file(__FILE__));\n"
  },
  {
    "path": "test/examples/08-opcache/vercel.json",
    "content": "{\n  \"version\": 2,\n  \"builds\": [{ \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" }]\n}\n"
  },
  {
    "path": "test/examples/09-routes/index.php",
    "content": "<?php\nprint('cow:RANDOMNESS_PLACEHOLDER:' . $_SERVER['REQUEST_URI']);\n"
  },
  {
    "path": "test/examples/09-routes/vercel.json",
    "content": "{\n  \"version\": 2,\n  \"builds\": [{ \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" }],\n  \"routes\": [{ \"src\": \"/(.*)\", \"dest\": \"index.php\" }]\n}\n"
  },
  {
    "path": "test/examples/10-composer-builds/.gitignore",
    "content": "vendor\n"
  },
  {
    "path": "test/examples/10-composer-builds/.vercelignore",
    "content": "vendor\n"
  },
  {
    "path": "test/examples/10-composer-builds/composer.json",
    "content": "{\n  \"require\": {\n    \"psr/log\": \"^1.1\"\n  }\n}\n"
  },
  {
    "path": "test/examples/10-composer-builds/index.php",
    "content": "<?php\nheader('content-type: text/plain');\n\nrequire_once('vendor/autoload.php');\n\nvar_dump(interface_exists('Psr\\Log\\LoggerInterface'));\n"
  },
  {
    "path": "test/examples/10-composer-builds/vercel.json",
    "content": "{\n  \"version\": 2,\n  \"builds\": [{ \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" }],\n  \"build\": {\n    \"env\": {\n      \"NOW_BUILDER_DEBUG\": \"1\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/examples/11-composer-env/.gitignore",
    "content": "vendor\n.vercel\n"
  },
  {
    "path": "test/examples/11-composer-env/.vercelignore",
    "content": "vendor\n"
  },
  {
    "path": "test/examples/11-composer-env/composer-test.json",
    "content": "{\n  \"require\": {\n    \"psr/log\": \"^1.1\"\n  }\n}\n"
  },
  {
    "path": "test/examples/11-composer-env/index.php",
    "content": "<?php\n/*\n * What is the goal of this test?\n *\n * It should verify that build environment variables are passed along correctly.\n * So in this case the variable `COMPOSER` is set to composer.json file with a non-standard value\n * (`composer-test.json` in this case)\n *\n * An empty composer.json file is added to trigger composer installation. Since it is empty, the test fails when\n * the `COMPOSER` variable is not set.\n */\n\nheader('content-type: text/plain');\n\nrequire_once('vendor/autoload.php');\n\nvar_dump(interface_exists('Psr\\Log\\LoggerInterface'));\n"
  },
  {
    "path": "test/examples/11-composer-env/vercel.json",
    "content": "{\n  \"version\": 2,\n  \"builds\": [{ \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" }],\n  \"build\": {\n    \"env\": {\n      \"COMPOSER\": \"composer-test.json\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/examples/12-composer/.gitignore",
    "content": "# PHP\n/vendor\n\n# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/12-composer/.vercelignore",
    "content": "/vendor\n"
  },
  {
    "path": "test/examples/12-composer/api/index.php",
    "content": "OK\n"
  },
  {
    "path": "test/examples/12-composer/composer.json",
    "content": "{\n  \"require\": {\n    \"php\": \"^7.4\",\n    \"tracy/tracy\": \"^2.6.0\"\n  }\n}\n"
  },
  {
    "path": "test/examples/12-composer/vercel.json",
    "content": "{\n  \"functions\": {\n    \"api/index.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"routes\": [\n    { \"src\": \"/(.*)\",  \"dest\": \"/api/index.php\" }\n  ],\n  \"build\": {\n    \"env\": {\n      \"NOW_PHP_DEBUG\": \"1\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/examples/13-composer-scripts/.gitignore",
    "content": "# PHP\n/vendor\n\n# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/13-composer-scripts/.vercelignore",
    "content": "/vendor\n"
  },
  {
    "path": "test/examples/13-composer-scripts/api/index.php",
    "content": "OK\n"
  },
  {
    "path": "test/examples/13-composer-scripts/composer.json",
    "content": "{\n  \"require\": {\n    \"php\": \"^7.4\",\n    \"tracy/tracy\": \"^2.6.0\"\n  },\n  \"scripts\": {\n    \"vercel\": [\n      \"@php --ini\",\n      \"@php -m\"\n    ]\n  }\n}\n"
  },
  {
    "path": "test/examples/13-composer-scripts/vercel.json",
    "content": "{\n  \"functions\": {\n    \"api/index.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"routes\": [\n    { \"src\": \"/(.*)\",  \"dest\": \"/api/index.php\" }\n  ],\n  \"build\": {\n    \"env\": {\n      \"NOW_PHP_DEBUG\": \"1\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/examples/14-folders/.gitignore",
    "content": "# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/14-folders/api/index.php",
    "content": "<?php\n\necho \"/\";\n"
  },
  {
    "path": "test/examples/14-folders/api/users/index.php",
    "content": "<?php\n\necho \"/user/\";\n"
  },
  {
    "path": "test/examples/14-folders/api/users/users.php",
    "content": "<?php\n\necho \"/user/users.php\";\n"
  },
  {
    "path": "test/examples/14-folders/vercel.json",
    "content": "{\n  \"functions\": {\n    \"api/**/*.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"routes\": [\n    { \"src\": \"/(.*)\", \"dest\": \"/api/$1\" }\n  ],\n  \"build\": {\n    \"env\": {\n      \"NOW_PHP_DEBUG\": \"1\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/examples/16-php-ini/.gitignore",
    "content": "# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/16-php-ini/api/index.php",
    "content": "<?php\nphpinfo();\nvar_dump(opcache_get_status());\n"
  },
  {
    "path": "test/examples/16-php-ini/api/php.ini",
    "content": "memory_limit=1024M\n"
  },
  {
    "path": "test/examples/16-php-ini/vercel.json",
    "content": "{\n  \"functions\": {\n    \"api/*.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"routes\": [\n    { \"src\": \"/(.*)\",  \"dest\": \"/api/index.php\" }\n  ],\n  \"build\": {\n    \"env\": {\n      \"NOW_PHP_DEBUG\": \"1\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/examples/17-zero/.gitignore",
    "content": "# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/17-zero/api/index.php",
    "content": "<?php\n\necho \"Index PHP\";\n"
  },
  {
    "path": "test/examples/17-zero/api/test.html",
    "content": "Test HTML\n"
  },
  {
    "path": "test/examples/17-zero/src/index.txt",
    "content": "Index HTML\n"
  },
  {
    "path": "test/examples/17-zero/vercel.json",
    "content": "{\n  \"functions\": {\n    \"api/**/*.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"build\": {\n    \"env\": {\n      \"NOW_PHP_DEBUG\": \"1\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/examples/18-exclude-files/.gitignore",
    "content": "# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/18-exclude-files/.vercelignore",
    "content": "/baz\n"
  },
  {
    "path": "test/examples/18-exclude-files/api/index.php",
    "content": "<?php\n\necho \"Index PHP\";\n"
  },
  {
    "path": "test/examples/18-exclude-files/baz/index.html",
    "content": "Ignored by .vercelignore\n"
  },
  {
    "path": "test/examples/18-exclude-files/foo/index.txt",
    "content": "Ignore by excludedFiles\n"
  },
  {
    "path": "test/examples/18-exclude-files/vercel.json",
    "content": "{\n  \"functions\": {\n    \"api/**/*.php\": {\n      \"runtime\": \"vercel-php@0.7.3\",\n      \"excludeFiles\": \"foo/**\"\n    }\n  },\n  \"build\": {\n    \"env\": {\n      \"NOW_PHP_DEBUG\": \"1\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/examples/19-server-workers/.gitignore",
    "content": "# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/19-server-workers/api/index.php",
    "content": "<?php\nphpinfo();\nvar_dump(opcache_get_status());\n"
  },
  {
    "path": "test/examples/19-server-workers/vercel.json",
    "content": "{\n  \"functions\": {\n    \"api/*.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"env\": {\n    \"PHP_CLI_SERVER_WORKERS\": \"4\"\n  },\n  \"build\": {\n    \"env\": {\n      \"NOW_PHP_DEBUG\": \"1\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/examples/20-read-files/.gitignore",
    "content": "# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/20-read-files/api/index.php",
    "content": "<?php\n\nheader('Content-Type: application/json');\n\necho file_get_contents(__DIR__ . '/../src/users.json');\n"
  },
  {
    "path": "test/examples/20-read-files/src/users.json",
    "content": "[\n  {\n    \"id\": 1,\n    \"name\": \"Felix\"\n  },\n  {\n    \"id\": 2,\n    \"name\": \"Chuck\"\n  }\n]\n"
  },
  {
    "path": "test/examples/20-read-files/vercel.json",
    "content": "{\n  \"functions\": {\n    \"api/*.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"routes\": [\n    { \"src\": \"/(.*)\", \"dest\": \"/api/$1\" }\n  ]\n}\n"
  },
  {
    "path": "test/spec/index.dev.js",
    "content": "const builder = require('./../../dist/index');\n\ntest('it should failed using now dev', async () => {\n  const mockLog = console.log = jest.fn();\n\n  jest.spyOn(process, 'exit').mockImplementation((code) => {\n      expect(code).toBe(255);\n      expect(mockLog).toHaveBeenCalledTimes(1);\n  });\n\n  await builder.build({\n    files: [],\n    entrypoint: 'test.php',\n    workPath: __dirname,\n    config: {},\n    meta: { isDev: true },\n  });\n});\n"
  },
  {
    "path": "test/spec/index.js",
    "content": "const builder = require('./../../dist/index');\n\ntest('creates simple lambda', async () => {\n  await builder.build({\n    files: [],\n    entrypoint: 'test.php',\n    workPath: __dirname,\n    config: {},\n    meta: {},\n  });\n});\n"
  },
  {
    "path": "test/spec/launchers/cgi.js",
    "content": "const cgi = require('./../../../dist/launchers/cgi');\n\ntest('create CGI request', () => {\n  const request = {\n    entrypoint: \"index.php\",\n    path: \"/index.php\",\n    host: \"https://vercel.com\",\n    method: \"GET\",\n    headers: {}\n  };\n  process.env.CUSTOM_VALUE = \"custom-value\";\n  const { env } = cgi.createCGIReq(request);\n\n  expect(env).toHaveProperty(\"SERVER_ROOT\", \"/user\");\n  expect(env).toHaveProperty(\"DOCUMENT_ROOT\", \"/user\");\n  expect(env).toHaveProperty(\"SERVER_NAME\", request.host);\n  expect(env).toHaveProperty(\"SERVER_PORT\", 443);\n  expect(env).toHaveProperty(\"HTTPS\", 'On');\n  expect(env).toHaveProperty(\"REDIRECT_STATUS\", 200);\n  expect(env).toHaveProperty(\"SCRIPT_NAME\", request.entrypoint);\n  expect(env).toHaveProperty(\"REQUEST_URI\", request.path);\n  expect(env).toHaveProperty(\"SCRIPT_FILENAME\", request.entrypoint);\n  expect(env).toHaveProperty(\"PATH_TRANSLATED\", request.entrypoint);\n  expect(env).toHaveProperty(\"REQUEST_METHOD\", request.method);\n  expect(env).toHaveProperty(\"QUERY_STRING\", '');\n  expect(env).toHaveProperty(\"GATEWAY_INTERFACE\", 'CGI/1.1');\n  expect(env).toHaveProperty(\"SERVER_PROTOCOL\", 'HTTP/1.1');\n  expect(env).toHaveProperty(\"SERVER_SOFTWARE\", 'Vercel PHP');\n  expect(env).toHaveProperty(\"PATH\", process.env.PATH);\n  expect(env).toHaveProperty(\"LD_LIBRARY_PATH\", process.env.LD_LIBRARY_PATH);\n  expect(env).toHaveProperty(\"CUSTOM_VALUE\", process.env.CUSTOM_VALUE);\n});\n"
  },
  {
    "path": "test/spec/path.js",
    "content": "const path = require('path');\n\ntest('relative path', () => {\n  const rootdir = '/var/task/user';\n  const request = '/var/task/user/api/users.php';\n  const file = path.relative(rootdir, request);\n\n  expect(file).toBe('api/users.php');\n});\n"
  },
  {
    "path": "test/spec/url.js",
    "content": "const url = require('url');\n\ntest('url.parse search & query are string', () => {\n  const { search, query } = url.parse('https://vercel.com/?foo=bar&foo2=baz#foo');\n  expect(search).toBe('?foo=bar&foo2=baz');\n  expect(query).toBe('foo=bar&foo2=baz');\n});\n\ntest('url.parse search string, query object', () => {\n  const { search, query } = url.parse('https://vercel.com/?foo=bar&foo2=baz#foo', true);\n  expect(search).toBe('?foo=bar&foo2=baz');\n  expect(query).toMatchObject({ foo: 'bar', 'foo2': 'baz' });\n});\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"resolveJsonModule\": true,\n    \"moduleResolution\": \"node\",\n    \"allowSyntheticDefaultImports\": true,\n    \"lib\": [\n      \"ES2019\"\n    ],\n    \"target\": \"ES2019\",\n    \"module\": \"CommonJS\",\n    \"outDir\": \"dist\",\n    \"sourceMap\": false,\n    \"declaration\": true,\n    \"noImplicitAny\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"skipLibCheck\": true,\n    \"typeRoots\": [\n      \"./node_modules/@types\"\n    ],\n    \"types\": []\n  },\n  \"include\": [\n    \"src/**/*.ts\"\n  ],\n  \"exclude\": [\n    \"errors\",\n    \"dist\",\n    \"node_modules\",\n    \"test\"\n  ]\n}\n"
  }
]