Full Code of juicyfx/now-php for AI

master 2c2f4f8561bd cached
118 files
62.6 KB
22.5k tokens
47 symbols
1 requests
Download .txt
Repository: juicyfx/now-php
Branch: master
Commit: 2c2f4f8561bd
Files: 118
Total size: 62.6 KB

Directory structure:
gitextract_vi8hg7za/

├── .editorconfig
├── .github/
│   ├── .kodiak.toml
│   ├── ISSUE_TEMPLATE/
│   │   ├── Bug.md
│   │   ├── Feature.md
│   │   └── Question.md
│   ├── dependabot.yml
│   └── workflows/
│       └── main.yml
├── .gitignore
├── CHANGELOG.md
├── CLAUDE.md
├── LICENSE
├── Makefile
├── README.md
├── conf/
│   └── build.ini
├── errors/
│   └── now-dev-no-local-php.md
├── jest.config.js
├── package.json
├── src/
│   ├── index.ts
│   ├── launchers/
│   │   ├── builtin.ts
│   │   ├── cgi.ts
│   │   ├── cli.ts
│   │   └── helpers.ts
│   ├── types.d.ts
│   └── utils.ts
├── test/
│   ├── examples/
│   │   ├── 00-php/
│   │   │   ├── .gitignore
│   │   │   ├── api/
│   │   │   │   ├── api/
│   │   │   │   │   ├── index.php
│   │   │   │   │   └── users.php
│   │   │   │   ├── ext/
│   │   │   │   │   ├── ds.php
│   │   │   │   │   ├── gd.php
│   │   │   │   │   ├── index.php
│   │   │   │   │   └── phalcon.php
│   │   │   │   ├── hello.php
│   │   │   │   ├── index.php
│   │   │   │   ├── ini/
│   │   │   │   │   └── index.php
│   │   │   │   ├── libs.php
│   │   │   │   └── test.php
│   │   │   └── vercel.json
│   │   ├── 00-test/
│   │   │   ├── .gitignore
│   │   │   ├── api/
│   │   │   │   ├── hey.txt
│   │   │   │   ├── index.php
│   │   │   │   ├── php.ini
│   │   │   │   └── test.php
│   │   │   ├── src/
│   │   │   │   └── foo.txt
│   │   │   └── vercel.json
│   │   ├── 01-cowsay/
│   │   │   ├── index.php
│   │   │   ├── subdirectory/
│   │   │   │   └── index.php
│   │   │   └── vercel.json
│   │   ├── 02-extensions/
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 03-env-vars/
│   │   │   ├── env/
│   │   │   │   └── index.php
│   │   │   └── vercel.json
│   │   ├── 04-include-files/
│   │   │   ├── excluded_file.php
│   │   │   ├── included_file.php
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 05-globals/
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 06-setcookie/
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 07-function/
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 08-opcache/
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 09-routes/
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 10-composer-builds/
│   │   │   ├── .gitignore
│   │   │   ├── .vercelignore
│   │   │   ├── composer.json
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 11-composer-env/
│   │   │   ├── .gitignore
│   │   │   ├── .vercelignore
│   │   │   ├── composer-test.json
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 12-composer/
│   │   │   ├── .gitignore
│   │   │   ├── .vercelignore
│   │   │   ├── api/
│   │   │   │   └── index.php
│   │   │   ├── composer.json
│   │   │   └── vercel.json
│   │   ├── 13-composer-scripts/
│   │   │   ├── .gitignore
│   │   │   ├── .vercelignore
│   │   │   ├── api/
│   │   │   │   └── index.php
│   │   │   ├── composer.json
│   │   │   └── vercel.json
│   │   ├── 14-folders/
│   │   │   ├── .gitignore
│   │   │   ├── api/
│   │   │   │   ├── index.php
│   │   │   │   └── users/
│   │   │   │       ├── index.php
│   │   │   │       └── users.php
│   │   │   └── vercel.json
│   │   ├── 16-php-ini/
│   │   │   ├── .gitignore
│   │   │   ├── api/
│   │   │   │   ├── index.php
│   │   │   │   └── php.ini
│   │   │   └── vercel.json
│   │   ├── 17-zero/
│   │   │   ├── .gitignore
│   │   │   ├── api/
│   │   │   │   ├── index.php
│   │   │   │   └── test.html
│   │   │   ├── src/
│   │   │   │   └── index.txt
│   │   │   └── vercel.json
│   │   ├── 18-exclude-files/
│   │   │   ├── .gitignore
│   │   │   ├── .vercelignore
│   │   │   ├── api/
│   │   │   │   └── index.php
│   │   │   ├── baz/
│   │   │   │   └── index.html
│   │   │   ├── foo/
│   │   │   │   └── index.txt
│   │   │   └── vercel.json
│   │   ├── 19-server-workers/
│   │   │   ├── .gitignore
│   │   │   ├── api/
│   │   │   │   └── index.php
│   │   │   └── vercel.json
│   │   └── 20-read-files/
│   │       ├── .gitignore
│   │       ├── api/
│   │       │   └── index.php
│   │       ├── src/
│   │       │   └── users.json
│   │       └── vercel.json
│   └── spec/
│       ├── index.dev.js
│       ├── index.js
│       ├── launchers/
│       │   └── cgi.js
│       ├── path.js
│       └── url.js
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# EditorConfig is awesome: http://EditorConfig.org

root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.{html}]
indent_style = tab
indent_size = tab
tab_width = 4

[*.{js,ts,json,yml,yaml,md}]
indent_style = space
indent_size = 2


================================================
FILE: .github/.kodiak.toml
================================================
version = 1

[merge]
automerge_label = "automerge"
blacklist_title_regex = "^WIP.*"
blacklist_labels = ["WIP"]
method = "rebase"
delete_branch_on_merge = true
notify_on_conflict = true
optimistic_updates = false


================================================
FILE: .github/ISSUE_TEMPLATE/Bug.md
================================================
---
name: Bug report 🐛
about: Something is not working as expected!
---

# Bug report

- Version: x.y.z
- URL: Yes (*.now.sh) / No
- Repository: Yes / No

## Description

<!-- Describe it -->


================================================
FILE: .github/ISSUE_TEMPLATE/Feature.md
================================================
---
name: Feature request 🚀
about: I would appreciate new feature or something!
---

# Feature Request

<!-- Describe it -->


================================================
FILE: .github/ISSUE_TEMPLATE/Question.md
================================================
---
name: Question ❓
about: Ask about anything!
---

# Question

<!-- Describe it -->


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: npm
  directory: "/"
  schedule:
    interval: daily
    time: '11:00'
  open-pull-requests-limit: 10
- package-ecosystem: npm
  directory: "/packages/php"
  schedule:
    interval: daily
    time: '11:00'
  open-pull-requests-limit: 10
- package-ecosystem: npm
  directory: "/packages/caddy"
  schedule:
    interval: daily
    time: '11:00'
  open-pull-requests-limit: 10


================================================
FILE: .github/workflows/main.yml
================================================
name: Main workflow

on: [push, pull_request]

jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: '22.x'

      - name: Dependencies
        run: make install

      - name: Build
        run: make build

      - name: Tests
        run: make test


================================================
FILE: .gitignore
================================================
# Node
/node_modules
/package-lock.json

# App
/dist

# Vercel
.vercel


================================================
FILE: CHANGELOG.md
================================================
# Changelog

### [0.9.0] - 2026-01-15

- PHP 8.5

### [0.8.0] - 2026-01-15

- PHP 8.4

### [0.7.4] - 2025-07-17

- Upgrade PHP 7.4-8-3 runtimes for Node 22

### [0.7.3] - 2024-10-19

- Upgrade PHP 8.3 runtime (fixes for curl, opcache)

### [0.7.2] - 2024-09-30

- Upgrade PHP 8.3 runtime

### [0.7.1] - 2024-04-16

- Fix autodetect runtime

### [0.6.2] - 2024-04-16

- Fix autodetect runtime

### [0.5.5] - 2024-04-16

- Fix autodetect runtime

### [0.4.4] - 2024-04-16

- Fix autodetect runtime

### [0.3.6] - 2024-04-16

- Fix autodetect runtime

### [0.7.0] - 2024-02-22

- PHP 8.3
- Use `@libphp/amazon-linux-2-v83: latest`

### [0.6.1] - 2024-01-24

- Update LD_LIBRARY_PATH

### [0.5.4] - 2024-01-24

- Update LD_LIBRARY_PATH

### [0.4.3] - 2024-01-24

- Update LD_LIBRARY_PATH

### [0.3.5] - 2024-01-24

- Update LD_LIBRARY_PATH

### [0.6.0] - 2023-03-27

- PHP 8.2
- Use `@libphp/amazon-linux-2-v82: latest`

### [0.5.3] - 2023-03-27

- Bump minimum node version from 14.x to 18.x
- Upgrade dependencies

### [0.5.2] - 2022-08-10

- Bump minimum node version from 12.x to 14.x

### [0.5.1] - 2022-05-05

- Ignore .vercel folder during deployment

### [0.5.0] - 2022-04-09

- PHP 8.1
  - Added extensions: geoip, zlib, zip
  - Removed extensions: psr
- Use `@libphp/amazon-linux-2-v81: latest`

### [0.4.0] - 2021-01-02

- PHP 8.0
- Use `@libphp/amazon-linux-2-v80: latest`

### [0.3.2] - 2021-01-02

- Typos
- More hints in FAQ
- Fix `excludeFiles` option
- Install PHP extensions mongodb
- Use `@libphp/amazon-linux-2-v74: latest`

### [0.3.1] - 2020-07-04

- Install PHP extensions redis, msgpack, igbinary
- Use `@libphp/amazon-linux-2-v74: latest`

### [0.3.0] - 2020-06-29

- Allow to execute composer script called `vercel`

  ```json
  {
    "scripts": {
      "vercel": [
        "@php -v",
        "npm -v"
      ]
    }
  }
  ```

- Drop support of `config['php.ini']` use `api/php.ini` file instead
- Support excludeFiles (default `['node_modules/**', 'now.json', '.nowignore']`)

  ```json
  {
    "functions": {
    "api/**/*.php": {
      "runtime": "vercel-php@0.3.0",
      "excludeFiles": ["node_modules", "somedir", "foo/bar"],
    }
  }
  ```

- Restructure test folder (merge fixtures + my examples)

### [0.2.0] - 2020-06-26

- Allow to override `php.ini`

  ```sh
  project
  ├── api
  │   ├── index.php
  │   └── php.ini
  └── now.json
  ```

- Extensive update of docs
- Introduce FAQ questions
- Move caddy package to [juicyfx/juicy](https://github.com/juicyfx/juicy)
- Simplify repository structure

### [0.1.0] - 2020-06-20

- Rename repository from now-php to **vercel-php**
- Rename NPM package from now-php to **vercel-php**
- Upgrade PHP to 7.4.7 and recompile PHP extensions
- Improve readme
- Separate PHP libs to solo repository [juicyfx/libphp](https://github.com/juicyfx/libphp) (bigger plans)
- Use [php.vercel.app](https://php.vercel.app) domain for official showtime
- Use [phpshow.vercel.app](https://phpshow.vercel.app) domain for runtime showcase

### [0.0.9] - 2020-03-28

- Use PHP 7.4 for installing Composer dependencies
- Upgrade PHP 7.4 and recompile PHP extensions

### [0.0.9] - 2020-01-16

- Use PHP 7.3 for installing Composer dependencies
- Separate [examples](https://github.com/juicyfx/vercel-examples) to solo repository
- Extensions
  - Disabled ssh2
  - Added psr
  - Rebuild phalcon, swoole

### [0.0.8] - 2020-01-07

- Runtime v3
- Upgrade to PHP 7.4.x
- Node 8.x reached EOL on AWS
- Used Amazon Linux 2
- CGI launcher inherits process.env [#38]
- Drop Circle CI
- Rebuild all PHP libs

### [0.0.7] - 2019-11-08

- Rename builder to runtime
- Runtime v3

**Migration**

```json
{
  "version": 2,
  "builds": [
    {
      "src": "index.php",
      "use": "now-php"
    }
  ]
}
```

➡️

```json
{
  "functions": {
    "api/*.php": {
      "runtime": "now-php@0.0.7"
    }
  },
  // Optionally provide routes
  "routes": [
    { "src": "/(.*)",  "dest": "/api/index.php" }
  ]
}
```

### [0.0.6] - 2019-11-07

- Change builds to functions

### [0.0.5] - 2019-09-30

- Added Lumen example
- Bugfix deploying PHP files in folders under different names then index.php

### [0.0.4] - 2019-09-30

- Implement intermediate caching (vendor, composer.lock, yarn.locak, package-lock.json, node_modules)
- Rewrite PHP built-in server document root

### [0.0.3] - 2019-09-04

- Bugfix passing query parameters and accessing $_GET

### [0.0.2] - 2019-08-23

- Bump now-php@latest

### [0.0.1-canary.39] - 2019-08-23

- Allow overriding php.ini
- Bugfix resolving PHP bin
- Bugfix deploying php files in subfolders

### [0.0.2-canary.2] - 2019-08-16

- Compile PHP 7.3.8

### [0.0.1-canary.5] - 2019-08-16

- First working copy of caddy server

### [0.0.1-canary.30] - 2019-08-16

- New exported method `getPhpLibFiles`
- Repair tests

### [0.0.1-canary.18] - 2019-08-02

- Bump now-php@latest

### [0.0.1-canary.18] - 2019-08-02

- Working on change response from string to Buffer
- Updated homepage

### [0.0.1-canary.17] - 2019-08-02

- Working on change response from string to Buffer

### [0.0.1-canary.15] - 2019-08-02

- CGI: REQUEST_URI contains only path, not host + path
- CGI: QUERY_STRING contains string without leading ?

### [0.0.1-canary.14] - 2019-07-29

- Tests: more tests

### [0.0.1-canary.13] - 2019-07-29

- Tests: take tests from official old builder

### [0.0.1-canary.12] - 2019-07-28

- Rewritten to TypeScript

### [0.0.1-canary.11] - 2019-07-28

- Working on `now-dev`

### [0.0.1-canary.8] - 2019-07-27

- First working `now-php` builder

### [0.0.1-canary.7] - 2019-07-27

- Working on `now` with `now-php`

### [0.0.1-canary.0] - 2019-07-27

- History begins


================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

**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).

## Commands

```bash
make install       # Install dependencies (npm install)
make build         # Compile TypeScript to dist/ (npm run build)
make build-watch   # Watch mode compilation (npm run watch)
make test          # Run Jest test suite (npm run test)
make test-watch    # Run tests in watch mode
make publish       # Publish to npm (latest tag)
make canary        # Publish to npm (canary tag)
```

## Architecture

### Vercel BuildV3 Runtime

The package implements Vercel's BuildV3 specification, exporting:
- `version = 3` - API version
- `build()` - Main builder function
- `prepareCache()` - Cache preparation
- `shouldServe()` - File serving decisions

### Build Flow (src/index.ts)

1. Download user files and PHP runtime (`@libphp/amazon-linux-2-v83`)
2. Run `composer install` if `composer.json` exists
3. Execute `composer run vercel` script if defined
4. Merge user `api/php.ini` with runtime defaults
5. Package everything into AWS Lambda function

### Runtime Execution Modes (src/launchers/)

- **builtin.ts** - PHP's built-in server (`php -S 0.0.0.0:3000`), proxied via Node.js. Default mode.
- **cgi.ts** - Spawns `php-cgi` per request with CGI environment variables. Stateless.
- **cli.ts** - Direct PHP CLI execution for simple scripts.

### Key Source Files

- `src/index.ts` - Build entry point implementing BuildV3
- `src/utils.ts` - Build utilities (Composer, PHP config, file collection)
- `src/launchers/helpers.ts` - Request/response transformation between Vercel events and PHP

### Type Definitions (src/types.d.ts)

Key types: `UserFiles`, `RuntimeFiles`, `Event`, `InvokedEvent`, `AwsRequest`, `AwsResponse`, `PhpInput`, `PhpOutput`, `CgiInput`

## Code Conventions

- Logging uses 🐘 emoji prefix for console output
- Exit codes: 253-255 for critical child process errors, 1 for general errors
- User files are prefixed with `/user/` in Lambda task root
- TypeScript strict mode with `noImplicitAny`, `noUnusedLocals`, `noUnusedParameters`
- 2-space indentation for JS/TS/JSON/YAML/MD files

## Testing

Tests are in `test/spec/` with example projects in `test/examples/`. Jest timeout is 10 seconds.


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2019 Milan Felix Sulc (f3l1x)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
.PHONY: install build test publish canary

install:
	npm install

build:
	npm run build

build-watch:
	npm run watch

test:
	npm run test

test-watch:
	npm run test-watch

publish:
	rm -rf ./dist
	npm publish --access public --tag latest

canary:
	rm -rf ./dist
	npm publish --access public --tag canary


================================================
FILE: README.md
================================================
<h1 align=center>PHP Runtime for <a href="https://vercel.com">Vercel</h1>

<p align=center>
  Enjoyable & powerful 🐘 PHP Runtime (<a href="https://php.vercel.app">php.vercel.app</a>) for Vercel platform.
</p>

<p align=center>
  <a href="https://www.npmjs.com/package/vercel-php"><img src="https://badgen.net/npm/v/vercel-php"></a>
  <a href="https://www.npmjs.com/package/vercel-php"><img src="https://badgen.net/npm/dt/vercel-php"></a>
  <a href="https://github.com/juicyfx/vercel-php/actions"><img src="https://badgen.net/github/checks/juicyfx/vercel-php"></a>
	<a href="https://bit.ly/f3l1xdis"><img src="https://badgen.net/badge/support/discussions/yellow"></a>
	<a href="http://bit.ly/f3l1xsponsor"><img src="https://badgen.net/badge/sponsor/donations/F96854"></a>
</p>

<p align=center>
  <a href="https://github.com/nette"><img src="https://github.com/nette.png" width="128"></a>
  <a href="https://github.com/symfony"><img src="https://github.com/symfony.png" width="128"></a>
  <a href="https://github.com/illuminate"><img src="https://github.com/illuminate.png" width="128"></a>
  <a href="https://github.com/slimphp"><img src="https://github.com/slimphp.png" width="128"></a>
  <a href="https://github.com/phalcon"><img src="https://github.com/phalcon.png" width="128"></a>
</p>

<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>

<p align=center>
Made 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>
</p>

-----

## 😎 Getting Started

Let'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.

Most simple example project is this one, using following project structure.

```sh
project
├── api
│   └── index.php
└── vercel.json
```

First file `api/index.php` is entrypoint of our application. It should be placed in **api** folder, it's very standard location for Vercel.

```php
<?php
phpinfo();
```

Second file `vercel.json` is pure gold here. Setup your project with configuration like this and voila. That's all.

```json
{
  "functions": {
    "api/*.php": {
      "runtime": "vercel-php@0.9.0"
    }
  }
}
```

Last thing you have to do is call `vercel`. If you are more interested take a look at features and usage.

```
# Install it globally
npm i -g vercel

# Log in
vercel login

# Let's fly
vercel
```

Are you ready to deploy your first PHP project to Vercel? Click & Go!

<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>

## 🤗 Features

- **Architecture**: PHP development server (🚀 fast enough)
- **PHP version**: 8.5 (https://example-php-8-5.vercel.app)
- **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
- **Speed**: cold ~250ms / warm ~5ms
- **Memory**: ~90mb
- **Frameworks**: Nette, Symfony, Lumen, Slim, Phalcon
- **Node.js**: 22.x

> List of all installable extensions is on this page https://blog.remirepo.net/pages/PECL-extensions-RPM-status.

## 💯 Versions

- `vercel-php@0.9.0` - Node autodetect / PHP 8.5.x (https://example-php-8-5.vercel.app)
- `vercel-php@0.8.0` - Node autodetect / PHP 8.4.x (https://example-php-8-4.vercel.app)
- `vercel-php@0.7.4` - Node autodetect / PHP 8.3.x (https://example-php-8-3.vercel.app)
- `vercel-php@0.6.2` - Node autodetect / PHP 8.2.x (https://example-php-8-2.vercel.app)
- `vercel-php@0.5.5` - Node autodetect / PHP 8.1.x (https://example-php-8-1.vercel.app)
- `vercel-php@0.4.5` - Node autodetect / PHP 8.0.x (https://example-php-8-0.vercel.app)
- `vercel-php@0.3.8` - Node autodetect / PHP 7.4.x (https://example-php-7-4.vercel.app)

## ⚙️  Usage

Before 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).

You should define `functions` property in `vercel.json` and list PHP files directly or using wildcard (*).
If you need to route everything to index, use `routes` property.

```json
{
  "functions": {
    "api/*.php": {
      "runtime": "vercel-php@0.9.0"
    }
  },
  "routes": [
    { "src": "/(.*)",  "dest": "/api/index.php" }
  ]
}
```

Do you have more questions (❓)? Let's move to [FAQ](#%EF%B8%8F-faq).

## 👨‍💻 `vercel dev`

For running `vercel dev` properly, you need to have PHP installed on your computer, [learn more](errors/now-dev-no-local-php.md).
But it's PHP and as you know PHP has built-in development server. It works out of box.

```
php -S localhost:8000 api/index.php
```

## 👀 Demo

- official - https://php.vercel.app/
- phpinfo - https://phpshow.vercel.app/
- extensions - https://phpshow.vercel.app/ext/
- ini - https://phpshow.vercel.app/ini/
- JSON API - https://phpshow.vercel.app/api/users.php
- test - https://phpshow.vercel.app/test.php

![PHP](https://api.microlink.io?url=https://phpshow.vercel.app&screenshot&embed=screenshot.url)

## 🎯Examples

- [PHP - fast & simple](https://github.com/juicyfx/vercel-examples/tree/master/php/)
- [Composer - install dependencies](https://github.com/juicyfx/vercel-examples/tree/master/php-composer/)
- [Framework - Laravel](https://github.com/juicyfx/vercel-examples/blob/master/php-laravel)
- [Framework - Lumen](https://github.com/juicyfx/vercel-examples/blob/master/php-lumen)
- [Framework - Nette](https://github.com/juicyfx/vercel-examples/blob/master/php-nette-tracy)
- [Framework - Phalcon](https://github.com/juicyfx/vercel-examples/blob/master/php-phalcon)
- [Framework - Slim](https://github.com/juicyfx/vercel-examples/blob/master/php-slim)
- [Framework - Symfony - Microservice](https://github.com/juicyfx/vercel-examples/blob/master/php-symfony-microservice)

Browse [more examples](https://github.com/juicyfx/vercel-examples). 👀

## 📜 Resources

- [2019/10/23 - Code Examples](https://github.com/trainit/2019-10-hubbr-zeit)
- [2019/10/19 - ZEIT - Deploy Serverless Microservices Right Now](https://slides.com/f3l1x/2019-10-19-zeit-deploy-serverless-microservices-right-now-vol2)
- [2019/08/23 - Code Examples](https://github.com/trainit/2019-08-serverless-zeit-now)
- [2019/07/07 - Bleeding Edge PHP on ZEIT Now](https://dev.to/nx1/bleeding-edge-php-on-zeit-now-565g)
- [2019/06/06 - Code Examples](https://github.com/trainit/2019-06-zeit-now)
- [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))

## 🚧 Roadmap

See [roadmap issue](https://github.com/juicyfx/vercel-php/issues/3). Help wanted.

## ⁉️ FAQ

<details>
  <summary>1. How to use more then one endpoint (index.php)?</summary>

```sh
project
├── api
│   ├── index.php
│   ├── users.php
│   └── books.php
└── vercel.json
```

```
{
  "functions": {
    "api/*.php": {
      "runtime": "vercel-php@0.9.0"
    },

    // Can be list also directly

    "api/index.php": {
      "runtime": "vercel-php@0.9.0"
    },
    "api/users.php": {
      "runtime": "vercel-php@0.9.0"
    },
    "api/books.php": {
      "runtime": "vercel-php@0.9.0"
    }
  }
}
```

</details>

<details>
  <summary>2. How to route everything to index?</summary>

```json
{
  "functions": {
    "api/index.php": {
      "runtime": "vercel-php@0.9.0"
    }
  },
  "routes": [
    { "src": "/(.*)",  "dest": "/api/index.php" }
  ]
}
```

</details>

<details>
  <summary>3. How to update memory limit?</summary>

Additional function properties are `memory`, `maxDuration`. Learn more about [functions](https://vercel.com/docs/configuration#project/functions).

```json
{
  "functions": {
    "api/*.php": {
      "runtime": "vercel-php@0.9.0",
      "memory": 3008,
      "maxDuration": 60
    }
  }
}
```

</details>

<details>
  <summary>4. How to use it with <a href="https://getcomposer.org/">Composer</a>?</summary>

Yes, [Composer](https://getcomposer.org/) is fully supported.

```sh
project
├── api
│   └── index.php
├── composer.json
└── vercel.json
```

```json
{
  "functions": {
    "api/*.php": {
      "runtime": "vercel-php@0.9.0"
    }
  }
}
```

```json
{
  "require": {
    "php": "^8.1",
    "tracy/tracy": "^2.0"
  }
}
```

It's also good thing to create `.vercelignore` file and put `/vendor` folder to this file. It will not upload
`/vendor` folder to Vercel platform.

</details>

<details>
  <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>

Yes, you can override php configuration. Take a look at [default configuration](https://phpshow.vercel.app/) at first.
Create a new file `api/php.ini` and place there your configuration. Don't worry, this particulary file will be
removed during building phase on Vercel.

```sh
project
├── api
│   ├── index.php
│   └── php.ini
└── vercel.json
```

```json
{
  "functions": {
    "api/*.php": {
      "runtime": "vercel-php@0.9.0"
    }
  }
}
```

```json
# Disable some functions
disable_functions = "exec, system"

# Update memory limit
memory_limit=1024M
```

</details>

<details>
  <summary>6. How to exclude some files or folders ?</summary>

Runtimes support excluding some files or folders, [take a look at doc](https://vercel.com/docs/configuration?query=excludeFiles#project/functions).

```json
{
  "functions": {
  "api/**/*.php": {
    "runtime": "vercel-php@0.9.0",
    "excludeFiles": "{foo/**,bar/config/*.yaml}",
  }
}
```

If you want to exclude files before uploading them to Vercel, use `.vercelignore` file.

</details>

<details>
  <summary>7. How to call composer script(s) ?</summary>

Calling composer scripts during build phase on Vercel is supported via script named `vercel`. You can easilly call php, npm or node.

```json
{
  "require": { ... },
  "require-dev": { ... },
  "scripts": {
    "vercel": [
      "@php -v",
      "npm -v"
    ]
  }
}
```

Files 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).

</details>

<details>
  <summary>8. How to include some files of folders?</summary>

If you are looking for [`config.includeFiles`](https://vercel.com/docs/configuration?query=includeFiles#project/functions) in runtime, unfortunately you can't include extra files.
All files in root folder are uploaded to Vercel, use `.vercelignore` to exclude them before upload.

</details>

<details>
  <summary>9. How to develop locally?</summary>

I think the best way at this moment is use [PHP Development Server](https://www.php.net/manual/en/features.commandline.webserver.php).

```
php -S localhost:8000 api/index.php
```

</details>

<details>
  <summary>10. What Node.js runtime is supported?</summary>

Use 22.x.

</details>

## 👨🏻‍💻CHANGELOG

Show me [CHANGELOG](./CHANGELOG.md)

## 🧙Contribution

1. Clone this repository.
   - `git clone git@github.com:juicyfx/vercel-php.git`
2. Install NPM dependencies
   - `make install`
3. Make your changes
4. Run TypeScript compiler
   - `make build`
5. Run tests
   - `make test`
6. Create a PR

## 📝 License

Copyright © 2019 [f3l1x](https://github.com/f3l1x).
This project is [MIT](LICENSE) licensed.


================================================
FILE: conf/build.ini
================================================
; Override for build phase on Vercel
extension_dir = "${PHP_INI_EXTENSION_DIR}"


================================================
FILE: errors/now-dev-no-local-php.md
================================================
# It looks like you don't have PHP on your machine.

**Why This Error Occurred**

You ran `now dev` on a machine where PHP is not installed.
For the time being, this runtime requires a local PHP installation to run the runtime locally.

**Possible Ways to Fix It**

1. Install PHP to your computer

**OSX**

```
brew install php@7.4
```

**Ubuntu**

```
apt-get -y install apt-transport-https lsb-release ca-certificates
wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg
sh -c 'echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list'
apt-get update
apt-get install php7.4-cli php7.4-cgi php7.4-json php7.4-curl php7.4-mbstring
```

**Fedora**

```
yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
yum install https://rpms.remirepo.net/enterprise/remi-release-7.rpm
yum install yum-utils
yum-config-manager --enable remi-php74
yum update
yum install php74-cli php74-cgi php74-json php74-curl php74-mbstring
```

2. Start PHP built-in Development Server

```sh
php -S localhost:8000 api/index.php
```

**Check that php is in the path**

If you do have installed PHP but still get this error, check that PHP executable is added to the PATH environment variable.


================================================
FILE: jest.config.js
================================================
module.exports = {
  rootDir: ".",
  verbose: true,
  testEnvironment: "node",
  testMatch: [
    "**/test/spec/**/*.js",
  ],
  testPathIgnorePatterns: [
    "/errors/",
    "/dist/",
    "/node_modules/",
  ],
  testTimeout: 10000
}


================================================
FILE: package.json
================================================
{
  "name": "vercel-php",
  "description": "Vercel PHP runtime",
  "version": "0.9.0",
  "license": "MIT",
  "main": "./dist/index.js",
  "homepage": "https://github.com/vercel-community/php",
  "repository": {
    "type": "git",
    "url": "https://github.com/vercel-community/php.git"
  },
  "keywords": [
    "vercel",
    "php",
    "builder",
    "runtime",
    "serverless",
    "deployment"
  ],
  "scripts": {
    "watch": "tsc --watch",
    "build": "tsc",
    "test": "jest --silent",
    "test-watch": "jest --watch",
    "prepublishOnly": "tsc"
  },
  "files": [
    "dist",
    "conf"
  ],
  "dependencies": {
    "@libphp/almalinux-9-v85": ">=0.0.2"
  },
  "devDependencies": {
    "@types/glob": "^9.0.0",
    "@types/node": "^22.0.0",
    "@vercel/build-utils": "^13.2.9",
    "jest": "^30.2.0",
    "typescript": "^5.9.3"
  }
}


================================================
FILE: src/index.ts
================================================
import path from 'path';
import {
  rename,
  shouldServe,
  glob,
  download,
  Lambda,
  BuildV3,
  PrepareCache,
  getNodeVersion
} from '@vercel/build-utils';
import {
  getPhpFiles,
  getLauncherFiles,
  runComposerInstall,
  runComposerScripts,
  readRuntimeFile,
  modifyPhpIni,
} from './utils';

const COMPOSER_FILE = process.env.COMPOSER || 'composer.json';

// ###########################
// EXPORTS
// ###########################

export const version = 3;

export const build: BuildV3 = 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: RuntimeFiles = await download(files, workPath, meta);

  console.log('🐘 Downloading PHP runtime files');

  // Collect runtime files containing PHP bins and libs
  const runtimeFiles: RuntimeFiles = {
    // Append PHP files (bins + shared object)
    ...await getPhpFiles(),

    // Append launcher files (builtin server, common helpers)
    ...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 runComposerInstall(workPath);

    // Run composer scripts (created files are collected bellow, , see harvestedFiles)
    await runComposerScripts(userFiles[COMPOSER_FILE], workPath);
  }

  // Append PHP directives into php.ini
  if (userFiles['api/php.ini']) {
    const phpini = await 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 = rename(
    await glob('**', {
      cwd: workPath,
      ignore: [
        '.vercel/**',
        ...(config?.excludeFiles
          ? Array.isArray(config.excludeFiles)
            ? config.excludeFiles
            : [config.excludeFiles]
          : [
              'node_modules/**',
              'now.json',
              '.nowignore',
              'vercel.json',
              '.vercelignore',
            ]),
      ],
    }),
    name => path.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 readRuntimeFile(runtimeFiles['php/php.ini']));
  }

  console.log('🐘 Creating lambda');
  const nodeVersion = await getNodeVersion(workPath);

  const lambda = new 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 };
};

export const prepareCache: PrepareCache = async ({ workPath }) => {
  return {
    // Composer
    ...(await glob('vendor/**', workPath)),
    ...(await glob('composer.lock', workPath)),
    // NPM
    ...(await glob('node_modules/**', workPath)),
    ...(await glob('package-lock.json', workPath)),
    ...(await glob('yarn.lock', workPath)),
    ...(await glob('pnpm-lock.yaml', workPath)),
    // Bun
    ...(await glob('bun.lock', workPath)),
    /* in case still used */
    ...(await glob('bun.lockb', workPath)),
  };
};

export { shouldServe };


================================================
FILE: src/launchers/builtin.ts
================================================
import http from 'http';
import { spawn, ChildProcess, SpawnOptions } from 'child_process';
import net from 'net';
import {
  getPhpDir,
  getUserDir,
  normalizeEvent,
  transformFromAwsRequest,
  transformToAwsResponse,
  isDev
} from './helpers';
import { join as pathJoin } from 'path';

let server: ChildProcess;

async function startServer(entrypoint: string): Promise<ChildProcess> {
  // Resolve document root and router
  const router = entrypoint;
  const docroot = pathJoin(getUserDir(), process.env.VERCEL_PHP_DOCROOT ?? '');

  console.log(`🐘 Spawning: PHP Built-In Server at ${docroot} (document root) and ${router} (router)`);

  // php spawn options
  const options: SpawnOptions = {
    stdio: ['pipe', 'pipe', 'pipe'],
    env: {
      ...process.env,
      LD_LIBRARY_PATH: `/var/task/lib:${process.env.LD_LIBRARY_PATH}`
    }
  };

  // now vs now-dev
  if (!isDev()) {
    options.env!.PATH = `${getPhpDir()}:${process.env.PATH}`;
    options.cwd = getPhpDir();
  } else {
    options.cwd = 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 = spawn(
    'php',
    ['-c', 'php.ini', '-S', '0.0.0.0:3000', '-t', docroot, router],
    options,
  );

  server.stdout?.on('data', data => {
    console.log(`🐘STDOUT: ${data.toString()}`);
  });

  server.stderr?.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(3000, 500);

  process.on('exit', () => {
    server.kill();
  })

  return server;
}

async function query({ entrypoint, uri, path, headers, method, body }: PhpInput): Promise<PhpOutput> {
  if (!server) {
    await startServer(entrypoint);
  }

  return new Promise(resolve => {
    const options = {
      hostname: 'localhost',
      port: 3000,
      path,
      method,
      headers,
    };

    console.log(`🐘 Accessing ${uri}`);
    console.log(`🐘 Querying ${path}`);

    const req = http.request(options, (res) => {
      const chunks: Uint8Array[] = [];

      res.on('data', (data) => {
        chunks.push(data);
      });
      res.on('end', () => {
        resolve({
          statusCode: res.statusCode || 200,
          headers: res.headers,
          body: Buffer.concat(chunks)
        });
      });
    });

    req.on('error', (error) => {
      console.error('🐘 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: number, attempts: number, cb: (error?: string) => void) {
  const client = net.connect(port, '127.0.0.1');
  client.on('error', (error: string) => {
    if (!attempts) return cb(error);
    setTimeout(() => {
      whenPortOpensCallback(port, attempts - 1, cb);
    }, 10);
  });
  client.on('connect', () => {
    client.destroy();
    cb();
  });
}

function whenPortOpens(port: number, attempts: number): Promise<void> {
  return new Promise((resolve, reject) => {
    whenPortOpensCallback(port, attempts, (error?: string) => {
      if (error) {
        reject(error);
      } else {
        resolve();
      }
    });
  });
}

async function launcher(event: Event): Promise<AwsResponse> {
  const awsRequest = normalizeEvent(event);
  const input = await transformFromAwsRequest(awsRequest);
  const output = await query(input);
  return transformToAwsResponse(output);
}

exports.launcher = launcher;

// (async function () {
//   const response = await launcher({
//     Action: "test",
//     httpMethod: "GET",
//     body: "",
//     path: "/",
//     host: "https://vercel.com",
//     headers: {
//       'HOST': 'vercel.com'
//     },
//     encoding: null,
//   });

//   console.log(response);
// })();


================================================
FILE: src/launchers/cgi.ts
================================================
import { spawn, SpawnOptions } from 'child_process';
import { parse as urlParse } from 'url';
import {
  getPhpDir,
  getUserDir,
  normalizeEvent,
  transformFromAwsRequest,
  transformToAwsResponse,
  isDev
} from './helpers';

function createCGIReq({ entrypoint, path, host, method, headers }: CgiInput): CgiRequest {
  const { query } = urlParse(path);

  const env: Env = {
    ...process.env,
    SERVER_ROOT: getUserDir(),
    DOCUMENT_ROOT: 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: Buffer) {
  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: string): CgiHeaders {
  if (!headers) return {}

  const result: CgiHeaders = {}

  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 }: PhpInput): Promise<PhpOutput> {
  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: SpawnOptions = {
    stdio: ['pipe', 'pipe', 'pipe'],
    env: env
  };

  // now vs now-dev
  if (!isDev()) {
    options.env!.PATH = `${getPhpDir()}:${process.env.PATH}`;
    options.cwd = getPhpDir();
  } else {
    options.cwd = getUserDir();
  }

  return new Promise((resolve) => {
    const chunks: Uint8Array[] = [];

    const php = 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: Event): Promise<AwsResponse> {
  const awsRequest = normalizeEvent(event);
  const input = await transformFromAwsRequest(awsRequest);
  const output = await query(input);
  return 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);
// })();


================================================
FILE: src/launchers/cli.ts
================================================
import { spawn, SpawnOptions } from 'child_process';
import {
  getPhpDir,
  normalizeEvent,
  transformFromAwsRequest,
  transformToAwsResponse,
  isDev,
  getUserDir
} from './helpers';

function query({ entrypoint, body }: PhpInput): Promise<PhpOutput> {
  console.log(`🐘 Spawning: PHP CLI ${entrypoint}`);

  // php spawn options
  const options: SpawnOptions = {
    stdio: ['pipe', 'pipe', 'pipe'],
    env: process.env
  };

  // now vs now-dev
  if (!isDev()) {
    options.env!.PATH = `${getPhpDir()}:${process.env.PATH}`;
    options.cwd = getPhpDir();
  } else {
    options.cwd = getUserDir();
  }

  return new Promise((resolve) => {
    const chunks: Uint8Array[] = [];

    const php = 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: Event): Promise<AwsResponse> {
  const awsRequest = normalizeEvent(event);
  const input = await transformFromAwsRequest(awsRequest);
  const output = await query(input);
  return transformToAwsResponse(output);
}

exports.launcher = launcher;

// (async function () {
//   const response = await launcher({
//       Action: "test",
//       httpMethod: "GET",
//       body: "",
//       path: "/",
//       host: "https://vercel.com",
//       headers: {
//           'HOST': 'vercel.com'
//       },
//       encoding: null,
//   });

//   console.log(response);
// })();


================================================
FILE: src/launchers/helpers.ts
================================================
import { join as pathJoin } from 'path';

export const getUserDir = (): string => pathJoin(process.env.LAMBDA_TASK_ROOT || '/', 'user');
export const getPhpDir = (): string => pathJoin(process.env.LAMBDA_TASK_ROOT || '/', 'php');
export const isDev = (): boolean => process.env.NOW_PHP_DEV === '1';

export function normalizeEvent(event: Event): AwsRequest {
  if (event.Action === 'Invoke') {
    const invokeEvent = JSON.parse(event.body);

    const {
      method, path, host, headers = {}, encoding,
    }: InvokedEvent = invokeEvent;

    let { body } = invokeEvent;

    if (body) {
      if (encoding === 'base64') {
        body = Buffer.from(body, encoding);
      } else if (encoding === undefined) {
        body = Buffer.from(body);
      } else {
        throw new Error(`Unsupported encoding: ${encoding}`);
      }
    }

    return {
      method,
      path,
      host,
      headers,
      body,
    };
  }

  const {
    httpMethod: method, path, host, headers = {}, body,
  } = event;

  return {
    method,
    path,
    host,
    headers,
    body,
  };
}

export async function transformFromAwsRequest({
  method, path, host, headers, body,
}: AwsRequest): Promise<PhpInput> {
  if (!process.env.NOW_ENTRYPOINT) {
    console.error('Missing ENV NOW_ENTRYPOINT');
  }

  const entrypoint = pathJoin(
    getUserDir(),
    process.env.NOW_ENTRYPOINT || 'index.php',
  );

  const uri = host + path;

  return { entrypoint, uri, path, host, method, headers, body };
}

export function transformToAwsResponse({ statusCode, headers, body }: PhpOutput): AwsResponse {
  return { statusCode, headers, body: body.toString('base64'), encoding: 'base64' };
}


================================================
FILE: src/types.d.ts
================================================
type Headers = { [k: string]: string | string[] | undefined };

interface UserFiles {
  [filePath: string]: import('@vercel/build-utils').File;
}

interface RuntimeFiles {
  [filePath: string]: import('@vercel/build-utils').File;
}

interface IncludedFiles {
  [filePath: string]: import('@vercel/build-utils').File;
}

interface MetaOptions {
  meta: import('@vercel/build-utils').Meta;
}

interface AwsRequest {
  method: string,
  path: string,
  host: string,
  headers: Headers,
  body: string,
}

interface AwsResponse {
  statusCode: number,
  headers: Headers,
  body: string,
  encoding?: string
}

interface Event {
  Action: string,
  body: string,
  httpMethod: string,
  path: string,
  host: string,
  headers: Headers,
  encoding: string | undefined | null,
}

interface InvokedEvent {
  method: string,
  path: string,
  host: string,
  headers: Headers,
  encoding: string | undefined | null,
}

interface CgiInput {
  entrypoint: string,
  path: string,
  host: string,
  method: string,
  headers: Headers,
}

interface PhpInput {
  entrypoint: string,
  path: string,
  uri: string,
  host: string,
  method: string,
  headers: Headers,
  body: string,
}

interface PhpOutput {
  statusCode: number,
  headers: Headers,
  body: Buffer,
}

interface CgiHeaders {
  [k: string]: string,
}

interface CgiRequest {
  env: Env,
}

interface Env {
  [k: string]: any,
}

interface PhpIni {
  [k: string]: any,
}


================================================
FILE: src/utils.ts
================================================
import path from 'path';
import { spawn, SpawnOptions } from 'child_process';
import { File, FileFsRef, FileBlob } from '@vercel/build-utils';
import * as libphp from "@libphp/almalinux-9-v85";

const PHP_PKG = path.dirname(require.resolve('@libphp/almalinux-9-v85/package.json'));
const PHP_BIN_DIR = path.join(PHP_PKG, "native/php");
const PHP_MODULES_DIR = path.join(PHP_BIN_DIR, "modules");
const PHP_LIB_DIR = path.join(PHP_PKG, "native/lib");
const COMPOSER_BIN = path.join(PHP_BIN_DIR, "composer");

export async function getPhpFiles(): Promise<RuntimeFiles> {
  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: RuntimeFiles = {};

  // Map from @libphp to Vercel's File objects
  for (const [filename, filepath] of Object.entries(files)) {
    runtimeFiles[filename] = new FileFsRef({
      fsPath: filepath
    })
  }

  // Set some bins executable
  (runtimeFiles['php/php'] as FileFsRef).mode = 33261; // 0755;
  (runtimeFiles['php/composer'] as FileFsRef).mode = 33261; // 0755;

  return runtimeFiles;
}

export function getLauncherFiles(): RuntimeFiles {
  const files: RuntimeFiles = {
    'helpers.js': new FileFsRef({
      fsPath: path.join(__dirname, 'launchers/helpers.js'),
    })
  }

  files['launcher.js'] = new FileFsRef({
    fsPath: path.join(__dirname, 'launchers/builtin.js'),
  });

  return files;
}

export async function modifyPhpIni(userFiles: UserFiles, runtimeFiles: RuntimeFiles): Promise<FileBlob | undefined> {
  // 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 FileBlob.fromStream({
    stream: runtimeFiles['php/php.ini'].toStream(),
  });

  const userPhpiniBlob = await FileBlob.fromStream({
    stream: userFiles['api/php.ini'].toStream(),
  });

  return new FileBlob({
    data: phpiniBlob.data.toString()
      .concat("; [User]\n")
      .concat(userPhpiniBlob.data.toString())
  });
}

export async function runComposerInstall(workPath: string): Promise<void> {
  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]');
}

export async function runComposerScripts(composerFile: File, workPath: string): Promise<void> {
  let composer;

  try {
    composer = JSON.parse(await readRuntimeFile(composerFile));
  } catch (e) {
    console.error('🐘 Composer file is not valid JSON');
    console.error(e);
    return;
  }

  if (composer?.scripts?.vercel) {
    console.log('🐘 Running composer scripts [START]');

    await runPhp(
      [COMPOSER_BIN, 'run', 'vercel'],
      {
        stdio: 'inherit',
        cwd: workPath
      }
    );

    console.log('🐘 Running composer scripts [DONE]');
  }
}

export async function ensureLocalPhp(): Promise<boolean> {
  try {
    await spawnAsync('which', ['php', 'php-cgi'], { stdio: 'pipe' });
    return true;
  } catch (e) {
    return false;
  }
}

export async function readRuntimeFile(file: File): Promise<string> {
  const blob = await FileBlob.fromStream({
    stream: file.toStream(),
  });

  return blob.data.toString();
}

// *****************************************************************************
// PRIVATE API *****************************************************************
// *****************************************************************************

async function runPhp(args: ReadonlyArray<string>, opts: SpawnOptions = {}) {
  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.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: string, args: ReadonlyArray<string>, opts: SpawnOptions = {}): Promise<void> {
  return new Promise((resolve, reject) => {
    const child = 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}`));
      }
    });
  })
}


================================================
FILE: test/examples/00-php/.gitignore
================================================
# Vercel
.vercel


================================================
FILE: test/examples/00-php/api/api/index.php
================================================
<?php

echo "Hello from subfolder!";


================================================
FILE: test/examples/00-php/api/api/users.php
================================================
<?php

$data = [
    ['id' => 1, 'name' => 'f3l1x'],
    ['id' => 2, 'name' => 'chemix'],
    ['id' => 3, 'name' => 'dg'],
    ['id' => 4, 'name' => 'milo'],
    ['id' => 5, 'name' => 'matej21'],
    ['id' => 6, 'name' => 'merxes'],
];

header('Content-Type: application/json');

echo json_encode($data);


================================================
FILE: test/examples/00-php/api/ext/ds.php
================================================
<?php
$map = new \Ds\Map(["a" => 1, "b" => 2, "c" => 3]);
$map->apply(function($key, $value) { return $value * 2; });

print_r($map);
?>

================================================
FILE: test/examples/00-php/api/ext/gd.php
================================================
<?php header ("Content-type: image/png");
$handle = ImageCreate (130, 50) or die ("Cannot Create image");
$bg_color = ImageColorAllocate ($handle, 255, 0, 0);
$txt_color = ImageColorAllocate ($handle, 255, 255, 255);
$line_color = ImageColorAllocate ($handle, 0, 0, 0);
ImageLine($handle, 65, 0, 130, 50, $line_color);
ImageString ($handle, 5, 5, 18, "PHP.About.com", $txt_color);
ImagePng ($handle);
?>

================================================
FILE: test/examples/00-php/api/ext/index.php
================================================
<?php
echo implode(',', get_loaded_extensions());
?>


================================================
FILE: test/examples/00-php/api/ext/phalcon.php
================================================
<?php

use Phalcon\Mvc\Micro;

$app = new Micro();

$app->get(
    "/",
    function () {
        echo "<h1>Welcome!</h1>";
    }
);

$app->get(
    "/say/hello/{name}",
    function ($name) use ($app) {
        echo "<h1>Hello! $name</h1>";
        echo "Your IP Address is ", $app->request->getClientAddress();
    }
);

$app->post(
    "/store/something",
    function () use ($app) {
        $name = $app->request->getPost("name");
        echo "<h1>Hello! $name</h1>";
    }
);

$app->notFound(
    function () use ($app) {
        $app->response->setStatusCode(404, "Not Found");
        $app->response->sendHeaders();
        echo "This is crazy, but this page was not found!";
    }
);

$app->handle();


================================================
FILE: test/examples/00-php/api/hello.php
================================================
<?php echo 'Hello from PHP on Now 2.0!';


================================================
FILE: test/examples/00-php/api/index.php
================================================
<?php
phpinfo();
var_dump(opcache_get_status());

================================================
FILE: test/examples/00-php/api/ini/index.php
================================================
<h1>php.ini</h1>

<table>
<thead>
    <tr>
        <th>option</th>
        <th>global_value</th>
        <th>local_value</th>
        <th>access</th>
    </tr>
</thead>
<tbody>
<?php
foreach (ini_get_all() as $name => $group) {
    echo "<tr>";
    echo sprintf('<td>%s</td>', $name);
    echo sprintf('<td>%s</td>', $group['global_value']);
    echo sprintf('<td>%s</td>', $group['local_value']);
    echo sprintf('<td>%s</td>', $group['access']);
    echo "</tr>";
}
?>
</tbody>
</table>


================================================
FILE: test/examples/00-php/api/libs.php
================================================
<?php

$path = getenv('LD_LIBRARY_PATH') ?? '';
$paths = explode(':', $path);

foreach ($paths as $path) {
    if (!is_dir($path)) {
        echo "No folder: $path<br>";
    } else {
        $files = scandir($path);
        echo "Scan folder: $path<br>";
        array_map(function($file) {
            echo "- $file <br>";
        }, $files);
    }
    echo "<hr>";
}


================================================
FILE: test/examples/00-php/api/test.php
================================================
<?php

header('X-PHP: test');
http_response_code($_GET['code'] ?? 200);

echo 'Test output';

var_dump(file_get_contents('php://input'));
var_dump($_GET);
var_dump($_POST);
var_dump($_SERVER);
var_dump($_ENV);
var_dump($_FILES);
var_dump(opcache_get_status());


================================================
FILE: test/examples/00-php/vercel.json
================================================
{
  "functions": {
    "api/**/*.php": {
      "runtime": "vercel-php@0.7.3"
    }
  },
  "routes": [
    { "src": "/api/(.*)", "dest": "/api/api/$1" },
    { "src": "/ini/(.*)", "dest": "/api/ini/$1" },
    { "src": "/ext/(.*)", "dest": "/api/ext/$1" },
    { "src": "/(.*)", "dest": "/api/$1" }
  ],
  "env": {
    "NOW_PHP_FOO": "bar3"
  },
  "build": {
    "env": {
      "NOW_PHP_DEBUG": "1"
    }
  }
}


================================================
FILE: test/examples/00-test/.gitignore
================================================
# Vercel
.vercel


================================================
FILE: test/examples/00-test/api/hey.txt
================================================
Test


================================================
FILE: test/examples/00-test/api/index.php
================================================
<?php
phpinfo();
var_dump(opcache_get_status());


================================================
FILE: test/examples/00-test/api/php.ini
================================================
memory_limit=1024M


================================================
FILE: test/examples/00-test/api/test.php
================================================
<?php

$pattern = $_GET['pattern'] ?? '*.*';

foreach (glob($pattern) as $filename) {
    echo "$filename size " . filesize($filename) . "\n";
}


================================================
FILE: test/examples/00-test/src/foo.txt
================================================
Foo


================================================
FILE: test/examples/00-test/vercel.json
================================================
{
  "functions": {
    "api/*.php": {
      "runtime": "vercel-php@0.7.3"
    }
  },
  "routes": [
    { "src": "/txt", "dest": "/api/php.txt" },
    { "src": "/ini", "dest": "/api/php.ini" },
    { "src": "/(.*)", "dest": "/api/$1" }
  ],
  "build": {
    "env": {
      "NOW_PHP_DEBUG": "1"
    }
  }
}


================================================
FILE: test/examples/01-cowsay/index.php
================================================
<?php
print('cow:RANDOMNESS_PLACEHOLDER');


================================================
FILE: test/examples/01-cowsay/subdirectory/index.php
================================================
<?php
print('yoda:RANDOMNESS_PLACEHOLDER');


================================================
FILE: test/examples/01-cowsay/vercel.json
================================================
{
  "version": 2,
  "builds": [
    { "src": "index.php", "use": "vercel-php@0.7.3" },
    { "src": "subdirectory/index.php", "use": "vercel-php@0.7.3" }
  ]
}


================================================
FILE: test/examples/02-extensions/index.php
================================================
<?php
echo implode(',', get_loaded_extensions());
?>


================================================
FILE: test/examples/02-extensions/vercel.json
================================================
{
  "version": 2,
  "builds": [{ "src": "index.php", "use": "vercel-php@0.7.3" }]
}


================================================
FILE: test/examples/03-env-vars/env/index.php
================================================
<?php
print($_ENV['RANDOMNESS_ENV_VAR'] . ':env');


================================================
FILE: test/examples/03-env-vars/vercel.json
================================================
{
  "version": 2,
  "builds": [{ "src": "env/index.php", "use": "vercel-php@0.7.3" }]
}


================================================
FILE: test/examples/04-include-files/excluded_file.php
================================================
<?php

echo 'Excluded!';


================================================
FILE: test/examples/04-include-files/included_file.php
================================================
<?php

echo 'included:RANDOMNESS_PLACEHOLDER';


================================================
FILE: test/examples/04-include-files/index.php
================================================
<?php

echo 'mainfile:';
if (file_exists('included_file.php') && !file_exists('excluded_file.php')) {
  require_once 'included_file.php';
} else {
  echo PHP_EOL;
  print_r(array_diff(scandir('.'), array('..', '.')));
}


================================================
FILE: test/examples/04-include-files/vercel.json
================================================
{
  "version": 2,
  "builds": [
    {
      "src": "index.php",
      "use": "vercel-php@0.7.3",
      "config": { "includeFiles": ["included*.php"] }
    }
  ]
}


================================================
FILE: test/examples/05-globals/index.php
================================================
<?php
header('Content-Type: text/plain');
print($_SERVER['SCRIPT_FILENAME'] . PHP_EOL);
print($_SERVER['REQUEST_METHOD'] . PHP_EOL);
print($_SERVER['REQUEST_URI'] . PHP_EOL);
print($_SERVER['HTTP_HOST'] . PHP_EOL);
print($_SERVER['HTTP_X_SOME_HEADER'] . PHP_EOL);
print($_SERVER['SERVER_PROTOCOL'] . PHP_EOL);
print($_SERVER['SERVER_NAME'] . PHP_EOL);
print($_SERVER['SERVER_PORT'] . PHP_EOL);
print($_SERVER['HTTPS'] . PHP_EOL);

print($_GET['get1'] . PHP_EOL);
var_dump($_GET['get2']);
print($_POST['post1'] . PHP_EOL);
var_dump($_POST['post2']);
print($_COOKIE['cookie1'] . PHP_EOL);
var_dump($_COOKIE['cookie2']);

print($_REQUEST['get1'] . PHP_EOL);
var_dump($_REQUEST['get2']);
print($_REQUEST['post1'] . PHP_EOL);
var_dump($_REQUEST['post2']);
print($_REQUEST['cookie1'] . PHP_EOL);
var_dump($_REQUEST['cookie2']);

print(file_get_contents('php://input') . PHP_EOL);
print('end' . PHP_EOL);


================================================
FILE: test/examples/05-globals/vercel.json
================================================
{
  "version": 2,
  "builds": [{ "src": "index.php", "use": "vercel-php@0.7.3" }]
}


================================================
FILE: test/examples/06-setcookie/index.php
================================================
<?php
header('Content-Type: text/plain');
header('Content-Type: text/plain; charset=UTF-16');
setcookie('cookie1', 'cookie1value');
setcookie('cookie2', 'cookie2value');


================================================
FILE: test/examples/06-setcookie/vercel.json
================================================
{
  "version": 2,
  "builds": [{ "src": "index.php", "use": "vercel-php@0.7.3" }]
}


================================================
FILE: test/examples/07-function/index.php
================================================
<?php

// regression test for go-php engine reusage. on failure prints
// Fatal error: Cannot redeclare some_function() (previously declared in /var/task/user/index.php:7)

function some_function() {
  print("paskantamasaari");
}

some_function();


================================================
FILE: test/examples/07-function/vercel.json
================================================
{
  "version": 2,
  "builds": [{ "src": "index.php", "use": "vercel-php@0.7.3" }]
}


================================================
FILE: test/examples/08-opcache/index.php
================================================
<?php
var_dump(opcache_compile_file(__FILE__));


================================================
FILE: test/examples/08-opcache/vercel.json
================================================
{
  "version": 2,
  "builds": [{ "src": "index.php", "use": "vercel-php@0.7.3" }]
}


================================================
FILE: test/examples/09-routes/index.php
================================================
<?php
print('cow:RANDOMNESS_PLACEHOLDER:' . $_SERVER['REQUEST_URI']);


================================================
FILE: test/examples/09-routes/vercel.json
================================================
{
  "version": 2,
  "builds": [{ "src": "index.php", "use": "vercel-php@0.7.3" }],
  "routes": [{ "src": "/(.*)", "dest": "index.php" }]
}


================================================
FILE: test/examples/10-composer-builds/.gitignore
================================================
vendor


================================================
FILE: test/examples/10-composer-builds/.vercelignore
================================================
vendor


================================================
FILE: test/examples/10-composer-builds/composer.json
================================================
{
  "require": {
    "psr/log": "^1.1"
  }
}


================================================
FILE: test/examples/10-composer-builds/index.php
================================================
<?php
header('content-type: text/plain');

require_once('vendor/autoload.php');

var_dump(interface_exists('Psr\Log\LoggerInterface'));


================================================
FILE: test/examples/10-composer-builds/vercel.json
================================================
{
  "version": 2,
  "builds": [{ "src": "index.php", "use": "vercel-php@0.7.3" }],
  "build": {
    "env": {
      "NOW_BUILDER_DEBUG": "1"
    }
  }
}


================================================
FILE: test/examples/11-composer-env/.gitignore
================================================
vendor
.vercel


================================================
FILE: test/examples/11-composer-env/.vercelignore
================================================
vendor


================================================
FILE: test/examples/11-composer-env/composer-test.json
================================================
{
  "require": {
    "psr/log": "^1.1"
  }
}


================================================
FILE: test/examples/11-composer-env/index.php
================================================
<?php
/*
 * What is the goal of this test?
 *
 * It should verify that build environment variables are passed along correctly.
 * So in this case the variable `COMPOSER` is set to composer.json file with a non-standard value
 * (`composer-test.json` in this case)
 *
 * An empty composer.json file is added to trigger composer installation. Since it is empty, the test fails when
 * the `COMPOSER` variable is not set.
 */

header('content-type: text/plain');

require_once('vendor/autoload.php');

var_dump(interface_exists('Psr\Log\LoggerInterface'));


================================================
FILE: test/examples/11-composer-env/vercel.json
================================================
{
  "version": 2,
  "builds": [{ "src": "index.php", "use": "vercel-php@0.7.3" }],
  "build": {
    "env": {
      "COMPOSER": "composer-test.json"
    }
  }
}


================================================
FILE: test/examples/12-composer/.gitignore
================================================
# PHP
/vendor

# Vercel
.vercel


================================================
FILE: test/examples/12-composer/.vercelignore
================================================
/vendor


================================================
FILE: test/examples/12-composer/api/index.php
================================================
OK


================================================
FILE: test/examples/12-composer/composer.json
================================================
{
  "require": {
    "php": "^7.4",
    "tracy/tracy": "^2.6.0"
  }
}


================================================
FILE: test/examples/12-composer/vercel.json
================================================
{
  "functions": {
    "api/index.php": {
      "runtime": "vercel-php@0.7.3"
    }
  },
  "routes": [
    { "src": "/(.*)",  "dest": "/api/index.php" }
  ],
  "build": {
    "env": {
      "NOW_PHP_DEBUG": "1"
    }
  }
}


================================================
FILE: test/examples/13-composer-scripts/.gitignore
================================================
# PHP
/vendor

# Vercel
.vercel


================================================
FILE: test/examples/13-composer-scripts/.vercelignore
================================================
/vendor


================================================
FILE: test/examples/13-composer-scripts/api/index.php
================================================
OK


================================================
FILE: test/examples/13-composer-scripts/composer.json
================================================
{
  "require": {
    "php": "^7.4",
    "tracy/tracy": "^2.6.0"
  },
  "scripts": {
    "vercel": [
      "@php --ini",
      "@php -m"
    ]
  }
}


================================================
FILE: test/examples/13-composer-scripts/vercel.json
================================================
{
  "functions": {
    "api/index.php": {
      "runtime": "vercel-php@0.7.3"
    }
  },
  "routes": [
    { "src": "/(.*)",  "dest": "/api/index.php" }
  ],
  "build": {
    "env": {
      "NOW_PHP_DEBUG": "1"
    }
  }
}


================================================
FILE: test/examples/14-folders/.gitignore
================================================
# Vercel
.vercel


================================================
FILE: test/examples/14-folders/api/index.php
================================================
<?php

echo "/";


================================================
FILE: test/examples/14-folders/api/users/index.php
================================================
<?php

echo "/user/";


================================================
FILE: test/examples/14-folders/api/users/users.php
================================================
<?php

echo "/user/users.php";


================================================
FILE: test/examples/14-folders/vercel.json
================================================
{
  "functions": {
    "api/**/*.php": {
      "runtime": "vercel-php@0.7.3"
    }
  },
  "routes": [
    { "src": "/(.*)", "dest": "/api/$1" }
  ],
  "build": {
    "env": {
      "NOW_PHP_DEBUG": "1"
    }
  }
}


================================================
FILE: test/examples/16-php-ini/.gitignore
================================================
# Vercel
.vercel


================================================
FILE: test/examples/16-php-ini/api/index.php
================================================
<?php
phpinfo();
var_dump(opcache_get_status());


================================================
FILE: test/examples/16-php-ini/api/php.ini
================================================
memory_limit=1024M


================================================
FILE: test/examples/16-php-ini/vercel.json
================================================
{
  "functions": {
    "api/*.php": {
      "runtime": "vercel-php@0.7.3"
    }
  },
  "routes": [
    { "src": "/(.*)",  "dest": "/api/index.php" }
  ],
  "build": {
    "env": {
      "NOW_PHP_DEBUG": "1"
    }
  }
}


================================================
FILE: test/examples/17-zero/.gitignore
================================================
# Vercel
.vercel


================================================
FILE: test/examples/17-zero/api/index.php
================================================
<?php

echo "Index PHP";


================================================
FILE: test/examples/17-zero/api/test.html
================================================
Test HTML


================================================
FILE: test/examples/17-zero/src/index.txt
================================================
Index HTML


================================================
FILE: test/examples/17-zero/vercel.json
================================================
{
  "functions": {
    "api/**/*.php": {
      "runtime": "vercel-php@0.7.3"
    }
  },
  "build": {
    "env": {
      "NOW_PHP_DEBUG": "1"
    }
  }
}


================================================
FILE: test/examples/18-exclude-files/.gitignore
================================================
# Vercel
.vercel


================================================
FILE: test/examples/18-exclude-files/.vercelignore
================================================
/baz


================================================
FILE: test/examples/18-exclude-files/api/index.php
================================================
<?php

echo "Index PHP";


================================================
FILE: test/examples/18-exclude-files/baz/index.html
================================================
Ignored by .vercelignore


================================================
FILE: test/examples/18-exclude-files/foo/index.txt
================================================
Ignore by excludedFiles


================================================
FILE: test/examples/18-exclude-files/vercel.json
================================================
{
  "functions": {
    "api/**/*.php": {
      "runtime": "vercel-php@0.7.3",
      "excludeFiles": "foo/**"
    }
  },
  "build": {
    "env": {
      "NOW_PHP_DEBUG": "1"
    }
  }
}


================================================
FILE: test/examples/19-server-workers/.gitignore
================================================
# Vercel
.vercel


================================================
FILE: test/examples/19-server-workers/api/index.php
================================================
<?php
phpinfo();
var_dump(opcache_get_status());


================================================
FILE: test/examples/19-server-workers/vercel.json
================================================
{
  "functions": {
    "api/*.php": {
      "runtime": "vercel-php@0.7.3"
    }
  },
  "env": {
    "PHP_CLI_SERVER_WORKERS": "4"
  },
  "build": {
    "env": {
      "NOW_PHP_DEBUG": "1"
    }
  }
}


================================================
FILE: test/examples/20-read-files/.gitignore
================================================
# Vercel
.vercel


================================================
FILE: test/examples/20-read-files/api/index.php
================================================
<?php

header('Content-Type: application/json');

echo file_get_contents(__DIR__ . '/../src/users.json');


================================================
FILE: test/examples/20-read-files/src/users.json
================================================
[
  {
    "id": 1,
    "name": "Felix"
  },
  {
    "id": 2,
    "name": "Chuck"
  }
]


================================================
FILE: test/examples/20-read-files/vercel.json
================================================
{
  "functions": {
    "api/*.php": {
      "runtime": "vercel-php@0.7.3"
    }
  },
  "routes": [
    { "src": "/(.*)", "dest": "/api/$1" }
  ]
}


================================================
FILE: test/spec/index.dev.js
================================================
const builder = require('./../../dist/index');

test('it should failed using now dev', async () => {
  const mockLog = console.log = jest.fn();

  jest.spyOn(process, 'exit').mockImplementation((code) => {
      expect(code).toBe(255);
      expect(mockLog).toHaveBeenCalledTimes(1);
  });

  await builder.build({
    files: [],
    entrypoint: 'test.php',
    workPath: __dirname,
    config: {},
    meta: { isDev: true },
  });
});


================================================
FILE: test/spec/index.js
================================================
const builder = require('./../../dist/index');

test('creates simple lambda', async () => {
  await builder.build({
    files: [],
    entrypoint: 'test.php',
    workPath: __dirname,
    config: {},
    meta: {},
  });
});


================================================
FILE: test/spec/launchers/cgi.js
================================================
const cgi = require('./../../../dist/launchers/cgi');

test('create CGI request', () => {
  const request = {
    entrypoint: "index.php",
    path: "/index.php",
    host: "https://vercel.com",
    method: "GET",
    headers: {}
  };
  process.env.CUSTOM_VALUE = "custom-value";
  const { env } = cgi.createCGIReq(request);

  expect(env).toHaveProperty("SERVER_ROOT", "/user");
  expect(env).toHaveProperty("DOCUMENT_ROOT", "/user");
  expect(env).toHaveProperty("SERVER_NAME", request.host);
  expect(env).toHaveProperty("SERVER_PORT", 443);
  expect(env).toHaveProperty("HTTPS", 'On');
  expect(env).toHaveProperty("REDIRECT_STATUS", 200);
  expect(env).toHaveProperty("SCRIPT_NAME", request.entrypoint);
  expect(env).toHaveProperty("REQUEST_URI", request.path);
  expect(env).toHaveProperty("SCRIPT_FILENAME", request.entrypoint);
  expect(env).toHaveProperty("PATH_TRANSLATED", request.entrypoint);
  expect(env).toHaveProperty("REQUEST_METHOD", request.method);
  expect(env).toHaveProperty("QUERY_STRING", '');
  expect(env).toHaveProperty("GATEWAY_INTERFACE", 'CGI/1.1');
  expect(env).toHaveProperty("SERVER_PROTOCOL", 'HTTP/1.1');
  expect(env).toHaveProperty("SERVER_SOFTWARE", 'Vercel PHP');
  expect(env).toHaveProperty("PATH", process.env.PATH);
  expect(env).toHaveProperty("LD_LIBRARY_PATH", process.env.LD_LIBRARY_PATH);
  expect(env).toHaveProperty("CUSTOM_VALUE", process.env.CUSTOM_VALUE);
});


================================================
FILE: test/spec/path.js
================================================
const path = require('path');

test('relative path', () => {
  const rootdir = '/var/task/user';
  const request = '/var/task/user/api/users.php';
  const file = path.relative(rootdir, request);

  expect(file).toBe('api/users.php');
});


================================================
FILE: test/spec/url.js
================================================
const url = require('url');

test('url.parse search & query are string', () => {
  const { search, query } = url.parse('https://vercel.com/?foo=bar&foo2=baz#foo');
  expect(search).toBe('?foo=bar&foo2=baz');
  expect(query).toBe('foo=bar&foo2=baz');
});

test('url.parse search string, query object', () => {
  const { search, query } = url.parse('https://vercel.com/?foo=bar&foo2=baz#foo', true);
  expect(search).toBe('?foo=bar&foo2=baz');
  expect(query).toMatchObject({ foo: 'bar', 'foo2': 'baz' });
});


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "strict": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "lib": [
      "ES2019"
    ],
    "target": "ES2019",
    "module": "CommonJS",
    "outDir": "dist",
    "sourceMap": false,
    "declaration": true,
    "noImplicitAny": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "typeRoots": [
      "./node_modules/@types"
    ],
    "types": []
  },
  "include": [
    "src/**/*.ts"
  ],
  "exclude": [
    "errors",
    "dist",
    "node_modules",
    "test"
  ]
}
Download .txt
gitextract_vi8hg7za/

├── .editorconfig
├── .github/
│   ├── .kodiak.toml
│   ├── ISSUE_TEMPLATE/
│   │   ├── Bug.md
│   │   ├── Feature.md
│   │   └── Question.md
│   ├── dependabot.yml
│   └── workflows/
│       └── main.yml
├── .gitignore
├── CHANGELOG.md
├── CLAUDE.md
├── LICENSE
├── Makefile
├── README.md
├── conf/
│   └── build.ini
├── errors/
│   └── now-dev-no-local-php.md
├── jest.config.js
├── package.json
├── src/
│   ├── index.ts
│   ├── launchers/
│   │   ├── builtin.ts
│   │   ├── cgi.ts
│   │   ├── cli.ts
│   │   └── helpers.ts
│   ├── types.d.ts
│   └── utils.ts
├── test/
│   ├── examples/
│   │   ├── 00-php/
│   │   │   ├── .gitignore
│   │   │   ├── api/
│   │   │   │   ├── api/
│   │   │   │   │   ├── index.php
│   │   │   │   │   └── users.php
│   │   │   │   ├── ext/
│   │   │   │   │   ├── ds.php
│   │   │   │   │   ├── gd.php
│   │   │   │   │   ├── index.php
│   │   │   │   │   └── phalcon.php
│   │   │   │   ├── hello.php
│   │   │   │   ├── index.php
│   │   │   │   ├── ini/
│   │   │   │   │   └── index.php
│   │   │   │   ├── libs.php
│   │   │   │   └── test.php
│   │   │   └── vercel.json
│   │   ├── 00-test/
│   │   │   ├── .gitignore
│   │   │   ├── api/
│   │   │   │   ├── hey.txt
│   │   │   │   ├── index.php
│   │   │   │   ├── php.ini
│   │   │   │   └── test.php
│   │   │   ├── src/
│   │   │   │   └── foo.txt
│   │   │   └── vercel.json
│   │   ├── 01-cowsay/
│   │   │   ├── index.php
│   │   │   ├── subdirectory/
│   │   │   │   └── index.php
│   │   │   └── vercel.json
│   │   ├── 02-extensions/
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 03-env-vars/
│   │   │   ├── env/
│   │   │   │   └── index.php
│   │   │   └── vercel.json
│   │   ├── 04-include-files/
│   │   │   ├── excluded_file.php
│   │   │   ├── included_file.php
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 05-globals/
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 06-setcookie/
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 07-function/
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 08-opcache/
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 09-routes/
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 10-composer-builds/
│   │   │   ├── .gitignore
│   │   │   ├── .vercelignore
│   │   │   ├── composer.json
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 11-composer-env/
│   │   │   ├── .gitignore
│   │   │   ├── .vercelignore
│   │   │   ├── composer-test.json
│   │   │   ├── index.php
│   │   │   └── vercel.json
│   │   ├── 12-composer/
│   │   │   ├── .gitignore
│   │   │   ├── .vercelignore
│   │   │   ├── api/
│   │   │   │   └── index.php
│   │   │   ├── composer.json
│   │   │   └── vercel.json
│   │   ├── 13-composer-scripts/
│   │   │   ├── .gitignore
│   │   │   ├── .vercelignore
│   │   │   ├── api/
│   │   │   │   └── index.php
│   │   │   ├── composer.json
│   │   │   └── vercel.json
│   │   ├── 14-folders/
│   │   │   ├── .gitignore
│   │   │   ├── api/
│   │   │   │   ├── index.php
│   │   │   │   └── users/
│   │   │   │       ├── index.php
│   │   │   │       └── users.php
│   │   │   └── vercel.json
│   │   ├── 16-php-ini/
│   │   │   ├── .gitignore
│   │   │   ├── api/
│   │   │   │   ├── index.php
│   │   │   │   └── php.ini
│   │   │   └── vercel.json
│   │   ├── 17-zero/
│   │   │   ├── .gitignore
│   │   │   ├── api/
│   │   │   │   ├── index.php
│   │   │   │   └── test.html
│   │   │   ├── src/
│   │   │   │   └── index.txt
│   │   │   └── vercel.json
│   │   ├── 18-exclude-files/
│   │   │   ├── .gitignore
│   │   │   ├── .vercelignore
│   │   │   ├── api/
│   │   │   │   └── index.php
│   │   │   ├── baz/
│   │   │   │   └── index.html
│   │   │   ├── foo/
│   │   │   │   └── index.txt
│   │   │   └── vercel.json
│   │   ├── 19-server-workers/
│   │   │   ├── .gitignore
│   │   │   ├── api/
│   │   │   │   └── index.php
│   │   │   └── vercel.json
│   │   └── 20-read-files/
│   │       ├── .gitignore
│   │       ├── api/
│   │       │   └── index.php
│   │       ├── src/
│   │       │   └── users.json
│   │       └── vercel.json
│   └── spec/
│       ├── index.dev.js
│       ├── index.js
│       ├── launchers/
│       │   └── cgi.js
│       ├── path.js
│       └── url.js
└── tsconfig.json
Download .txt
SYMBOL INDEX (47 symbols across 8 files)

FILE: src/index.ts
  constant COMPOSER_FILE (line 21) | const COMPOSER_FILE = process.env.COMPOSER || 'composer.json';

FILE: src/launchers/builtin.ts
  function startServer (line 16) | async function startServer(entrypoint: string): Promise<ChildProcess> {
  function query (line 79) | async function query({ entrypoint, uri, path, headers, method, body }: P...
  function whenPortOpensCallback (line 128) | function whenPortOpensCallback(port: number, attempts: number, cb: (erro...
  function whenPortOpens (line 142) | function whenPortOpens(port: number, attempts: number): Promise<void> {
  function launcher (line 154) | async function launcher(event: Event): Promise<AwsResponse> {

FILE: src/launchers/cgi.ts
  function createCGIReq (line 12) | function createCGIReq({ entrypoint, path, host, method, headers }: CgiIn...
  function parseCGIResponse (line 59) | function parseCGIResponse(response: Buffer) {
  function parseCGIHeaders (line 86) | function parseCGIHeaders(headers: string): CgiHeaders {
  function query (line 103) | function query({ entrypoint, path, host, headers, method, body }: PhpInp...
  function launcher (line 190) | async function launcher(event: Event): Promise<AwsResponse> {

FILE: src/launchers/cli.ts
  function query (line 11) | function query({ entrypoint, body }: PhpInput): Promise<PhpOutput> {
  function launcher (line 93) | async function launcher(event: Event): Promise<AwsResponse> {

FILE: src/launchers/helpers.ts
  function normalizeEvent (line 7) | function normalizeEvent(event: Event): AwsRequest {
  function transformFromAwsRequest (line 49) | async function transformFromAwsRequest({
  function transformToAwsResponse (line 66) | function transformToAwsResponse({ statusCode, headers, body }: PhpOutput...

FILE: src/types.d.ts
  type Headers (line 1) | type Headers = { [k: string]: string | string[] | undefined };
  type UserFiles (line 3) | interface UserFiles {
  type RuntimeFiles (line 7) | interface RuntimeFiles {
  type IncludedFiles (line 11) | interface IncludedFiles {
  type MetaOptions (line 15) | interface MetaOptions {
  type AwsRequest (line 19) | interface AwsRequest {
  type AwsResponse (line 27) | interface AwsResponse {
  type Event (line 34) | interface Event {
  type InvokedEvent (line 44) | interface InvokedEvent {
  type CgiInput (line 52) | interface CgiInput {
  type PhpInput (line 60) | interface PhpInput {
  type PhpOutput (line 70) | interface PhpOutput {
  type CgiHeaders (line 76) | interface CgiHeaders {
  type CgiRequest (line 80) | interface CgiRequest {
  type Env (line 84) | interface Env {
  type PhpIni (line 88) | interface PhpIni {

FILE: src/utils.ts
  constant PHP_PKG (line 6) | const PHP_PKG = path.dirname(require.resolve('@libphp/almalinux-9-v85/pa...
  constant PHP_BIN_DIR (line 7) | const PHP_BIN_DIR = path.join(PHP_PKG, "native/php");
  constant PHP_MODULES_DIR (line 8) | const PHP_MODULES_DIR = path.join(PHP_BIN_DIR, "modules");
  constant PHP_LIB_DIR (line 9) | const PHP_LIB_DIR = path.join(PHP_PKG, "native/lib");
  constant COMPOSER_BIN (line 10) | const COMPOSER_BIN = path.join(PHP_BIN_DIR, "composer");
  function getPhpFiles (line 12) | async function getPhpFiles(): Promise<RuntimeFiles> {
  function getLauncherFiles (line 36) | function getLauncherFiles(): RuntimeFiles {
  function modifyPhpIni (line 50) | async function modifyPhpIni(userFiles: UserFiles, runtimeFiles: RuntimeF...
  function runComposerInstall (line 72) | async function runComposerInstall(workPath: string): Promise<void> {
  function runComposerScripts (line 96) | async function runComposerScripts(composerFile: File, workPath: string):...
  function ensureLocalPhp (line 122) | async function ensureLocalPhp(): Promise<boolean> {
  function readRuntimeFile (line 131) | async function readRuntimeFile(file: File): Promise<string> {
  function runPhp (line 143) | async function runPhp(args: ReadonlyArray<string>, opts: SpawnOptions = ...
  function spawnAsync (line 165) | function spawnAsync(command: string, args: ReadonlyArray<string>, opts: ...

FILE: test/examples/07-function/index.php
  function some_function (line 6) | function some_function() {
Condensed preview — 118 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (76K chars).
[
  {
    "path": ".editorconfig",
    "chars": 292,
    "preview": "# EditorConfig is awesome: http://EditorConfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newli"
  },
  {
    "path": ".github/.kodiak.toml",
    "chars": 212,
    "preview": "version = 1\n\n[merge]\nautomerge_label = \"automerge\"\nblacklist_title_regex = \"^WIP.*\"\nblacklist_labels = [\"WIP\"]\nmethod = "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Bug.md",
    "chars": 192,
    "preview": "---\nname: Bug report 🐛\nabout: Something is not working as expected!\n---\n\n# Bug report\n\n- Version: x.y.z\n- URL: Yes (*.no"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Feature.md",
    "chars": 125,
    "preview": "---\nname: Feature request 🚀\nabout: I would appreciate new feature or something!\n---\n\n# Feature Request\n\n<!-- Describe it"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Question.md",
    "chars": 86,
    "preview": "---\nname: Question ❓\nabout: Ask about anything!\n---\n\n# Question\n\n<!-- Describe it -->\n"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 415,
    "preview": "version: 2\nupdates:\n- package-ecosystem: npm\n  directory: \"/\"\n  schedule:\n    interval: daily\n    time: '11:00'\n  open-p"
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 405,
    "preview": "name: Main workflow\n\non: [push, pull_request]\n\njobs:\n  run:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout"
  },
  {
    "path": ".gitignore",
    "chars": 71,
    "preview": "# Node\n/node_modules\n/package-lock.json\n\n# App\n/dist\n\n# Vercel\n.vercel\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 5645,
    "preview": "# 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- Upgra"
  },
  {
    "path": "CLAUDE.md",
    "chars": 2479,
    "preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
  },
  {
    "path": "LICENSE",
    "chars": 1081,
    "preview": "MIT License\n\nCopyright (c) 2019 Milan Felix Sulc (f3l1x)\n\nPermission is hereby granted, free of charge, to any person ob"
  },
  {
    "path": "Makefile",
    "chars": 304,
    "preview": ".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\nte"
  },
  {
    "path": "README.md",
    "chars": 12275,
    "preview": "<h1 align=center>PHP Runtime for <a href=\"https://vercel.com\">Vercel</h1>\n\n<p align=center>\n  Enjoyable & powerful 🐘 PHP"
  },
  {
    "path": "conf/build.ini",
    "chars": 80,
    "preview": "; Override for build phase on Vercel\nextension_dir = \"${PHP_INI_EXTENSION_DIR}\"\n"
  },
  {
    "path": "errors/now-dev-no-local-php.md",
    "chars": 1266,
    "preview": "# 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 P"
  },
  {
    "path": "jest.config.js",
    "chars": 235,
    "preview": "module.exports = {\n  rootDir: \".\",\n  verbose: true,\n  testEnvironment: \"node\",\n  testMatch: [\n    \"**/test/spec/**/*.js\""
  },
  {
    "path": "package.json",
    "chars": 845,
    "preview": "{\n  \"name\": \"vercel-php\",\n  \"description\": \"Vercel PHP runtime\",\n  \"version\": \"0.9.0\",\n  \"license\": \"MIT\",\n  \"main\": \"./"
  },
  {
    "path": "src/index.ts",
    "chars": 4114,
    "preview": "import path from 'path';\nimport {\n  rename,\n  shouldServe,\n  glob,\n  download,\n  Lambda,\n  BuildV3,\n  PrepareCache,\n  ge"
  },
  {
    "path": "src/launchers/builtin.ts",
    "chars": 4354,
    "preview": "import http from 'http';\nimport { spawn, ChildProcess, SpawnOptions } from 'child_process';\nimport net from 'net';\nimpor"
  },
  {
    "path": "src/launchers/cgi.ts",
    "chars": 5069,
    "preview": "import { spawn, SpawnOptions } from 'child_process';\nimport { parse as urlParse } from 'url';\nimport {\n  getPhpDir,\n  ge"
  },
  {
    "path": "src/launchers/cli.ts",
    "chars": 2700,
    "preview": "import { spawn, SpawnOptions } from 'child_process';\nimport {\n  getPhpDir,\n  normalizeEvent,\n  transformFromAwsRequest,\n"
  },
  {
    "path": "src/launchers/helpers.ts",
    "chars": 1675,
    "preview": "import { join as pathJoin } from 'path';\n\nexport const getUserDir = (): string => pathJoin(process.env.LAMBDA_TASK_ROOT "
  },
  {
    "path": "src/types.d.ts",
    "chars": 1426,
    "preview": "type Headers = { [k: string]: string | string[] | undefined };\n\ninterface UserFiles {\n  [filePath: string]: import('@ver"
  },
  {
    "path": "src/utils.ts",
    "chars": 4914,
    "preview": "import path from 'path';\nimport { spawn, SpawnOptions } from 'child_process';\nimport { File, FileFsRef, FileBlob } from "
  },
  {
    "path": "test/examples/00-php/.gitignore",
    "chars": 17,
    "preview": "# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/00-php/api/api/index.php",
    "chars": 37,
    "preview": "<?php\n\necho \"Hello from subfolder!\";\n"
  },
  {
    "path": "test/examples/00-php/api/api/users.php",
    "chars": 305,
    "preview": "<?php\n\n$data = [\n    ['id' => 1, 'name' => 'f3l1x'],\n    ['id' => 2, 'name' => 'chemix'],\n    ['id' => 3, 'name' => 'dg'"
  },
  {
    "path": "test/examples/00-php/api/ext/ds.php",
    "chars": 136,
    "preview": "<?php\n$map = new \\Ds\\Map([\"a\" => 1, \"b\" => 2, \"c\" => 3]);\n$map->apply(function($key, $value) { return $value * 2; });\n\np"
  },
  {
    "path": "test/examples/00-php/api/ext/gd.php",
    "chars": 403,
    "preview": "<?php header (\"Content-type: image/png\");\n$handle = ImageCreate (130, 50) or die (\"Cannot Create image\");\n$bg_color = Im"
  },
  {
    "path": "test/examples/00-php/api/ext/index.php",
    "chars": 53,
    "preview": "<?php\necho implode(',', get_loaded_extensions());\n?>\n"
  },
  {
    "path": "test/examples/00-php/api/ext/phalcon.php",
    "chars": 711,
    "preview": "<?php\n\nuse Phalcon\\Mvc\\Micro;\n\n$app = new Micro();\n\n$app->get(\n    \"/\",\n    function () {\n        echo \"<h1>Welcome!</h1"
  },
  {
    "path": "test/examples/00-php/api/hello.php",
    "chars": 41,
    "preview": "<?php echo 'Hello from PHP on Now 2.0!';\n"
  },
  {
    "path": "test/examples/00-php/api/index.php",
    "chars": 48,
    "preview": "<?php\nphpinfo();\nvar_dump(opcache_get_status());"
  },
  {
    "path": "test/examples/00-php/api/ini/index.php",
    "chars": 490,
    "preview": "<h1>php.ini</h1>\n\n<table>\n<thead>\n    <tr>\n        <th>option</th>\n        <th>global_value</th>\n        <th>local_value"
  },
  {
    "path": "test/examples/00-php/api/libs.php",
    "chars": 369,
    "preview": "<?php\n\n$path = getenv('LD_LIBRARY_PATH') ?? '';\n$paths = explode(':', $path);\n\nforeach ($paths as $path) {\n    if (!is_d"
  },
  {
    "path": "test/examples/00-php/api/test.php",
    "chars": 261,
    "preview": "<?php\n\nheader('X-PHP: test');\nhttp_response_code($_GET['code'] ?? 200);\n\necho 'Test output';\n\nvar_dump(file_get_contents"
  },
  {
    "path": "test/examples/00-php/vercel.json",
    "chars": 409,
    "preview": "{\n  \"functions\": {\n    \"api/**/*.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"routes\": [\n    { \"src\": \"/api"
  },
  {
    "path": "test/examples/00-test/.gitignore",
    "chars": 17,
    "preview": "# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/00-test/api/hey.txt",
    "chars": 5,
    "preview": "Test\n"
  },
  {
    "path": "test/examples/00-test/api/index.php",
    "chars": 49,
    "preview": "<?php\nphpinfo();\nvar_dump(opcache_get_status());\n"
  },
  {
    "path": "test/examples/00-test/api/php.ini",
    "chars": 19,
    "preview": "memory_limit=1024M\n"
  },
  {
    "path": "test/examples/00-test/api/test.php",
    "chars": 145,
    "preview": "<?php\n\n$pattern = $_GET['pattern'] ?? '*.*';\n\nforeach (glob($pattern) as $filename) {\n    echo \"$filename size \" . files"
  },
  {
    "path": "test/examples/00-test/src/foo.txt",
    "chars": 4,
    "preview": "Foo\n"
  },
  {
    "path": "test/examples/00-test/vercel.json",
    "chars": 305,
    "preview": "{\n  \"functions\": {\n    \"api/*.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"routes\": [\n    { \"src\": \"/txt\", "
  },
  {
    "path": "test/examples/01-cowsay/index.php",
    "chars": 43,
    "preview": "<?php\nprint('cow:RANDOMNESS_PLACEHOLDER');\n"
  },
  {
    "path": "test/examples/01-cowsay/subdirectory/index.php",
    "chars": 44,
    "preview": "<?php\nprint('yoda:RANDOMNESS_PLACEHOLDER');\n"
  },
  {
    "path": "test/examples/01-cowsay/vercel.json",
    "chars": 160,
    "preview": "{\n  \"version\": 2,\n  \"builds\": [\n    { \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" },\n    { \"src\": \"subdirectory/index."
  },
  {
    "path": "test/examples/02-extensions/index.php",
    "chars": 53,
    "preview": "<?php\necho implode(',', get_loaded_extensions());\n?>\n"
  },
  {
    "path": "test/examples/02-extensions/vercel.json",
    "chars": 84,
    "preview": "{\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",
    "chars": 51,
    "preview": "<?php\nprint($_ENV['RANDOMNESS_ENV_VAR'] . ':env');\n"
  },
  {
    "path": "test/examples/03-env-vars/vercel.json",
    "chars": 88,
    "preview": "{\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",
    "chars": 25,
    "preview": "<?php\n\necho 'Excluded!';\n"
  },
  {
    "path": "test/examples/04-include-files/included_file.php",
    "chars": 47,
    "preview": "<?php\n\necho 'included:RANDOMNESS_PLACEHOLDER';\n"
  },
  {
    "path": "test/examples/04-include-files/index.php",
    "chars": 220,
    "preview": "<?php\n\necho 'mainfile:';\nif (file_exists('included_file.php') && !file_exists('excluded_file.php')) {\n  require_once 'in"
  },
  {
    "path": "test/examples/04-include-files/vercel.json",
    "chars": 163,
    "preview": "{\n  \"version\": 2,\n  \"builds\": [\n    {\n      \"src\": \"index.php\",\n      \"use\": \"vercel-php@0.7.3\",\n      \"config\": { \"incl"
  },
  {
    "path": "test/examples/05-globals/index.php",
    "chars": 898,
    "preview": "<?php\nheader('Content-Type: text/plain');\nprint($_SERVER['SCRIPT_FILENAME'] . PHP_EOL);\nprint($_SERVER['REQUEST_METHOD']"
  },
  {
    "path": "test/examples/05-globals/vercel.json",
    "chars": 84,
    "preview": "{\n  \"version\": 2,\n  \"builds\": [{ \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" }]\n}\n"
  },
  {
    "path": "test/examples/06-setcookie/index.php",
    "chars": 170,
    "preview": "<?php\nheader('Content-Type: text/plain');\nheader('Content-Type: text/plain; charset=UTF-16');\nsetcookie('cookie1', 'cook"
  },
  {
    "path": "test/examples/06-setcookie/vercel.json",
    "chars": 84,
    "preview": "{\n  \"version\": 2,\n  \"builds\": [{ \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" }]\n}\n"
  },
  {
    "path": "test/examples/07-function/index.php",
    "chars": 248,
    "preview": "<?php\n\n// regression test for go-php engine reusage. on failure prints\n// Fatal error: Cannot redeclare some_function() "
  },
  {
    "path": "test/examples/07-function/vercel.json",
    "chars": 84,
    "preview": "{\n  \"version\": 2,\n  \"builds\": [{ \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" }]\n}\n"
  },
  {
    "path": "test/examples/08-opcache/index.php",
    "chars": 48,
    "preview": "<?php\nvar_dump(opcache_compile_file(__FILE__));\n"
  },
  {
    "path": "test/examples/08-opcache/vercel.json",
    "chars": 84,
    "preview": "{\n  \"version\": 2,\n  \"builds\": [{ \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" }]\n}\n"
  },
  {
    "path": "test/examples/09-routes/index.php",
    "chars": 70,
    "preview": "<?php\nprint('cow:RANDOMNESS_PLACEHOLDER:' . $_SERVER['REQUEST_URI']);\n"
  },
  {
    "path": "test/examples/09-routes/vercel.json",
    "chars": 139,
    "preview": "{\n  \"version\": 2,\n  \"builds\": [{ \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" }],\n  \"routes\": [{ \"src\": \"/(.*)\", \"dest\""
  },
  {
    "path": "test/examples/10-composer-builds/.gitignore",
    "chars": 7,
    "preview": "vendor\n"
  },
  {
    "path": "test/examples/10-composer-builds/.vercelignore",
    "chars": 7,
    "preview": "vendor\n"
  },
  {
    "path": "test/examples/10-composer-builds/composer.json",
    "chars": 45,
    "preview": "{\n  \"require\": {\n    \"psr/log\": \"^1.1\"\n  }\n}\n"
  },
  {
    "path": "test/examples/10-composer-builds/index.php",
    "chars": 136,
    "preview": "<?php\nheader('content-type: text/plain');\n\nrequire_once('vendor/autoload.php');\n\nvar_dump(interface_exists('Psr\\Log\\Logg"
  },
  {
    "path": "test/examples/10-composer-builds/vercel.json",
    "chars": 152,
    "preview": "{\n  \"version\": 2,\n  \"builds\": [{ \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" }],\n  \"build\": {\n    \"env\": {\n      \"NOW_"
  },
  {
    "path": "test/examples/11-composer-env/.gitignore",
    "chars": 15,
    "preview": "vendor\n.vercel\n"
  },
  {
    "path": "test/examples/11-composer-env/.vercelignore",
    "chars": 7,
    "preview": "vendor\n"
  },
  {
    "path": "test/examples/11-composer-env/composer-test.json",
    "chars": 45,
    "preview": "{\n  \"require\": {\n    \"psr/log\": \"^1.1\"\n  }\n}\n"
  },
  {
    "path": "test/examples/11-composer-env/index.php",
    "chars": 554,
    "preview": "<?php\n/*\n * What is the goal of this test?\n *\n * It should verify that build environment variables are passed along corr"
  },
  {
    "path": "test/examples/11-composer-env/vercel.json",
    "chars": 160,
    "preview": "{\n  \"version\": 2,\n  \"builds\": [{ \"src\": \"index.php\", \"use\": \"vercel-php@0.7.3\" }],\n  \"build\": {\n    \"env\": {\n      \"COMP"
  },
  {
    "path": "test/examples/12-composer/.gitignore",
    "chars": 32,
    "preview": "# PHP\n/vendor\n\n# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/12-composer/.vercelignore",
    "chars": 8,
    "preview": "/vendor\n"
  },
  {
    "path": "test/examples/12-composer/api/index.php",
    "chars": 3,
    "preview": "OK\n"
  },
  {
    "path": "test/examples/12-composer/composer.json",
    "chars": 70,
    "preview": "{\n  \"require\": {\n    \"php\": \"^7.4\",\n    \"tracy/tracy\": \"^2.6.0\"\n  }\n}\n"
  },
  {
    "path": "test/examples/12-composer/vercel.json",
    "chars": 223,
    "preview": "{\n  \"functions\": {\n    \"api/index.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"routes\": [\n    { \"src\": \"/(."
  },
  {
    "path": "test/examples/13-composer-scripts/.gitignore",
    "chars": 32,
    "preview": "# PHP\n/vendor\n\n# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/13-composer-scripts/.vercelignore",
    "chars": 8,
    "preview": "/vendor\n"
  },
  {
    "path": "test/examples/13-composer-scripts/api/index.php",
    "chars": 3,
    "preview": "OK\n"
  },
  {
    "path": "test/examples/13-composer-scripts/composer.json",
    "chars": 148,
    "preview": "{\n  \"require\": {\n    \"php\": \"^7.4\",\n    \"tracy/tracy\": \"^2.6.0\"\n  },\n  \"scripts\": {\n    \"vercel\": [\n      \"@php --ini\",\n"
  },
  {
    "path": "test/examples/13-composer-scripts/vercel.json",
    "chars": 223,
    "preview": "{\n  \"functions\": {\n    \"api/index.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"routes\": [\n    { \"src\": \"/(."
  },
  {
    "path": "test/examples/14-folders/.gitignore",
    "chars": 17,
    "preview": "# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/14-folders/api/index.php",
    "chars": 17,
    "preview": "<?php\n\necho \"/\";\n"
  },
  {
    "path": "test/examples/14-folders/api/users/index.php",
    "chars": 22,
    "preview": "<?php\n\necho \"/user/\";\n"
  },
  {
    "path": "test/examples/14-folders/api/users/users.php",
    "chars": 31,
    "preview": "<?php\n\necho \"/user/users.php\";\n"
  },
  {
    "path": "test/examples/14-folders/vercel.json",
    "chars": 214,
    "preview": "{\n  \"functions\": {\n    \"api/**/*.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"routes\": [\n    { \"src\": \"/(.*"
  },
  {
    "path": "test/examples/16-php-ini/.gitignore",
    "chars": 17,
    "preview": "# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/16-php-ini/api/index.php",
    "chars": 49,
    "preview": "<?php\nphpinfo();\nvar_dump(opcache_get_status());\n"
  },
  {
    "path": "test/examples/16-php-ini/api/php.ini",
    "chars": 19,
    "preview": "memory_limit=1024M\n"
  },
  {
    "path": "test/examples/16-php-ini/vercel.json",
    "chars": 219,
    "preview": "{\n  \"functions\": {\n    \"api/*.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"routes\": [\n    { \"src\": \"/(.*)\","
  },
  {
    "path": "test/examples/17-zero/.gitignore",
    "chars": 17,
    "preview": "# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/17-zero/api/index.php",
    "chars": 25,
    "preview": "<?php\n\necho \"Index PHP\";\n"
  },
  {
    "path": "test/examples/17-zero/api/test.html",
    "chars": 10,
    "preview": "Test HTML\n"
  },
  {
    "path": "test/examples/17-zero/src/index.txt",
    "chars": 11,
    "preview": "Index HTML\n"
  },
  {
    "path": "test/examples/17-zero/vercel.json",
    "chars": 153,
    "preview": "{\n  \"functions\": {\n    \"api/**/*.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"build\": {\n    \"env\": {\n      "
  },
  {
    "path": "test/examples/18-exclude-files/.gitignore",
    "chars": 17,
    "preview": "# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/18-exclude-files/.vercelignore",
    "chars": 5,
    "preview": "/baz\n"
  },
  {
    "path": "test/examples/18-exclude-files/api/index.php",
    "chars": 25,
    "preview": "<?php\n\necho \"Index PHP\";\n"
  },
  {
    "path": "test/examples/18-exclude-files/baz/index.html",
    "chars": 25,
    "preview": "Ignored by .vercelignore\n"
  },
  {
    "path": "test/examples/18-exclude-files/foo/index.txt",
    "chars": 24,
    "preview": "Ignore by excludedFiles\n"
  },
  {
    "path": "test/examples/18-exclude-files/vercel.json",
    "chars": 185,
    "preview": "{\n  \"functions\": {\n    \"api/**/*.php\": {\n      \"runtime\": \"vercel-php@0.7.3\",\n      \"excludeFiles\": \"foo/**\"\n    }\n  },\n"
  },
  {
    "path": "test/examples/19-server-workers/.gitignore",
    "chars": 17,
    "preview": "# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/19-server-workers/api/index.php",
    "chars": 49,
    "preview": "<?php\nphpinfo();\nvar_dump(opcache_get_status());\n"
  },
  {
    "path": "test/examples/19-server-workers/vercel.json",
    "chars": 200,
    "preview": "{\n  \"functions\": {\n    \"api/*.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"env\": {\n    \"PHP_CLI_SERVER_WORK"
  },
  {
    "path": "test/examples/20-read-files/.gitignore",
    "chars": 17,
    "preview": "# Vercel\n.vercel\n"
  },
  {
    "path": "test/examples/20-read-files/api/index.php",
    "chars": 106,
    "preview": "<?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",
    "chars": 87,
    "preview": "[\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",
    "chars": 147,
    "preview": "{\n  \"functions\": {\n    \"api/*.php\": {\n      \"runtime\": \"vercel-php@0.7.3\"\n    }\n  },\n  \"routes\": [\n    { \"src\": \"/(.*)\","
  },
  {
    "path": "test/spec/index.dev.js",
    "chars": 436,
    "preview": "const builder = require('./../../dist/index');\n\ntest('it should failed using now dev', async () => {\n  const mockLog = c"
  },
  {
    "path": "test/spec/index.js",
    "chars": 224,
    "preview": "const builder = require('./../../dist/index');\n\ntest('creates simple lambda', async () => {\n  await builder.build({\n    "
  },
  {
    "path": "test/spec/launchers/cgi.js",
    "chars": 1416,
    "preview": "const cgi = require('./../../../dist/launchers/cgi');\n\ntest('create CGI request', () => {\n  const request = {\n    entryp"
  },
  {
    "path": "test/spec/path.js",
    "chars": 238,
    "preview": "const path = require('path');\n\ntest('relative path', () => {\n  const rootdir = '/var/task/user';\n  const request = '/var"
  },
  {
    "path": "test/spec/url.js",
    "chars": 508,
    "preview": "const url = require('url');\n\ntest('url.parse search & query are string', () => {\n  const { search, query } = url.parse('"
  },
  {
    "path": "tsconfig.json",
    "chars": 687,
    "preview": "{\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"resolveJsonModule\": true,\n    \"moduleReso"
  }
]

About this extraction

This page contains the full source code of the juicyfx/now-php GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 118 files (62.6 KB), approximately 22.5k tokens, and a symbol index with 47 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!