Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ jobs:
- name: Run Playwright tests
run: |
pnpm run start:ci & \
pnpm wait-on -t 3m http://localhost:3002 tcp:localhost:3001 && \
pnpm wait-on -t 3m http://example-nextjs16.localhost:1355 && \
HOME=/root pnpm exec playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
Expand Down
57 changes: 56 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ This is a **pnpm** monorepo. `pnpm` is enforced (`only-allow`). Most scripts run
- `pnpm run test:int` — Playwright e2e (`--project=chromium`). Starts the seven demo servers unless they are already up, and waits for all seven before the first test.
- `pnpm run tsc` — `tsc --noEmit` typecheck only.
- `pnpm run build` — Rollup build of the library into `dist/` (ESM `.mjs` + CJS `.js` + `.d.ts`).
- `pnpm run dev` — library in Rollup watch mode + all example apps. Next.js 15 demo on `http://localhost:3000`.
- `pnpm run dev` — library in Rollup watch mode + all example apps, each at `http://<package-name>.localhost:1355` (see Demo app URLs).
- `pnpm run kill` — kill hung Next/Vite/wireit processes (use this if dev/test servers hang).
- `pnpm run cleanup` / `pnpm run reinstall` — nuke build artifacts and node_modules.
- `pnpm run setup` — `playwright install --with-deps` (needed before e2e on a fresh machine).
Expand All @@ -28,6 +28,61 @@ This is a **pnpm** monorepo. `pnpm` is enforced (`only-allow`). Most scripts run
Run a single unit test: `npx vitest run packages/urlstate/encoder/encoder.test.ts` (or pass a `-t "name"` filter).
Run a single e2e spec: `npx playwright test tests/useUrlState/main.spec.ts --project=chromium`.

## Demo app URLs

The seven example apps run behind [portless](https://portless.sh), a local
reverse proxy, so each answers on its own hostname instead of a port anyone else
might be holding:

| App | URL |
| --- | --- |
| `example-nextjs14` | `http://example-nextjs14.localhost:1355` |
| `example-nextjs15` | `http://example-nextjs15.localhost:1355` |
| `example-nextjs16` | `http://example-nextjs16.localhost:1355` |
| `example-react` | `http://example-react.localhost:1355` |
| `example-react-router6` | `http://example-react-router6.localhost:1355` |
| `example-remix2` | `http://example-remix2.localhost:1355` |
| `example-react-router7` | `http://example-react-router7.localhost:1355` |

The hostname is each package's own `name`, so portless infers it — nothing in
`package.json` names an app twice. Actual ports are random (4000–4999) and
nobody needs to know them; `portless list` prints the mapping.

Only `example-nextjs16` has a homepage; the other two Next apps answer 404 at
`/` and serve their test routes (`/test-ssr`, `/test-ssr-usp`, …) instead. A
*portless* 404 — "No app registered for …" — means something else: the proxy is
up but the app is not, which is what one crashed service looks like, because
wireit tears down all seven when any of them fails.

`scripts/portless.sh` is the only place the proxy port lives, and it is what
every `dev:*`/`start:*` wireit task runs. It pins two things:

- **Port 1355, not 443 or 80.** Anything under 1024 makes portless auto-elevate
with `sudo`, and portless [spawns the child dev server as root when it
does](https://github.com/vercel-labs/portless/issues/287) — root-owned `.next`
and `node_modules/.vite` caches that the next non-sudo run cannot delete.
- **Plain HTTP.** HTTPS would put a generated CA in the system trust store,
which on Fedora/Arch [can fail outright](https://github.com/vercel-labs/portless/issues/267).

Neither the CA nor `/etc/hosts` is touched: `*.localhost` already resolves to
127.0.0.1 through glibc's `myhostname`, so nothing outside `~/.portless` changes.

CI runs portless too — the e2e URLs are these hostnames, so there is no second
set to keep in sync. `PORTLESS=0 pnpm run dev` skips the proxy entirely and each
app falls back to the fixed port in its own `package.json` (`${PORT:-3000}` and
friends), which is also what `lighthouse:serve` still uses on 3012.

A dev server killed by hand leaves its route behind; `portless prune` clears
those, and every task passes `--force` so a stale route never blocks a restart.

Run the e2e suite from the main checkout. In a *linked* git worktree portless
prepends the branch name as a subdomain — `fix-ui.example-nextjs15.localhost` —
and every URL in `tests/` is a literal, so all seven apps look unregistered. A
worktree on `main`/`master` is exempt and needs nothing. Turning the prefix off
is not an option (`portless run` has no flag for it and `--name` keeps it), and
would be the wrong trade anyway: two worktrees would then `--force` each other's
routes away and quietly serve the other checkout's app.

## Layout

- `packages/urlstate/` — **the library source, and the root npm package itself.** It is deliberately NOT a pnpm workspace member (see `pnpm-workspace.yaml`); it's published from the repo root via `dist/`.
Expand Down
63 changes: 33 additions & 30 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@
],
"service": {
"readyWhen": {
"lineMatches": "http://localhost:3000"
"lineMatches": "Ready in"
Comment thread
asmyshlyaev177 marked this conversation as resolved.
}
},
"command": "pnpm run dev:nextjs15"
Expand All @@ -169,22 +169,23 @@
"command": "pnpm run test:exports && pnpm run test:int"
},
"kill-next": {
"command": "ps aux | grep .next | grep -v grep | awk '{print $2}' | xargs kill -9 2>&1; exit 0"
"command": "ps aux | grep -E 'next-server|next/dist/bin/next' | grep -v grep | awk '{print $2}' | xargs -r kill -9 2>&1; exit 0"
},
"kill-vite": {
"command": "ps aux | grep '.bin/vite' | grep -v grep | awk '{print $2}' | xargs kill -9 2>&1; exit 0"
"command": "ps aux | grep -E 'vite/bin/vite' | grep -v grep | awk '{print $2}' | xargs -r kill -9 2>&1; exit 0"
},
"kill-remix": {
"command": "ps aux | grep -E 'remix-serve|@remix-run/serve' | grep -v grep | awk '{print $2}' | xargs kill -9 2>&1; exit 0"
"command": "ps aux | grep -E 'remix-serve|remix-run/serve' | grep -v grep | awk '{print $2}' | xargs -r kill -9 2>&1; exit 0"
},
"kill-services": {
"command": "ps aux | grep -E '.next|.bin/vite|remix-serve|@remix-run/serve' | grep -v grep | awk '{print $2}' | xargs -r kill -9 2>&1; exit 0"
"command": "ps aux | grep -E 'next-server|next/dist/bin/next|vite/bin/vite|remix-serve|remix-run/serve' | grep -v grep | awk '{print $2}' | xargs -r kill -9 2>&1; exit 0",
"comment": "Patterns match how the servers actually appear in ps, not a loose 'next'. An unescaped dot used to make '.next' match the '-next' in 'example-nextjs16.localhost', so this killed any process whose arguments named a demo app — including CI's own wait-on, which exited the job 137."
},
"kill-wireit": {
"command": "ps axu | grep '[w]ireit' | grep -v grep | awk -v me=$PPID '$2 != me {print $2}' | xargs -r kill -9 2>&1; exit 0"
},
"kill": {
"command": "ps aux | grep -E '.next|.bin/vite|remix-serve|@remix-run/serve' | grep -v grep | awk '{print $2}' | xargs -r kill -9 2>&1; ps axu | grep '[w]ireit' | grep -v grep | awk -v me=$PPID '$2 != me {print $2}' | xargs -r kill -9 2>&1; exit 0"
"command": "ps aux | grep -E 'next-server|next/dist/bin/next|vite/bin/vite|remix-serve|remix-run/serve' | grep -v grep | awk '{print $2}' | xargs -r kill -9 2>&1; ps axu | grep '[w]ireit' | grep -v grep | awk -v me=$PPID '$2 != me {print $2}' | xargs -r kill -9 2>&1; exit 0"
Comment thread
asmyshlyaev177 marked this conversation as resolved.
},
"test:int:ci": {
"command": "pnpm exec cross-env HOME=/root PLAYWRIGHT_BROWSERS_PATH=0 playwright test"
Expand Down Expand Up @@ -218,27 +219,28 @@
]
},
"dev:router6": {
"command": "cd packages/example-react-router6 && pnpm run dev",
"command": "cd packages/example-react-router6 && ../../scripts/portless.sh -- pnpm run dev",
"service": true
},
"dev:router7": {
"command": "cd packages/example-react-router7 && pnpm run dev",
"command": "cd packages/example-react-router7 && ../../scripts/portless.sh -- pnpm run dev",
"service": true
},
"dev:remix2": {
"command": "cd packages/example-remix2 && PORT=3003 pnpm run dev",
"command": "cd packages/example-remix2 && ../../scripts/portless.sh -- pnpm run dev",
"service": true
},
"dev:nextjs14": {
"command": "cd packages/example-nextjs14 && pnpm run dev --turbo",
"command": "cd packages/example-nextjs14 && ../../scripts/portless.sh -- pnpm run dev --turbo",
"service": true
},
"dev:nextjs15": {
"command": "cd packages/example-nextjs15 && pnpm run dev --turbo",
"service": true
"command": "cd packages/example-nextjs15 && ../../scripts/portless.sh -- pnpm run dev",
"service": true,
"comment": "No --turbo: Turbopack 15.5.19 panics with \"Next.js package not found\" against this pnpm layout (it reports version 0.0.0), and one failed service tears down all seven. 14 and 16 are unaffected."
},
"dev:nextjs16": {
"command": "cd packages/example-nextjs16 && pnpm run dev --turbo",
"command": "cd packages/example-nextjs16 && ../../scripts/portless.sh -- pnpm run dev --turbo",
"service": true
},
"build:next14": {
Expand Down Expand Up @@ -293,64 +295,64 @@
"dependencies": [
"build:demo"
],
"command": "cd packages/example-nextjs16 && pnpm run start",
"command": "cd packages/example-nextjs16 && ../../scripts/portless.sh -- pnpm run start",
"service": true
},
"start:demo:ci": {
"command": "cd packages/example-nextjs16 && pnpm run start",
"command": "cd packages/example-nextjs16 && ../../scripts/portless.sh -- pnpm run start",
"service": true
},
"start:react": {
"command": "pnpm exec vite preview --port 5180 packages/example-react",
"command": "cd packages/example-react && ../../scripts/portless.sh -- pnpm run start",
"dependencies": [
"build:react"
],
"service": true
},
"start:react:ci": {
"command": "pnpm exec vite preview --port 5180 packages/example-react",
"command": "cd packages/example-react && ../../scripts/portless.sh -- pnpm run start",
"dependencies": [
"kill-services"
],
"service": true
},
"start:router6": {
"command": "pnpm exec vite preview --port 5181 packages/example-react-router6",
"command": "cd packages/example-react-router6 && ../../scripts/portless.sh -- pnpm run preview",
"dependencies": [
"build:router6"
],
"service": true
},
"start:router6:ci": {
"command": "pnpm exec vite preview --port 5181 packages/example-react-router6",
"command": "cd packages/example-react-router6 && ../../scripts/portless.sh -- pnpm run preview",
"dependencies": [
"kill-services"
],
"service": true
},
"start:router7": {
"command": "pnpm exec vite preview --port 5183 packages/example-react-router7",
"command": "cd packages/example-react-router7 && ../../scripts/portless.sh -- pnpm run preview",
"dependencies": [
"build:router7"
],
"service": true
},
"start:router7:ci": {
"command": "pnpm exec vite preview --port 5183 packages/example-react-router7",
"command": "cd packages/example-react-router7 && ../../scripts/portless.sh -- pnpm run preview",
"dependencies": [
"kill-services"
],
"service": true
},
"start:remix2": {
"command": "cd packages/example-remix2 && pnpm run start",
"command": "cd packages/example-remix2 && ../../scripts/portless.sh -- pnpm run start",
"dependencies": [
"build:remix2"
],
"service": true
},
"start:remix2:ci": {
"command": "cd packages/example-remix2 && pnpm run start",
"command": "cd packages/example-remix2 && ../../scripts/portless.sh -- pnpm run start",
"dependencies": [
"kill-services"
],
Expand All @@ -360,36 +362,36 @@
"dependencies": [
"build:next14"
],
"command": "cd packages/example-nextjs14 && pnpm run start",
"command": "cd packages/example-nextjs14 && ../../scripts/portless.sh -- pnpm run start",
"service": true
},
"start:demo15": {
"dependencies": [
"build:next15"
],
"command": "cd packages/example-nextjs15 && pnpm run start",
"command": "cd packages/example-nextjs15 && ../../scripts/portless.sh -- pnpm run start",
"service": true
},
"start:demo14:ci": {
"command": "cd packages/example-nextjs14 && pnpm run start",
"command": "cd packages/example-nextjs14 && ../../scripts/portless.sh -- pnpm run start",
"dependencies": [
"kill-services"
],
"service": true
},
"start:demo15:ci": {
"command": "cd packages/example-nextjs15 && pnpm run start",
"command": "cd packages/example-nextjs15 && ../../scripts/portless.sh -- pnpm run start",
"service": true
},
"start:demo16": {
"dependencies": [
"build:next16"
],
"command": "cd packages/example-nextjs16 && pnpm run start",
"command": "cd packages/example-nextjs16 && ../../scripts/portless.sh -- pnpm run start",
"service": true
},
"start:demo16:ci": {
"command": "cd packages/example-nextjs16 && pnpm run start",
"command": "cd packages/example-nextjs16 && ../../scripts/portless.sh -- pnpm run start",
"dependencies": [
"kill-services"
],
Expand Down Expand Up @@ -420,7 +422,7 @@
"service": true
},
"dev:react": {
"command": "cd packages/example-react && pnpm run dev",
"command": "cd packages/example-react && ../../scripts/portless.sh -- pnpm run dev",
"service": true
},
"build:packages": {
Expand Down Expand Up @@ -531,6 +533,7 @@
"pin-github-action": "^1.9.1",
"playwright": "^1.55.1",
"playwright-lighthouse": "^4.0.0",
"portless": "^0.15.5",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
"react": "^19.2.6",
Expand Down
4 changes: 2 additions & 2 deletions packages/example-nextjs14/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"private": true,
"type": "module",
"scripts": {
"dev": "pnpm exec next dev --port 3000",
"dev": "pnpm exec next dev --port ${PORT:-3000}",
"build": "pnpm exec next build",
"start": "pnpm exec next start --port 3000",
"start": "pnpm exec next start --port ${PORT:-3000}",
"lint": "pnpm exec next lint"
},
"dependencies": {
Expand Down
4 changes: 2 additions & 2 deletions packages/example-nextjs15/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "pnpm exec next dev --port 3001",
"dev": "pnpm exec next dev --port ${PORT:-3001}",
"build": "pnpm exec next build",
"start": "pnpm exec next start --port 3001",
"start": "pnpm exec next start --port ${PORT:-3001}",
"lint": "pnpm exec next lint"
},
"dependencies": {
Expand Down
9 changes: 9 additions & 0 deletions packages/example-nextjs16/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!-- BEGIN:nextjs-agent-rules -->

# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.

This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.

<!-- END:nextjs-agent-rules -->
1 change: 1 addition & 0 deletions packages/example-nextjs16/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
4 changes: 2 additions & 2 deletions packages/example-nextjs16/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "pnpm run generate-middleware && pnpm exec next dev --port 3002",
"dev": "pnpm run generate-middleware && pnpm exec next dev --port ${PORT:-3002}",
"build": "pnpm run generate-middleware && pnpm exec next build",
"start": "pnpm exec next start --port 3002",
"start": "pnpm exec next start --port ${PORT:-3002}",
"lint": "pnpm exec next lint",
"generate-middleware": "node scripts/generate-middleware-content.cjs"
},
Expand Down
4 changes: 2 additions & 2 deletions packages/example-react-router6/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "pnpm exec vite --port 5181",
"dev": "pnpm exec vite --port ${PORT:-5181}",
"build": "pnpm exec vite build",
"lint": "pnpm exec eslint .",
"preview": "pnpm exec vite preview --port 5181"
"preview": "pnpm exec vite preview --port ${PORT:-5181}"
},
"dependencies": {
"react": "^18.3.1",
Expand Down
4 changes: 2 additions & 2 deletions packages/example-react-router7/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "pnpm exec vite --port 5183",
"dev": "pnpm exec vite --port ${PORT:-5183}",
"build": "pnpm exec vite build",
"lint": "pnpm exec eslint .",
"preview": "pnpm exec vite preview --port 5183"
"preview": "pnpm exec vite preview --port ${PORT:-5183}"
},
"dependencies": {
"react": "^18.3.1",
Expand Down
4 changes: 2 additions & 2 deletions packages/example-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
"type": "module",
"main": "./src/main.tsx",
"scripts": {
"dev": "pnpm exec vite --port 5180",
"dev": "pnpm exec vite --port ${PORT:-5180}",
"build": "pnpm exec vite build",
"lint": "pnpm exec eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"start": "pnpm exec vite preview --port 5180"
"start": "pnpm exec vite preview --port ${PORT:-5180}"
},
"dependencies": {
"react": "^18.3.1",
Expand Down
4 changes: 2 additions & 2 deletions packages/example-remix2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
"type": "module",
"scripts": {
"build": "pnpm exec remix vite:build",
"dev": "pnpm exec remix vite:dev --port 5182",
"dev": "pnpm exec remix vite:dev --port ${PORT:-5182}",
"lint": "pnpm exec eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
"start": "PORT=5182 pnpm exec remix-serve ./build/server/index.js",
"start": "PORT=${PORT:-5182} pnpm exec remix-serve ./build/server/index.js",
"typecheck": "pnpm exectsc"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { defineConfig, devices } from '@playwright/test';
*/
// require('dotenv').config();

const BASE_URL = 'http://127.0.0.1:3002';
const BASE_URL = 'http://example-nextjs16.localhost:1355';

/**
* See https://playwright.dev/docs/test-configuration.
Expand Down
Loading