From c0b85b8ac6877b90175e50e25771d2553f61bb56 Mon Sep 17 00:00:00 2001 From: "Ricardo Q. Bazan" Date: Sun, 18 Oct 2026 12:45:02 -0500 Subject: [PATCH 1/5] docs: point CLAUDE.md at the development guide, and say the SEO audit runs by hand Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TzjvWWSt2KQNsVgAtswGTL --- CLAUDE.md | 15 +-------------- docsite/CLAUDE.md | 4 ++-- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index a1fe952..367c3a9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,18 +1,5 @@ # env -Contract-first environment configuration with typed schemas. Runtime-agnostic core (Node, Bun, Deno, browser, Workers, Edge) with opt-in adapters for Node, Vite, React (SSR), and a curated set of Zod-based environment schema primitives. +The monorepo of `@variablelab/env`: the library, its docs site and its examples. See [docs/development.md](./docs/development.md) for setup, tooling, and commands. - -## Repository structure - -Monorepo managed with pnpm workspaces + Turborepo: - -- [`package/`](./package/README.md) — the `@variablelab/env` npm package (source & tests) -- [`docsite/`](./docsite/README.md) — Fumadocs; published at [lab.variable.land/env/docs](https://lab.variable.land/env/docs) -- [`examples/`](./examples/README.md) — runnable example apps with Playwright e2e suites -- [`docs/`](./docs) — repository docs (`contributing.md`, `development.md`) - -## Release - -- This project use changesets to manage releases, never touch the `CHANGELOG.md` or bump `package.json` file directly, it is generated by changesets. diff --git a/docsite/CLAUDE.md b/docsite/CLAUDE.md index 569b2b6..a7155ef 100644 --- a/docsite/CLAUDE.md +++ b/docsite/CLAUDE.md @@ -1,6 +1,6 @@ # docsite — agent notes -Workspace-specific guidance. The general project layout and run commands live in [README.md](./README.md). +Workspace-specific guidance. Run commands are in [docs/development.md](../docs/development.md#working-on-the-docs-docsite), and the layout is in [README.md](./README.md). ## SEO audit (Lighthouse CI) @@ -19,6 +19,6 @@ The pinned `@lhci/cli@X.Y.Z` version lives in two places that **must be kept in When bumping the version, update both — otherwise CI keeps restoring the old `~/.cache/pnpm/dlx` and the new bin is re-downloaded every run, defeating the cache. -URLs and budgets live in [`.lighthouserc.cjs`](./.lighthouserc.cjs). The URL list is parsed from `dist/client/env/sitemap.xml` (produced by prerendering [`src/routes/sitemap[.]xml.ts`](./src/routes/sitemap[.]xml.ts) during `vite build`) — new docs are picked up automatically. Budgets: SEO and best-practices pinned at 100 (`error`); accessibility ≥90 and performance ≥50 (`warn`). Any regression in `meta-description`, `document-title`, `is-crawlable`, etc. fails the PR. +URLs and budgets live in [`.lighthouserc.cjs`](./.lighthouserc.cjs). The URL list is parsed from `dist/client/env/sitemap.xml` (produced by prerendering [`src/routes/sitemap[.]xml.ts`](./src/routes/sitemap[.]xml.ts) during `vite build`) — new docs are picked up automatically. Budgets: SEO and best-practices pinned at 100 (`error`); accessibility ≥90 and performance ≥50 (`warn`). Any regression in `meta-description`, `document-title`, `is-crawlable` and so on fails the audit run. In CI the audit is the "Audit SEO" job of `docsite.yml`, which runs only by hand (`workflow_dispatch`); [docs/development.md](../docs/development.md#workflows) lists what runs on a pull request. **Why Lighthouse CI instead of Unlighthouse:** an earlier version of this workflow used Unlighthouse. It worked but `unlighthouse-ci` leaks Chrome process handles after a successful scan and never exits cleanly — under GitHub Actions the step would hang several minutes after the audit finished. LHCI handles process cleanup correctly and is the more battle-tested CI primitive. From c574b18ab00c41bf07a7c17bd69b9b094a5de3d8 Mon Sep 17 00:00:00 2001 From: "Ricardo Q. Bazan" Date: Sun, 18 Oct 2026 12:45:03 -0500 Subject: [PATCH 2/5] docs: keep the examples list in one place, and fix stale tasks, counts and steps Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TzjvWWSt2KQNsVgAtswGTL --- docs/contributing.md | 24 ++++++++++++------------ docs/development.md | 44 +++++++++++--------------------------------- docsite/README.md | 1 - examples/README.md | 42 +++++++++++++++++++++--------------------- 4 files changed, 44 insertions(+), 67 deletions(-) diff --git a/docs/contributing.md b/docs/contributing.md index 81a98e8..aa67349 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -18,7 +18,8 @@ Branch from `main` with a short prefix that mirrors the change type: - `feat/`: a new feature or new public API. - `fix/`: a bug fix. - `docs/`: a docs-only change. -- `chore/`: tooling, CI, dependencies, or a refactor with no behavior change. +- `ci/`: a change to the CI workflows. +- `chore/`: tooling, dependencies, or a refactor with no behavior change. - `test/`: a test-only change. The `feat/` and `fix/` prefixes also publish a preview release (see [Preview releases](#preview-releases)). @@ -31,14 +32,14 @@ Commits follow [Conventional Commits](https://www.conventionalcommits.org/), kep feat(zod): add zEnum primitive for case-insensitive enums fix(react): EnvScript no longer double-encodes the JSON payload docs: clarify resolution order for runtimeEnv -chore(ci): bump jdx/mise-action to v2 +ci: bump jdx/mise-action to v4.3.0 ``` The pull request title takes the same form, because it becomes the squash-merge commit. ### Checklist -- [ ] The branch is named `feat/*`, `fix/*`, `docs/*`, `chore/*` or `test/*`, and is up to date with `main`. +- [ ] The branch is named `feat/*`, `fix/*`, `docs/*`, `ci/*`, `chore/*` or `test/*`, and is up to date with `main`. - [ ] The pull request title follows Conventional Commits. - [ ] Tests cover the change: unit tests in `package/src/__tests__/`, and e2e tests in `examples//test/e2e/` when relevant. - [ ] `mise run lib:test` and `mise run check` pass locally. @@ -92,21 +93,20 @@ When pull requests with changesets land on `main`, the release job in [`ci.yml`] Examples are runtime-isolated demos. Each one installs the packed `@variablelab/env` tarball, as an outside consumer would, with its own lockfile and outside the pnpm workspace. To add one: -1. Create the directory under `examples//` with the source for the demo, and give it ports no other example uses (see [Ports](../examples/README.md#ports)). -2. Add a `package.json` that consumes `@variablelab/env` from the local tarball: +1. Create the directory under `examples//` with the source for the demo, and give it ports no other example uses (the [examples list](../examples/README.md) gives the ports of each example). +2. Add a `package.json` that consumes `@variablelab/env` from the local tarball, with the runtime's own devDependencies, such as `@types/bun` for Bun or `@types/deno` for Deno: ```jsonc "@variablelab/env": "file:../../package/.local/variablelab-env.tgz" ``` A pnpm example also copies the `.npmrc` from an existing pnpm example. -3. Add a `mise.toml` with the runtime tools (`node`, `bun`, `deno` or `pnpm`) and the standard tasks (`setup`, `start`, `test:e2e`, `check`). Use the existing example with the closest runtime as a template. +3. Add a `mise.toml` with the runtime tools (`node`, `bun`, `deno` or `pnpm`) and the [per-example tasks](development.md#per-example-tasks) in the development guide. Use the existing example with the closest runtime as a template. 4. Add a `biome.json` extending `@rrlab/biome-config`, except in a Deno-only example. -5. Add the path to `[monorepo].config_roots` in the root [`mise.toml`](../mise.toml). -6. Add a row to the matrix in [`.github/workflows/e2e.yml`](../.github/workflows/e2e.yml). -7. Update the table in [`examples/README.md`](../examples/README.md). -8. Run the install and the e2e suite, and commit the generated lockfile. -9. From the new directory, run `mise lock -p linux-x64,linux-x64-musl,linux-arm64,linux-arm64-musl,macos-x64,macos-arm64,windows-x64` and commit its `mise.lock`. CI installs tools with `--locked`, so an example without one fails E2E. +5. Add a row to the matrix in [`.github/workflows/e2e.yml`](../.github/workflows/e2e.yml). +6. Add the example to the list in [`examples/README.md`](../examples/README.md). +7. Run the install and the e2e suite, and commit the generated lockfile. +8. From the new directory, run `mise lock -p linux-x64,linux-x64-musl,linux-arm64,linux-arm64-musl,macos-x64,macos-arm64,windows-x64` and commit its `mise.lock`. CI installs tools with `--locked`, so an example without one fails E2E. -The setup mechanics, such as which package manager fits which runtime, why Deno needs `nodeModulesDir: "manual"` and the rule to depend on `//:env:pack`, are in the [development guide](development.md#working-on-the-examples-examples). +The setup mechanics are in the development guide: [how the examples consume the library](development.md#how-the-examples-consume-the-library), [why Deno needs `nodeModulesDir: "manual"`](development.md#deno-cant-import-the-library-from-the-tarball) and [the rule to depend on `//:env:pack`](development.md#task-not-found-envpack). [`examples/README.md`](../examples/README.md) gives the package manager of each example. ## Code review diff --git a/docs/development.md b/docs/development.md index 6dca65f..bf386c5 100644 --- a/docs/development.md +++ b/docs/development.md @@ -12,6 +12,8 @@ env/ ├── docs/ # Repository docs (contributing.md, development.md) ├── mise.toml # Tool versions + task orchestration ├── mise.lock # Locked tool versions + checksums (each example has its own) +├── mise.audit.toml # The dependency audit's tools, added with MISE_ENV=audit +├── mise.audit.lock # Locked versions + checksums of those tools ├── pnpm-workspace.yaml # Workspace for `package` + `docsite` only ├── lefthook.yml # Git hooks (Biome and the type check on commit, tests on push) └── biome.json # Root biome config (excludes examples/**) @@ -70,7 +72,7 @@ mise run check # JS & TS check across package + docsite mise run test:e2e # the e2e suite of every example ``` -Run `mise tasks` from anywhere in the repo to see the full list. +Run `mise tasks` to list the root tasks and those of the directory you are in, or `mise tasks ls --all` to list every example's tasks too. ### Root tasks (in [`mise.toml`](../mise.toml)) @@ -88,13 +90,13 @@ Run `mise tasks` from anywhere in the repo to see the full list. | `examples:check` | JS & TS check across every example. | | `examples:bump` | `reinstall` the tarball in every example. | -### Per-example tasks (the same in every `examples/*/mise.toml`) +### Per-example tasks | Task | What it does | | ------------- | ------------------------------------------------------------------------------------ | | `setup` | Pack env and install deps with the example's own package manager (depends on `//:env:pack`). | | `reinstall` | Force-reinstall the env tarball. | -| `start` | Run the server (or `dev`, `build` and `preview` in the Vite-based ones). | +| `start` | Run the server. The SPA examples have `dev`, `build` and `preview` instead, and the SSR and Next.js examples add `dev` and `build`. | | `test:e2e` | Playwright. | | `check` | JS & TS check. | @@ -119,13 +121,14 @@ On every pull request to `main`: - **E2E** ([`e2e.yml`](../.github/workflows/e2e.yml)): a matrix job per example, when the pull request touches `examples/`, `package/`, the workflow itself, the pnpm lockfile or workspace, or the root mise files. Each job sets up its own runtime with `jdx/mise-action` v4.3.0 (with `working_directory: examples/`), packs the library with `mise run env:pack`, and runs `mise run //examples/:test:e2e`. - **Examples** ([`examples.yml`](../.github/workflows/examples.yml)): one job, on the same paths as E2E, that installs each example's tools, packs the library and runs `mise run examples:check`. - **Workflow audit** ([`workflow-audit.yml`](../.github/workflows/workflow-audit.yml)): zizmor on the workflows in `.github/`. +- **Docsite** ([`docsite.yml`](../.github/workflows/docsite.yml)): the Build job runs `pnpm --filter docsite build`, which builds the docs site without deploying it. [Workers Builds](../docsite/README.md#deploying) deploys `main`. CI, E2E and Examples use the experimental mise monorepo feature (`MISE_EXPERIMENTAL=1`). Outside pull requests: - **Dependency audit** ([`dependency-audit.yml`](../.github/workflows/dependency-audit.yml)): every Monday, an audit of every lockfile in the repo. It keeps an issue labeled `dependency-audit` open while it finds known vulnerabilities. -- **Docsite** ([`docsite.yml`](../.github/workflows/docsite.yml)): a Lighthouse SEO audit of the docs site, run by hand. +- **Docsite** ([`docsite.yml`](../.github/workflows/docsite.yml)): the Audit SEO job, a Lighthouse SEO audit of the docs site, run by hand. ### Git hooks @@ -190,12 +193,12 @@ Content lives in `docsite/content/docs/` as MDX. [`docsite/README.md`](../docsit ## Working on the examples (`examples/*`) -Every example has the same task names, so you can `cd` into any of them and run: +Every example has `setup`, `reinstall`, `test:e2e` and `check`, so you can `cd` into any of them and run: ```sh mise run setup # pack env + install deps (uses the example's native PM) mise run reinstall # force-reinstall the env tarball -mise run start # run the server / dev server +mise run start # the server (`mise run dev` in the SPA examples) mise run test:e2e # Playwright mise run check # JS & TS check ``` @@ -219,36 +222,11 @@ Every example declares the dependency as a local file tarball: ### The examples -Each item names the runtime, the package manager and what the example shows. [`examples/README.md`](../examples/README.md) lists the entry points each one exercises. - -- [`backend-node`](../examples/backend-node): Node 24, with pnpm. A plain Node server. -- [`backend-bun`](../examples/backend-bun): Bun 1.2.4, with bun. A native Bun consumer. -- [`backend-deno`](../examples/backend-deno): Deno 2 for the server and Node 24 for the test runner, with bun. Deno can't extract `file:` tarballs natively, so `bun install` hydrates `node_modules/` (flat layout) and Deno reads it through `nodeModulesDir: "manual"`. -- [`backend-node-cjs`](../examples/backend-node-cjs): Node 24, with pnpm. `loadConfig` (sync), with the server booted from a CommonJS `require()` entry. -- [`backend-bun-cjs`](../examples/backend-bun-cjs): Bun 1.2.4, with bun. `loadConfig` (sync) under a Bun CommonJS `require()` entry. -- [`backend-deno-cjs`](../examples/backend-deno-cjs): Deno 2 for the server and Node 24 for the test runner, with bun. `loadConfig` (sync) under a Deno CommonJS `require()` entry. -- [`worker-cloudflare`](../examples/worker-cloudflare): Cloudflare Workers, with pnpm. It checks `runtimeEnv: c.env` per request, without the `nodejs_compat` flag. -- [`edge-nextjs`](../examples/edge-nextjs): Next.js Edge, with pnpm. It checks the Edge runtime path. -- [`spa-vite-plugin`](../examples/spa-vite-plugin): Vite and React, with pnpm. The `envConfig()` plugin and the `#config` alias. -- [`spa-vite-dynamic`](../examples/spa-vite-dynamic): Vite and React, with pnpm. The dynamic `import()` pattern. -- [`ssr-react-router`](../examples/ssr-react-router): React Router 7, with pnpm. `` for SSR and hydration. -- [`ssr-tanstack-start`](../examples/ssr-tanstack-start): TanStack Start, with pnpm. `` through TanStack Start. - -The `backend-*`, `worker-*` and `edge-nextjs` examples drive Playwright in HTTP-only mode (the `request` fixture). The SPA and SSR examples drive a real Chromium browser, so they need `mise run playwright:install` first, which `setup` already runs. +The examples, with the runtime, package manager, entry points and what each one shows, are listed in [`examples/README.md`](../examples/README.md). ### Adding a new example -The full step-by-step is in the [contributing guide](contributing.md#adding-a-new-example). The shape is: - -1. A new directory under `examples//`. -2. A `package.json` with `"@variablelab/env": "file:../../package/.local/variablelab-env.tgz"` and the runtime's own devDependencies, such as `@types/bun` for Bun or `@types/deno` for Deno. -3. A `mise.toml` with `[tools]` and the standard tasks, copied from the closest existing example. -4. A `biome.json` extending `@rrlab/biome-config`, except in a Deno-only example. -5. The path in `[monorepo].config_roots` in the root [`mise.toml`](../mise.toml). -6. A row in the matrix in [`.github/workflows/e2e.yml`](../.github/workflows/e2e.yml). -7. A row in the table in [`examples/README.md`](../examples/README.md). -8. `mise run setup` from the new directory, and a commit of the generated lockfile. -9. `mise lock` for every platform from the new directory, and a commit of its `mise.lock` (CI installs tools with `--locked`). +Follow [Adding a new example](contributing.md#adding-a-new-example) in the contributing guide. ## Troubleshooting diff --git a/docsite/README.md b/docsite/README.md index 1e874f5..f9bc4f5 100644 --- a/docsite/README.md +++ b/docsite/README.md @@ -32,7 +32,6 @@ pnpm dev # local dev server pnpm build # production build (Vite + Cloudflare adapter) pnpm preview # preview the built bundle pnpm run deploy # build + wrangler deploy -pnpm test:types # typechecks ``` `docsite/` lives in the small pnpm workspace alongside [`package/`](../package). It is **not** runtime-isolated like the [`examples/`](../examples) — those each declare their own runtime via mise and use a packed tarball of `@variablelab/env`. diff --git a/examples/README.md b/examples/README.md index d8c96b3..eaebe9b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,23 +1,23 @@ -# `@variablelab/env` examples +# env examples Real-world usage examples for [`@variablelab/env`](../package). **Each example is runtime-isolated**: it declares its own runtime in a local `mise.toml`, brings its own package manager and lockfile, and consumes `@variablelab/env` from a packed tarball — exactly as an external consumer would. End-to-end tests use Playwright and exercise real `env` failure modes (missing required vars, wrong types, per-mode config isolation, SSR↔client hydration drift, config loaded synchronously from a CommonJS/`require()` entry). -| Example | Runtime | Package manager | `env` entries exercised | -| -------------------------------------------- | ---------------------------------------- | --------------- | -------------------------------------------------------------------------------- | -| [`backend-node`](./backend-node) | Node.js 24 | pnpm | `@variablelab/env` + `@variablelab/env/fs` + `@variablelab/env/zod` | -| [`backend-bun`](./backend-bun) | Bun 1.2.4 | bun | `@variablelab/env` + `@variablelab/env/fs` + `@variablelab/env/zod` | -| [`backend-deno`](./backend-deno) | Deno 2 (server) + Node 24 (test runner) | bun | `@variablelab/env` + `@variablelab/env/fs` + `@variablelab/env/zod` | -| [`backend-node-cjs`](./backend-node-cjs) | Node.js 24 | pnpm | `@variablelab/env` + `@variablelab/env/fs` (sync `loadConfig` via `require()`) + `@variablelab/env/zod` | -| [`backend-bun-cjs`](./backend-bun-cjs) | Bun 1.2.4 | bun | `@variablelab/env` + `@variablelab/env/fs` (sync `loadConfig` via `require()`) + `@variablelab/env/zod` | -| [`backend-deno-cjs`](./backend-deno-cjs) | Deno 2 (server) + Node 24 (test runner) | bun | `@variablelab/env` + `@variablelab/env/fs` (sync `loadConfig` via `require()`) + `@variablelab/env/zod` | -| [`worker-cloudflare`](./worker-cloudflare) | Cloudflare Workers (wrangler) | pnpm | `@variablelab/env` (`runtimeEnv: c.env`, per-request) + `@variablelab/env/zod` | -| [`edge-nextjs`](./edge-nextjs) | Next.js Edge runtime | pnpm | `@variablelab/env` + `@variablelab/env/zod` (no FS on Edge) | -| [`spa-vite-plugin`](./spa-vite-plugin) | Node.js (Vite) | pnpm | `@variablelab/env` + `@variablelab/env/vite` (`envConfig()` + `#config`) | -| [`spa-vite-dynamic`](./spa-vite-dynamic) | Node.js (Vite) | pnpm | `@variablelab/env` (dynamic `import()`) + `@variablelab/env/vite` (for `__ENV_NAME__`) | -| [`ssr-react-router`](./ssr-react-router) | Node.js (React Router 7) | pnpm | `@variablelab/env` + `@variablelab/env/fs` + `@variablelab/env/react` (``) | -| [`ssr-tanstack-start`](./ssr-tanstack-start) | Node.js (TanStack Start) | pnpm | `@variablelab/env` + `@variablelab/env/vite` + `@variablelab/env/react` | - -The `backend-*`, `worker-*`, and `edge-nextjs` examples drive Playwright in HTTP-only mode (the `request` fixture); the 4 SPA / SSR examples drive a real chromium browser. +Each item names the runtime, the package manager, what the example shows and the entry points it imports. + +- [`backend-node`](./backend-node): Node 24, with pnpm. A plain Node server. Imports `@variablelab/env`, `@variablelab/env/fs` and `@variablelab/env/zod`. +- [`backend-bun`](./backend-bun): Bun 1.2.4, with bun. A native Bun consumer. Imports `@variablelab/env`, `@variablelab/env/fs` and `@variablelab/env/zod`. +- [`backend-deno`](./backend-deno): Deno 2 for the server and Node 24 for the test runner, with bun. A Deno server whose `node_modules/` bun installs: see [How `@variablelab/env` is consumed](#how-variablelabenv-is-consumed). Imports `@variablelab/env`, `@variablelab/env/fs` and `@variablelab/env/zod`. +- [`backend-node-cjs`](./backend-node-cjs): Node 24, with pnpm. `loadConfig` (sync), with the server booted from a CommonJS `require()` entry. Imports `@variablelab/env`, `@variablelab/env/fs` and `@variablelab/env/zod`. +- [`backend-bun-cjs`](./backend-bun-cjs): Bun 1.2.4, with bun. `loadConfig` (sync) under a Bun CommonJS `require()` entry. Imports `@variablelab/env`, `@variablelab/env/fs` and `@variablelab/env/zod`. +- [`backend-deno-cjs`](./backend-deno-cjs): Deno 2 for the server and Node 24 for the test runner, with bun. `loadConfig` (sync) under a Deno CommonJS `require()` entry. Imports `@variablelab/env`, `@variablelab/env/fs` and `@variablelab/env/zod`. +- [`worker-cloudflare`](./worker-cloudflare): Cloudflare Workers through wrangler, with pnpm. `runtimeEnv: c.env` per request, without the `nodejs_compat` flag. Imports `@variablelab/env` and `@variablelab/env/zod`. +- [`edge-nextjs`](./edge-nextjs): the Next.js Edge runtime, with pnpm. The Edge runtime path, which has no filesystem. Imports `@variablelab/env` and `@variablelab/env/zod`. +- [`spa-vite-plugin`](./spa-vite-plugin): Vite and React, with pnpm. The `envConfig()` plugin and the `#config` alias. Imports `@variablelab/env`, `@variablelab/env/vite` and `@variablelab/env/zod`. +- [`spa-vite-dynamic`](./spa-vite-dynamic): Vite and React, with pnpm. The dynamic `import()` pattern, with the plugin only for `__ENV_NAME__`. Imports `@variablelab/env`, `@variablelab/env/vite` and `@variablelab/env/zod`. +- [`ssr-react-router`](./ssr-react-router): React Router 7, with pnpm. `` for SSR and hydration. Imports `@variablelab/env`, `@variablelab/env/fs`, `@variablelab/env/react` and `@variablelab/env/zod`. +- [`ssr-tanstack-start`](./ssr-tanstack-start): TanStack Start, with pnpm. `` through TanStack Start. Imports `@variablelab/env`, `@variablelab/env/vite`, `@variablelab/env/react` and `@variablelab/env/zod`. + +The `backend-*`, `worker-*` and `edge-nextjs` examples drive Playwright in HTTP-only mode (the `request` fixture), and the SPA and SSR examples drive a real Chromium browser. ## How `@variablelab/env` is consumed @@ -29,14 +29,14 @@ Each example declares the dependency as a local file tarball: The tarball is generated by `mise run env:pack` (which calls `pnpm pack` inside `package/`). This guarantees each example consumes the package **as published** — same `files`, same `publishConfig.exports`, same peerDeps — instead of reaching into the workspace source tree. -`backend-deno` is the special case: `deno install` / `deno add` don't extract `file:` tarballs — they leave `node_modules/@variablelab/env` as a dangling symlink to the `.tgz`. So we use `bun install` to hydrate the directory (bun extracts the tarball into a flat layout) and let Deno read it via `nodeModulesDir: "manual"`. Deno still owns the runtime; bun is only there to do the install step. +The Deno examples are the special case: `deno install` / `deno add` don't extract `file:` tarballs — they leave `node_modules/@variablelab/env` as a dangling symlink to the `.tgz`. So we use `bun install` to hydrate the directory (bun extracts the tarball into a flat layout) and let Deno read it via `nodeModulesDir: "manual"`. Deno still owns the runtime; bun is only there to do the install step. ## Setup (from the monorepo root) ```sh mise install # node + pnpm; bun/deno installed per-example as needed mise run setup # bootstraps everything: tools, root deps, env tarball, all examples, Playwright browsers -mise run test:e2e # runs all 12 e2e suites +mise run test:e2e # runs the e2e suite of every example ``` ## Run a single example @@ -47,7 +47,7 @@ Each task is invoked from the monorepo root with the `//path:task` syntax: mise run //examples/backend-node:test:e2e mise run //examples/backend-bun:start mise run //examples/spa-vite-plugin:dev -mise run //examples/ssr-react-router:test:types +mise run //examples/ssr-react-router:check ``` Or `cd` into the example and run tasks unprefixed: @@ -70,7 +70,7 @@ mise run env:watch mise run //examples/backend-node:start ``` -`env:watch` runs `tsdown --watch` plus a chokidar that pipes into `env:pack` + `examples:bump` (which `reinstall`s the tarball in every example). +`env:watch` runs chokidar on `package/src/`, which calls `env:pack` and then `examples:bump` (which `reinstall`s the tarball in every example) on every change. ## Why isolated? From fb575137d378000f1466ed52f0f97ecb8facd8f4 Mon Sep 17 00:00:00 2001 From: "Ricardo Q. Bazan" Date: Sun, 18 Oct 2026 12:45:04 -0500 Subject: [PATCH 3/5] docs(docsite): match the pages to the code Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TzjvWWSt2KQNsVgAtswGTL --- docsite/content/docs/api-reference/core.mdx | 6 +++--- docsite/content/docs/api-reference/index.mdx | 4 ++-- docsite/content/docs/api-reference/react.mdx | 4 ++-- docsite/content/docs/api-reference/zod.mdx | 5 +++-- docsite/content/docs/community.mdx | 2 +- docsite/content/docs/concepts/env-name.mdx | 2 +- docsite/content/docs/concepts/env-var-naming.mdx | 2 +- docsite/content/docs/concepts/overview.mdx | 7 ++++--- docsite/content/docs/concepts/resolution.mdx | 10 ++++++---- docsite/content/docs/getting-started/installation.mdx | 2 +- docsite/content/docs/guides/custom-modes.mdx | 6 +++--- docsite/content/docs/guides/fs-loadconfig.mdx | 2 +- docsite/content/docs/guides/index.mdx | 4 ++-- docsite/content/docs/guides/spa-vite-plugin.mdx | 2 +- 14 files changed, 31 insertions(+), 27 deletions(-) diff --git a/docsite/content/docs/api-reference/core.mdx b/docsite/content/docs/api-reference/core.mdx index 22f325b..4fb26db 100644 --- a/docsite/content/docs/api-reference/core.mdx +++ b/docsite/content/docs/api-reference/core.mdx @@ -4,17 +4,17 @@ description: Exports from the @variablelab/env entrypoint — the runtime-agnost icon: Box --- -The `@variablelab/env` entrypoint is the only one you always import — it contains the runtime-agnostic core. Adapter entrypoints (`/node`, `/vite`, `/react`, `/zod`) build on top of it. +The `@variablelab/env` entrypoint is the only one you always import — it contains the runtime-agnostic core. The other entrypoints are opt-in, and the [API reference overview](/docs/api-reference) lists them. ## Exports | Export | Kind | Summary | | ------------ | -------- | ------------------------------------------------------------------------------------ | | `schema` | Function | Declare the contract — branches and Standard Schema leaves. | -| `defineEnv` | Function | Merge config + environment variables, validate, and return a typed `env` object. | +| `defineEnv` | Function | Merge config + environment variables, validate, and return a typed `env` object: `Promise>` when `config` is a Promise, otherwise `Env`. | | `envName` | Function | Detect the current environment name across runtimes — `development`, `production`, … | | `readEnv` | Function | Read raw values from the active source (`process.env` on the server, `window.__env` in the browser). Returns `{}` on runtimes without `process` (Workers) — pass `runtimeEnv` to `defineEnv` instead. | -| `Config` | Type | The input shape of per-environment config files for a given schema. | +| `Config` | Type | The shape of per-environment config files for a given schema: the output type of every leaf (post-coercion), with every key optional. | | `Env` | Type | The output shape of the resolved `env` object for a given schema. | diff --git a/docsite/content/docs/api-reference/index.mdx b/docsite/content/docs/api-reference/index.mdx index dbd76f1..d6ef6e8 100644 --- a/docsite/content/docs/api-reference/index.mdx +++ b/docsite/content/docs/api-reference/index.mdx @@ -1,10 +1,10 @@ --- title: Overview -description: Every export of @variablelab/env, organized by the entrypoint you import it from. +description: The exports of @variablelab/env, organized by the entrypoint you import them from. icon: Map --- -The package is split into five entrypoints. Each page here documents one of them — signature, options, return type, and a minimal usage snippet. For "when to reach for this" prose, follow the link back to the guide that uses it. +The package is split into entrypoints. Each page here lists the exports of one of them. For "when to reach for this" prose, follow the link back to the guide that uses it. ## Entrypoints diff --git a/docsite/content/docs/api-reference/react.mdx b/docsite/content/docs/api-reference/react.mdx index eaf2487..a4ff445 100644 --- a/docsite/content/docs/api-reference/react.mdx +++ b/docsite/content/docs/api-reference/react.mdx @@ -4,13 +4,13 @@ description: Exports from the @variablelab/env/react entrypoint — the React in icon: Atom --- -The `@variablelab/env/react` entrypoint is the React integration for server-rendered apps — SSR (request-time) or SSG (build-time). Render `` in your document and the resolved env shows up at `window.__env` before client code runs. +The `@variablelab/env/react` entrypoint is the React integration for server-rendered apps — SSR (request-time) or SSG (build-time). Render `` in your document. It writes the object into a `