-
Notifications
You must be signed in to change notification settings - Fork 0
feat: port the Laravel 13 starter app to WFL #724
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
06b3368
78ce058
8115c6c
7b95b19
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| # Design: port laravel/laravel (the app) to WFL | ||
|
|
||
| **Status:** Implemented as `examples/laravel-app/` | ||
| **Source:** [laravel/laravel](https://github.com/laravel/laravel) 13.x | ||
| **Risk class:** R2 (example + HTTP contract; no change to the concurrent | ||
| listen/respond/stream implementation — §11.3 stays on those primitives) | ||
|
|
||
| ## What “the actual app” is | ||
|
|
||
| `laravel/laravel` is the application skeleton users get from `laravel new`. | ||
| It is not the framework. On 13.x the runnable HTTP surface is: | ||
|
|
||
| 1. `GET|HEAD /` → `resources/views/welcome.blade.php` | ||
| 2. `GET|HEAD /up` → framework health registered in `bootstrap/app.php` | ||
| 3. `GET|HEAD /robots.txt` → `User-agent: *` / `Disallow:` | ||
| 4. Unknown paths → 404 | ||
| 5. Other methods on those registered paths → 405 | ||
|
|
||
| Plus unused-but-present scaffolding: empty `Controller`, `User` model, | ||
| `artisan inspire`, Vite/CSS/JS entrypoints, config, migrations. | ||
|
|
||
| ## Approach | ||
|
|
||
| Port the **observable app**, not the framework. | ||
|
|
||
| - Keep WFL idioms (`route`, `include from`, `listen`, containers). | ||
| - Extract routing into testable actions (`status_for_path`, `body_for_path`, | ||
| `content_type_for_path`) so `TestPrograms/` can assert without a socket, | ||
| and `tests/laravel_app_http_test.rs` can assert the real binary over HTTP. | ||
| - Write original welcome-page copy and CSS. Do not reconstruct Blade, | ||
| Tailwind, or Laravel’s logo/SVG. | ||
| - Document every gap in `examples/laravel-app/README.md`. A missing | ||
| framework feature is a documented gap, not a silent pretence. | ||
|
|
||
| ## Rejected alternatives | ||
|
|
||
| 1. **Reimplement Laravel in WFL** (service container, Eloquent, Blade, | ||
| Artisan). WFL does not have those primitives; claiming them would | ||
| violate the docs-honesty rule. | ||
| 2. **Single-file demo that only prints “Hello”.** That is not a port of | ||
| the starter app’s public routes. | ||
| 3. **Copy `welcome.blade.php` verbatim.** Copyrighted; also fights WFL’s | ||
| no-unlearning / original-example bar. | ||
|
|
||
| ## Residual gaps (by design) | ||
|
|
||
| Eloquent, Blade, Vite, Artisan, queues, cache, mail, sessions, | ||
| broadcasting, service providers, and `.env` config beyond bind address. | ||
| See the capability map in the example README. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # Green evidence — Laravel starter-app port | ||
|
|
||
| **Date:** 2026-09-02 | ||
| **Risk class:** R2 — new example and HTTP contract. This change does **not** | ||
| modify `main loop concurrently:`, request handling, or streaming statements | ||
| (testing.md §11.3). Those primitives already have R3 coverage in | ||
| `tests/concurrent_*.rs` / `tests/http_*.rs`; this example only calls them. | ||
| **Red ancestor:** `06b3368` (`test: add failing Laravel starter-app port coverage`) | ||
|
|
||
| ## Layers | ||
|
|
||
| | Layer | Command | Result | | ||
| |---|---|---| | ||
| | WFL feature tests | `./target/release/wfl --test TestPrograms/laravel_app/laravel_starter_app.test.wfl` | 16 passed, 0 failed, exit 0 | | ||
| | Rust HTTP e2e | `cargo test --test laravel_app_http_test` | 5 passed, 0 failed | | ||
| | Inspire CLI | `./target/release/wfl examples/laravel-app/inspire.wfl` | printed `Readability is a feature, not a luxury.` | | ||
| | Hygiene | `python3 scripts/check_repo_hygiene.py --mode static` | exit 0 | | ||
|
|
||
| HTTP e2e (real `wfl` binary, real sockets): `GET /` 200, `GET /up` 200 + “Application up”, `GET /robots.txt` skeleton body, unknown path 404, `POST /` 405. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # Red evidence — Laravel starter-app port | ||
|
|
||
| **Date:** 2026-09-02 | ||
| **Risk class:** R2 (new user-facing example + HTTP contract; no runtime/concurrency change) | ||
| **Command:** `./target/release/wfl --test TestPrograms/laravel_app/laravel_starter_app.test.wfl` | ||
| **Exit:** 1 | ||
| **Result:** 16 tests, 1 passed (`that true is true`), 15 failed for the intended reason. | ||
|
|
||
| Stub router returns `418` / `"stub"` / `"application/octet-stream"` so the failures are assertion mismatches, not missing files or parse errors: | ||
|
|
||
| - `GET /` / `HEAD /` / `GET /up` / `GET /robots.txt` expected 200, got 418 | ||
| - unknown path expected 404, got 418 | ||
| - `POST /` expected 405, got 418 | ||
| - welcome / health / robots / 404 / 405 bodies did not contain the required markers | ||
| - HTML and `text/plain` content types were `application/octet-stream` | ||
| - `User.display_name()` returned `""` instead of the stored name | ||
| - `inspire_quote` length was 0 | ||
|
|
||
| This Red run is an ancestor of the Green implementation commit on `cursor/laravel-app-port-b108`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # 2026-09-02 — Port of laravel/laravel (the starter app) to WFL | ||
|
|
||
| ## What this is | ||
|
|
||
| A faithful-as-able port of the official Laravel **application skeleton** | ||
| ([laravel/laravel](https://github.com/laravel/laravel) 13.x) — the repo you | ||
| get from `laravel new`, not `laravel/framework`. | ||
|
|
||
| ## What mapped | ||
|
|
||
| The 13.x skeleton's public HTTP surface is small, and WFL can express it: | ||
|
|
||
| - `GET /` welcome HTML (original copy and CSS; not a Blade reconstruction) | ||
| - `GET /up` health, the route `bootstrap/app.php` registers | ||
| - `GET /robots.txt` with the skeleton's two-line body | ||
| - unknown paths → 404 | ||
| - non-GET/HEAD on registered paths → 405 | ||
| - a `User` container for `app/Models/User.php`'s name + email fields | ||
| - `inspire.wfl` as the `artisan inspire` stand-in | ||
|
|
||
| Routing is extracted into actions so | ||
| `TestPrograms/laravel_app/laravel_starter_app.test.wfl` can assert without a | ||
| socket, and `tests/laravel_app_http_test.rs` drives the real `wfl` binary | ||
| over HTTP. | ||
|
|
||
| ## What did not map | ||
|
|
||
| Eloquent, Blade, Vite, Artisan, queues, cache, mail, sessions, | ||
| broadcasting, service providers, and most of `.env` / `config/*.php`. Those | ||
| are framework features. The example README's capability map lists them as | ||
| gaps rather than pretending they shipped. | ||
|
|
||
| ## Tests | ||
|
|
||
| Red: stub router returned 418 / `"stub"`; 15 of 16 feature tests failed on | ||
| assertions (`Engineering/evidence/2026-09-02-laravel-app-port-red.md`). | ||
| Green: the same file, plus the HTTP e2e suite. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| // Feature + unit coverage for the WFL port of laravel/laravel (13.x starter app). | ||
| // Mirrors Laravel's tests/Feature/ExampleTest.php (GET / is 200) and | ||
| // tests/Unit/ExampleTest.php, and adds the other public routes the skeleton | ||
| // actually exposes: /up (bootstrap health), /robots.txt, 404, and 405. | ||
|
Comment on lines
+1
to
+4
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Rename this test file to identify its feature.
As per coding guidelines, WFL test-framework files must use feature-oriented 🤖 Prompt for AI AgentsSource: Coding guidelines There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed to |
||
|
|
||
| include from "../../examples/laravel-app/routes.wfl" | ||
| include from "../../examples/laravel-app/user.wfl" | ||
| include from "../../examples/laravel-app/quotes.wfl" | ||
|
|
||
| describe "Example": | ||
| test "that true is true": | ||
| expect yes to be yes | ||
| end test | ||
| end describe | ||
|
|
||
| describe "Laravel starter app routes": | ||
| test "GET / returns 200": | ||
| store reply_status as status_for_path of "/" and "GET" | ||
| expect reply_status to equal 200 | ||
| end test | ||
|
|
||
| test "HEAD / returns 200": | ||
| store reply_status as status_for_path of "/" and "HEAD" | ||
| expect reply_status to equal 200 | ||
| end test | ||
|
|
||
| test "GET /up returns 200": | ||
| store reply_status as status_for_path of "/up" and "GET" | ||
| expect reply_status to equal 200 | ||
| end test | ||
|
|
||
| test "GET /robots.txt returns 200": | ||
| store reply_status as status_for_path of "/robots.txt" and "GET" | ||
| expect reply_status to equal 200 | ||
| end test | ||
|
|
||
| test "GET /missing returns 404": | ||
| store reply_status as status_for_path of "/missing" and "GET" | ||
| expect reply_status to equal 404 | ||
| end test | ||
|
|
||
| test "POST / returns 405": | ||
| store reply_status as status_for_path of "/" and "POST" | ||
| expect reply_status to equal 405 | ||
| end test | ||
| end describe | ||
|
|
||
| describe "Laravel starter app responses": | ||
| test "welcome page identifies the WFL starter app": | ||
| store welcome_html as body_for_path of "/" and "GET" | ||
| expect welcome_html to contain "Your starter app is ready" | ||
| expect welcome_html to contain "WFL starter" | ||
| end test | ||
|
|
||
| test "health page reports the application is up": | ||
| store health_html as body_for_path of "/up" and "GET" | ||
| expect health_html to contain "Application up" | ||
| end test | ||
|
|
||
| test "robots.txt matches the Laravel skeleton": | ||
| store robots_body as body_for_path of "/robots.txt" and "GET" | ||
| expect robots_body to contain "User-agent: *" | ||
| expect robots_body to contain "Disallow:" | ||
| end test | ||
|
|
||
| test "unknown path returns a Not Found body": | ||
| store missing_html as body_for_path of "/no-such-route" and "GET" | ||
| expect missing_html to contain "Not Found" | ||
| end test | ||
|
|
||
| test "POST / returns a method-not-allowed body": | ||
| store not_allowed_body as body_for_path of "/" and "POST" | ||
| expect not_allowed_body to contain "Method Not Allowed" | ||
| end test | ||
|
|
||
| test "HTML routes advertise an HTML content type": | ||
| store welcome_type as content_type_for_path of "/" | ||
| store health_type as content_type_for_path of "/up" | ||
| store missing_type as content_type_for_path of "/missing" | ||
| expect welcome_type to equal "text/html" | ||
| expect health_type to equal "text/html" | ||
| expect missing_type to equal "text/html" | ||
| end test | ||
|
|
||
| test "robots.txt is served as text/plain": | ||
| store robots_type as content_type_for_path of "/robots.txt" | ||
| expect robots_type to equal "text/plain" | ||
| end test | ||
| end describe | ||
|
|
||
| describe "Laravel starter app User model": | ||
| test "a User exposes name and email": | ||
| create new User as demo_user: | ||
| name is "Test User" | ||
| email is "test@example.com" | ||
| end | ||
| store person_name as demo_user.display_name() | ||
| store person_email as demo_user.email_address() | ||
| expect person_name to equal "Test User" | ||
| expect person_email to equal "test@example.com" | ||
| end test | ||
| end describe | ||
|
|
||
| describe "Laravel starter app inspire command": | ||
| test "inspire_quote returns a non-empty quote": | ||
| store quote as inspire_quote | ||
| expect quote to be of type "Text" | ||
| store quote_length as length of quote | ||
| expect quote_length to be greater than 10 | ||
| end test | ||
| end describe | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Bind the starter app to loopback. Override to 0.0.0.0 to expose it. | ||
| web_server_bind_address = 127.0.0.1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| # WFL port of the Laravel application skeleton | ||
|
|
||
| This directory is a WFL port of the official | ||
| [laravel/laravel](https://github.com/laravel/laravel) **starter app** (13.x), | ||
| not the Laravel framework. | ||
|
|
||
| Laravel's application repo is intentionally thin: a welcome page, a health | ||
| endpoint, a `robots.txt`, an empty controller, a User model, and an `inspire` | ||
| console command. Everything else (Eloquent, Blade, Artisan, Vite, queues, | ||
| mail, sessions) lives in `laravel/framework` and is **not** claimed here. | ||
|
|
||
| ## Run | ||
|
|
||
| ```bash | ||
| wfl examples/laravel-app/app.wfl | ||
| ``` | ||
|
|
||
| Then open `http://127.0.0.1:8000`. | ||
|
|
||
| ```bash | ||
| wfl examples/laravel-app/inspire.wfl # artisan inspire equivalent | ||
| ``` | ||
|
|
||
| `.wflcfg` binds the server to `127.0.0.1`. Change `web_server_bind_address` to | ||
| `0.0.0.0` only when you intend to expose it. | ||
|
|
||
| ## Capability map | ||
|
|
||
| | Laravel 13.x skeleton | WFL port | Status | | ||
| |---|---|---| | ||
| | `GET /` → `view('welcome')` | `welcome_page` HTML | Mapped (original copy/CSS) | | ||
| | `GET /up` health (`bootstrap/app.php`) | `/up` → 200 + “Application up” | Mapped | | ||
| | `public/robots.txt` | same two-line body | Mapped | | ||
| | Unknown path | 404 HTML | Mapped | | ||
| | `Route::get` (GET/HEAD only) | other methods → 405 | Mapped | | ||
| | `app/Models/User.php` | `User` container (`name`, `email`) | Partial — no Eloquent, casts, or auth | | ||
| | `artisan inspire` | `inspire.wfl` / `inspire_quote` | Partial — original quote, not the framework catalog | | ||
| | `app/Http/Controllers/Controller.php` | (empty in Laravel) | Nothing to port | | ||
| | `app/Providers/AppServiceProvider.php` | — | Not ported (no service container) | | ||
| | Blade / Vite / Tailwind | inline HTML in `views.wfl` | Not ported | | ||
| | Eloquent, migrations, factories, seeders | — | Not ported | | ||
| | Artisan, queues, cache, mail, sessions, broadcasting | — | Not ported | | ||
| | `.env` / `config/*.php` | `.wflcfg` bind address only | Partial | | ||
| | PHPUnit Feature/Unit example tests | `TestPrograms/laravel_app/laravel_starter_app.test.wfl` | Mapped + expanded | | ||
|
|
||
| ## Layout | ||
|
|
||
| ```text | ||
| app.wfl front controller (public/index.php + listen) | ||
| routes.wfl route table (routes/web.php + /up) | ||
| views.wfl welcome, health, 404, robots bodies | ||
| user.wfl User container | ||
| quotes.wfl inspire_quote | ||
| inspire.wfl console inspire command | ||
| public/robots.txt crawler policy | ||
| .wflcfg loopback bind | ||
| ``` | ||
|
|
||
| ## Tests | ||
|
|
||
| ```bash | ||
| wfl --test TestPrograms/laravel_app/laravel_starter_app.test.wfl | ||
| cargo test --test laravel_app_http_test | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| // Front controller for the WFL port of laravel/laravel 13.x. | ||
| // Mirrors public/index.php + bootstrap/app.php: listen, then dispatch routes. | ||
|
|
||
| include from "routes.wfl" | ||
|
|
||
| store listen_port as 8000 | ||
|
|
||
| display "WFL starter (laravel/laravel port) on port " with listen_port | ||
| display "Routes: GET / , GET /up , GET /robots.txt" | ||
|
|
||
| create map allow_get_head: | ||
| "Allow" is "GET, HEAD" | ||
| end map | ||
|
|
||
| listen on port listen_port as laravel_app | ||
|
|
||
| main loop concurrently: | ||
| wait for request comes in on laravel_app as incoming_request | ||
| store request_path as path of incoming_request | ||
| store request_method as method of incoming_request | ||
| store reply_status as status_for_path of request_path and request_method | ||
| store reply_body as body_for_path of request_path and request_method | ||
| store reply_type as content_type_for_path of request_path | ||
| check if reply_status is equal to 405: | ||
| respond to incoming_request with reply_body and status reply_status and content_type reply_type and headers allow_get_head | ||
| otherwise: | ||
| respond to incoming_request with reply_body and status reply_status and content_type reply_type | ||
| end check | ||
| end loop |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| // Equivalent of `php artisan inspire` from routes/console.php. | ||
| include from "quotes.wfl" | ||
| store quote as inspire_quote | ||
| display quote |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| User-agent: * | ||
| Disallow: |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| // Stand-in for Laravel's `artisan inspire` command. The quote list is original | ||
| // WFL text, not the framework's Inspiring::quote() catalog. | ||
|
|
||
| define action called inspire_quote: | ||
| return "Readability is a feature, not a luxury." | ||
| end action |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the reviewed commit,
git merge-base --is-ancestor 06b3368 88c3723exits 1, and88c3723has36b5444as its sole parent, so this advertised Red commit exists only on a separate history while the reviewed commit introduces the tests and implementation together. This makes the evidence inaccurate and removes the required auditable Red→Green record; preserve the test-only commit as an ancestor or reference a qualifying timestamped pre-Green artifact instead.AGENTS.md reference: AGENTS.md:L5-L7
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
06b3368is still an ancestor of this branch. Current HEAD is7b95b19; parents are7b95b19 → 8115c6c → 78ce058 → 06b3368 → 36b5444.git merge-base --is-ancestor 06b3368 HEADexits 0. The SHA88c3723is not in this repository — that ancestry check was against a commit that is not this PR.