From 47e0bdd04b51be5e4754bc4146e039b2b1f40452 Mon Sep 17 00:00:00 2001 From: "Ricardo Q. Bazan" Date: Sun, 18 Oct 2026 07:34:38 -0500 Subject: [PATCH 1/7] docs: rename the repository docs to lowercase docs/CONTRIBUTING.md becomes docs/contributing.md and docs/DEVELOPMENT.md becomes docs/development.md, with every link to them updated. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XRCpaD5QTPJsEjnhikoV2S --- CLAUDE.md | 2 +- README.md | 4 ++-- docs/{CONTRIBUTING.md => contributing.md} | 4 ++-- docs/{DEVELOPMENT.md => development.md} | 6 +++--- docsite/README.md | 2 +- docsite/content/docs/community.mdx | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) rename docs/{CONTRIBUTING.md => contributing.md} (97%) rename docs/{DEVELOPMENT.md => development.md} (98%) diff --git a/CLAUDE.md b/CLAUDE.md index 605e463..1262ca9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,7 @@ 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`) +- [`docs/`](./docs) — repository docs (`contributing.md`, `development.md`) ## Release diff --git a/README.md b/README.md index eb31338..f5c2a4b 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,11 @@ mise run check # JS & TS check across package + docsite Run `mise tasks` for the full list. -For everything else — the per-task reference, the per-example layout, how to iterate on the library while seeing changes in the examples, troubleshooting — see [DEVELOPMENT.md](./docs/DEVELOPMENT.md). +For everything else — the per-task reference, the per-example layout, how to iterate on the library while seeing changes in the examples, troubleshooting — see [development.md](./docs/development.md). ## Contributing -Issues, ideas, and PRs welcome. Read [CONTRIBUTING.md](./docs/CONTRIBUTING.md) before opening a PR — it covers the branch / commit / changeset conventions and the release flow. +Issues, ideas, and PRs welcome. Read [contributing.md](./docs/contributing.md) before opening a PR — it covers the branch / commit / changeset conventions and the release flow. ## License diff --git a/docs/CONTRIBUTING.md b/docs/contributing.md similarity index 97% rename from docs/CONTRIBUTING.md rename to docs/contributing.md index 2eedf8f..b9dd183 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/contributing.md @@ -1,6 +1,6 @@ # Contributing to `@variablelab/env` -Thanks for taking the time to contribute. This document covers the conventions we follow so your PR can land smoothly. For local setup, the per-task reference, and how the monorepo is wired, see [DEVELOPMENT.md](./DEVELOPMENT.md). +Thanks for taking the time to contribute. This document covers the conventions we follow so your PR can land smoothly. For local setup, the per-task reference, and how the monorepo is wired, see [development.md](./development.md). ## Ways to contribute @@ -105,7 +105,7 @@ Examples are runtime-isolated demos. Each one is a real consumer of the publishe 8. Run the install + e2e 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. -The detailed setup mechanics — which package manager fits which runtime, why Deno needs `nodeModulesDir: "manual"`, the depends-on-`//:env:pack` rule — are documented in [DEVELOPMENT.md](./DEVELOPMENT.md#working-on-the-examples-examples). +The detailed setup mechanics — which package manager fits which runtime, why Deno needs `nodeModulesDir: "manual"`, the depends-on-`//:env:pack` rule — are documented in [development.md](./development.md#working-on-the-examples-examples). ## Preview releases diff --git a/docs/DEVELOPMENT.md b/docs/development.md similarity index 98% rename from docs/DEVELOPMENT.md rename to docs/development.md index 15e0efd..3451a6f 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/development.md @@ -1,6 +1,6 @@ # Development -How to set up, work in, and ship from this monorepo. For contribution conventions (branches, commits, changesets, PR flow), see [CONTRIBUTING.md](./CONTRIBUTING.md). +How to set up, work in, and ship from this monorepo. For contribution conventions (branches, commits, changesets, PR flow), see [contributing.md](./contributing.md). ## What's in here @@ -9,7 +9,7 @@ env/ ├── package/ # @variablelab/env library — published to npm ├── docsite/ # Fumadocs site → lab.variable.land/env/docs (Cloudflare Workers) ├── examples/ # 12 runtime-isolated demos (Node, Bun, Deno, Workers, Edge, Vite, SSR) -├── docs/ # Repository docs (CONTRIBUTING.md, DEVELOPMENT.md) +├── docs/ # Repository docs (contributing.md, development.md) ├── mise.toml # Tool versions + task orchestration ├── mise.lock # Locked tool versions + checksums (each example has its own) ├── pnpm-workspace.yaml # Workspace for `package` + `docsite` only @@ -156,7 +156,7 @@ The `backend-*`, `worker-*`, and `edge-nextjs` examples drive Playwright in HTTP ### Adding a new example -The full step-by-step is in [CONTRIBUTING.md](./CONTRIBUTING.md#adding-a-new-example). The shape is: +The full step-by-step is in [contributing.md](./contributing.md#adding-a-new-example). The shape is: 1. New dir under `examples//`. 2. `package.json` with `"@variablelab/env": "file:../../package/.local/variablelab-env.tgz"` and the runtime's flavor of devDeps (e.g. `@types/bun` for Bun, `@types/deno` for Deno). diff --git a/docsite/README.md b/docsite/README.md index 183a67f..1e874f5 100644 --- a/docsite/README.md +++ b/docsite/README.md @@ -19,7 +19,7 @@ Built with [TanStack Start](https://tanstack.com/start) + [Fumadocs](https://fum ## Running -From the repo root (after `mise install && mise run setup` — see [DEVELOPMENT.md](../docs/DEVELOPMENT.md)): +From the repo root (after `mise install && mise run setup` — see [development.md](../docs/development.md)): ```sh mise run docsite # alias for `pnpm --filter=docsite dev` diff --git a/docsite/content/docs/community.mdx b/docsite/content/docs/community.mdx index 361fca0..538c363 100644 --- a/docsite/content/docs/community.mdx +++ b/docsite/content/docs/community.mdx @@ -12,7 +12,7 @@ The code lives at [github.com/withvariablelab/env](https://github.com/withvariab - **Found a bug?** [Open an issue](https://github.com/withvariablelab/env/issues/new) with a minimal reproduction — that's the fastest path to a fix. - **Have an idea?** Start a [discussion](https://github.com/withvariablelab/env/discussions) so we can shape it together before code is written. -- **Want to send a PR?** Read [`CONTRIBUTING.md`](https://github.com/withvariablelab/env/blob/main/docs/CONTRIBUTING.md) in the repo, branch from `main`, and feel free to ping us in the PR if anything is unclear. +- **Want to send a PR?** Read [`contributing.md`](https://github.com/withvariablelab/env/blob/main/docs/contributing.md) in the repo, branch from `main`, and feel free to ping us in the PR if anything is unclear. - **Improving these docs?** Every page on this site has an "Edit on GitHub" link in the actions row — open a PR with the change you'd like to see. Doc fixes are some of the most appreciated contributions. ## About Variable Land From 8809acf6e588e7ba4eb0a81b1001f5a0f3a285b3 Mon Sep 17 00:00:00 2001 From: "Ricardo Q. Bazan" Date: Sun, 18 Oct 2026 07:35:39 -0500 Subject: [PATCH 2/7] chore: name the license holder and ship LICENSE in the package The holder line becomes "rcrd ", and package/ gets an identical copy of the root LICENSE, since npm only packs the package folder's. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XRCpaD5QTPJsEjnhikoV2S --- LICENSE | 2 +- package/LICENSE | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 package/LICENSE diff --git a/LICENSE b/LICENSE index 7e7c82c..f65a6b1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 rcrd +Copyright (c) 2026 rcrd Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/package/LICENSE b/package/LICENSE new file mode 100644 index 0000000..f65a6b1 --- /dev/null +++ b/package/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 rcrd + +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. From 926c1901d0f02dc8077fb2d110ff90e7b55d6d64 Mon Sep 17 00:00:00 2001 From: "Ricardo Q. Bazan" Date: Sun, 18 Oct 2026 07:35:40 -0500 Subject: [PATCH 3/7] docs: give the root and package READMEs the Variable Lab structure The root README links the package README and the docs, lists the folders, and ends with Contributing, License and the Signature. The package README gains Usage, an Entry points table and absolute links, and package.json names its directory so npm resolves the repository. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XRCpaD5QTPJsEjnhikoV2S --- README.md | 55 ++++++++++--------------------------- package/README.md | 64 +++++++++++++++++++++++++++----------------- package/package.json | 3 ++- 3 files changed, 56 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index f5c2a4b..cdfdc79 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,33 @@

- +

-

env

+

env

Only declared variables, checked at boot.

-[![npm version](https://img.shields.io/npm/v/@variablelab/env?label=%40variablelab%2Fenv&color=blue)](https://www.npmjs.com/package/@variablelab/env) -[![CI](https://github.com/withvariablelab/env/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/withvariablelab/env/actions/workflows/ci.yml) -[![changesets](https://img.shields.io/badge/maintained%20with-changesets-176de3.svg)](https://github.com/changesets/changesets) +This repository holds env: the `@variablelab/env` library, its docs site and its examples. -Contract-first environment configuration with typed schemas — runtime-agnostic core (Node, Bun, Deno, browser, Workers, Edge) with opt-in adapters for Node, Vite, and React (SSR). - -```bash +```sh pnpm add @variablelab/env ``` -**[lab.variable.land/env/docs](https://lab.variable.land/env/docs)** — full docs +Usage and the entry points are in the library's [README](package/README.md), and everything else is in the [docs](https://lab.variable.land/env/docs/getting-started/installation). ## Repository -This is the monorepo. Three things live here: - -| Path | What it is | -| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -| [`package/`](./package) | The [`@variablelab/env`](./package) library — published to npm | -| [`docsite/`](./docsite) | The Fumadocs site behind [lab.variable.land/env/docs](https://lab.variable.land/env/docs), on Cloudflare Workers | -| [`examples/`](./examples) | 9 runtime-isolated demos (Node, Bun, Deno, Workers, Edge, Vite SPA, SSR) — each one is a real consumer of the published tarball | - -## Working on it - -The whole thing is orchestrated with [mise](https://mise.jdx.dev). Get the runtimes, install, run the e2e suites: - -```bash -mise install # installs node + pnpm; bun/deno added on demand by examples -mise run setup # root deps + pack the library + install every example + Playwright browsers -mise run test:e2e # runs the e2e suite of every example -``` - -Day-to-day: - -```bash -mise run docsite # docs site dev server -mise run lib:test # unit tests for @variablelab/env -mise run lib:build # build @variablelab/env -mise run check # JS & TS check across package + docsite -``` - -Run `mise tasks` for the full list. - -For everything else — the per-task reference, the per-example layout, how to iterate on the library while seeing changes in the examples, troubleshooting — see [development.md](./docs/development.md). +- [`package/`](package): the `@variablelab/env` library, published to npm. +- [`docsite/`](docsite): the docs at [lab.variable.land/env/docs](https://lab.variable.land/env/docs), built with Fumadocs and served by Cloudflare Workers. +- [`examples/`](examples): runtime-isolated apps for Node, Bun, Deno, Workers, Edge, a Vite SPA and SSR. Each one installs the packed library, as an outside consumer would. ## Contributing -Issues, ideas, and PRs welcome. Read [contributing.md](./docs/contributing.md) before opening a PR — it covers the branch / commit / changeset conventions and the release flow. +The [contributing guide](docs/contributing.md) covers issues, pull requests and releases, and the [development guide](docs/development.md) covers setting up the repo and running its checks. ## License -[MIT](./LICENSE) © [Variable Land](https://variable.land) +The code is [MIT](LICENSE). The Sign in `package/brand/` is not: its use is set by [BRAND-USE.md](https://github.com/withvariablelab/brand/blob/main/BRAND-USE.md). + +

+  Variable Land +

diff --git a/package/README.md b/package/README.md index 9f820e4..f33cc19 100644 --- a/package/README.md +++ b/package/README.md @@ -1,50 +1,66 @@

- +

-

env

+

env

Only declared variables, checked at boot.

-Contract-first environment configuration with typed schemas. Define your env once with [Standard Schema](https://github.com/standard-schema/standard-schema) (Zod, Valibot, ArkType…), get a fully typed `env` object that merges per-environment config with your environment variables. +Contract-first environment configuration. You define the env once with a [Standard Schema](https://github.com/standard-schema/standard-schema) validator, such as Zod, Valibot or ArkType, and get a typed `env` object that merges per-environment config with your environment variables. -Runtime-agnostic core — Node, Bun, Deno, browser, Workers, Edge. Opt-in entry points: a filesystem-based config loader (works on Node, Bun, and Deno), a Vite plugin, a React `` helper for hydrating runtime env across SSR or CSR, and a curated set of Zod-based schema primitives. +The core runs on Node, Bun, Deno, the browser, Workers and Edge. Every other entry point is opt-in. -## Installation +## Install ```sh pnpm add @variablelab/env ``` -`@variablelab/env` is agnostic to the schema validator. If you'll use Zod (the docs default, and the validator behind the optional `@variablelab/env/zod` primitives), install it alongside: +No validator is bundled. To use Zod, the default in the docs and the validator behind `@variablelab/env/zod`, install it beside env: ```sh pnpm add zod ``` -## Documentation +## Usage + +```ts +// env.ts +import { defineEnv, schema } from "@variablelab/env"; +import * as e from "@variablelab/env/zod"; + +const Env = schema({ + server: { PORT: e.port, HOST: e.host }, +}); + +export const env = defineEnv({ schema: Env }); +``` -**[lab.variable.land/env/docs](https://lab.variable.land/env/docs)** +## Entry points -- [Getting started](https://lab.variable.land/env/docs/getting-started/installation) -- [Concepts](https://lab.variable.land/env/docs/concepts/overview) — mental model, resolution order, env-var naming -- [Guides](https://lab.variable.land/env/docs/guides) — recipes for Node, SPA, SSR -- [API reference](https://lab.variable.land/env/docs/api-reference) +| Entry point | What it adds | +| --- | --- | +| `@variablelab/env` | `defineEnv` and `schema`: the runtime-agnostic core. | +| `@variablelab/env/fs` | `loadConfig`, which reads config files on Node, Bun and Deno. | +| `@variablelab/env/vite` | `envConfig()`, a Vite plugin. | +| `@variablelab/env/react` | ``, which hydrates the runtime env across SSR and CSR. | +| `@variablelab/env/zod` | Zod schema primitives, such as `port`, `host` and `secret`. | -## Examples +## Documentation + +All of it is at [lab.variable.land/env/docs](https://lab.variable.land/env/docs): -Runnable apps under [`examples/`](https://github.com/withvariablelab/env/tree/main/examples): +- [Getting started](https://lab.variable.land/env/docs/getting-started/installation). +- [Concepts](https://lab.variable.land/env/docs/concepts/overview): the mental model, the resolution order and env-var naming. +- [Guides](https://lab.variable.land/env/docs/guides): recipes for Node, SPA and SSR. +- [API reference](https://lab.variable.land/env/docs/api-reference). -- [`backend-node`](https://github.com/withvariablelab/env/tree/main/examples/backend-node) — Node server with `loadConfig` -- [`backend-bun`](https://github.com/withvariablelab/env/tree/main/examples/backend-bun) — Bun server with `loadConfig` -- [`backend-deno`](https://github.com/withvariablelab/env/tree/main/examples/backend-deno) — Deno server with `loadConfig` -- [`worker-cloudflare`](https://github.com/withvariablelab/env/tree/main/examples/worker-cloudflare) — Cloudflare Worker, `runtimeEnv: c.env` per-request -- [`edge-nextjs`](https://github.com/withvariablelab/env/tree/main/examples/edge-nextjs) — Next.js App Router route handler with `runtime: 'edge'` -- [`spa-vite-dynamic`](https://github.com/withvariablelab/env/tree/main/examples/spa-vite-dynamic) — SPA, dynamic import pattern -- [`spa-vite-plugin`](https://github.com/withvariablelab/env/tree/main/examples/spa-vite-plugin) — SPA, `#config` alias -- [`ssr-react-router`](https://github.com/withvariablelab/env/tree/main/examples/ssr-react-router) — SSR with React Router 7 -- [`ssr-tanstack-start`](https://github.com/withvariablelab/env/tree/main/examples/ssr-tanstack-start) — SSR with TanStack Start +Runnable apps for each runtime are in [`examples/`](https://github.com/withvariablelab/env/tree/main/examples). ## License -MIT +The code is [MIT](https://github.com/withvariablelab/env/blob/main/LICENSE). The Sign in `brand/` is not: its use is set by [BRAND-USE.md](https://github.com/withvariablelab/brand/blob/main/BRAND-USE.md). + +

+  Variable Land +

diff --git a/package/package.json b/package/package.json index d42ec0f..dc47df6 100644 --- a/package/package.json +++ b/package/package.json @@ -8,7 +8,8 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/withvariablelab/env.git" + "url": "git+https://github.com/withvariablelab/env.git", + "directory": "package" }, "license": "MIT", "author": "rcrd ", From 67834fa9401260529180c69f049354620159ac42 Mon Sep 17 00:00:00 2001 From: "Ricardo Q. Bazan" Date: Sun, 18 Oct 2026 07:39:12 -0500 Subject: [PATCH 4/7] docs: give the contributing and development guides their sections contributing.md takes Issues, Pull requests, Changesets and Releases, with preview releases under Pull requests. development.md takes Layout, Setup, Tasks and CI, with the git hooks under CI. Claims now match mise.toml, lefthook.yml and the workflows, and the examples are no longer counted. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XRCpaD5QTPJsEjnhikoV2S --- docs/contributing.md | 139 ++++++++++------------ docs/development.md | 277 +++++++++++++++++++++++-------------------- 2 files changed, 209 insertions(+), 207 deletions(-) diff --git a/docs/contributing.md b/docs/contributing.md index b9dd183..96cda83 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,39 +1,31 @@ -# Contributing to `@variablelab/env` +# Contributing -Thanks for taking the time to contribute. This document covers the conventions we follow so your PR can land smoothly. For local setup, the per-task reference, and how the monorepo is wired, see [development.md](./development.md). +env takes bug reports, fixes, features, docs and examples. The docs live in [`docsite/content/docs/`](../docsite/content/docs), and a correction as short as one paragraph is welcome. A new example covers a runtime or framework the others don't: see [Adding a new example](#adding-a-new-example). To set up the repo and run its checks, see the [development guide](development.md). -## Ways to contribute +## Issues -- **Report a bug.** Open an issue with a minimal repro and the actual vs. expected behavior. Include runtime (Node / Bun / Deno / Workers / Edge), `@variablelab/env` version, and the schema validator you use. -- **Propose a feature.** Open an issue first so we can align on scope before you spend time on a PR. We bias toward small, composable APIs. -- **Improve the docs.** Even one-paragraph corrections are welcome. Docs live in [`docsite/content/docs/`](../docsite/content/docs). -- **Add an example.** Demos that exercise a runtime or framework we don't cover yet are great PRs. See [adding a new example](#adding-a-new-example) below. -- **Send a fix or feature PR.** Read the rest of this document first. +- **Report a bug.** Open an issue with a minimal repro and the actual and expected behavior. Include the runtime (Node, Bun, Deno, Workers or Edge), the `@variablelab/env` version and the schema validator you use. +- **Propose a feature.** Open an issue first, so the scope is agreed before you spend time on a pull request. env favors small, composable APIs. -## Before you open a PR +A typo fix, dead-code removal or a single-file bug fix can go straight to a pull request. Anything that adds API, changes behavior or touches public types starts as an issue. -1. **An issue exists for non-trivial changes.** For typo fixes, dead-code removal, or a single-file bug fix, just open the PR. For anything that adds API, changes behavior, or touches public types, open an issue first. -2. **Your branch is up to date with `main`.** -3. **`mise run test:e2e` passes locally** (or at least the suites your change touches). -4. **You added a changeset** if the change affects the published `@variablelab/env`. See [Changesets](#changesets). - -## Branch and commit conventions +## Pull requests ### Branch names -Use a short prefix that mirrors the change type: +Branch from `main` with a short prefix that mirrors the change type: -- `feat/` — new feature or new public API -- `fix/` — bug fix -- `docs/` — docs-only changes -- `chore/` — tooling, CI, dependencies, refactors with no behavior change -- `test/` — test-only changes +- `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. +- `test/`: a test-only change. -The CI release workflow uses the `feat/*` and `fix/*` prefixes to publish preview releases under the `pr-` dist-tag (see [Preview releases](#preview-releases)). +The `feat/` and `fix/` prefixes also publish a preview release (see [Preview releases](#preview-releases)). ### Commit messages -We follow [Conventional Commits](https://www.conventionalcommits.org/), kept terse: +Commits follow [Conventional Commits](https://www.conventionalcommits.org/), kept terse: ``` feat(zod): add zEnum primitive for case-insensitive enums @@ -42,97 +34,86 @@ docs: clarify resolution order for runtimeEnv chore(ci): bump jdx/mise-action to v2 ``` -The PR title follows the same format — that title becomes the squash-merge commit. +The pull request title takes the same form, because it becomes the squash-merge commit. -## Changesets +### Checklist + +- [ ] The branch is named `feat/*`, `fix/*`, `docs/*`, `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. +- [ ] `mise run test:e2e` passes locally, at least for the examples the change touches. +- [ ] A changeset is added, if the published `@variablelab/env` changed. See [Changesets](#changesets). +- [ ] The docs are updated, if public API or behavior changed. -We version and release with [Changesets](https://github.com/changesets/changesets). Every PR that affects what gets shipped to npm needs a changeset. +### Merging -Add one as part of your PR: +Every check on the pull request has to pass. The [development guide](development.md#ci) lists the workflows that run. A maintainer squash-merges. + +### Preview releases + +A pull request from a `feat/*` or `fix/*` branch, or with the `preview` label, publishes a preview build of `@variablelab/env` to npm under the `pr-` dist-tag, so reviewers can install the unmerged version: ```sh -pnpm changeset +pnpm install @variablelab/env@pr-123 ``` -That command walks you through: +Every push to the pull request publishes the preview again. -1. Which packages changed (almost always just `@variablelab/env`). -2. The bump type — `patch` (bug fix), `minor` (new feature, backwards-compatible), `major` (breaking change). -3. A user-facing description (it lands in the changelog). +## Changesets -It writes a markdown file under `.changeset/` — commit it with the rest of your changes. +Versions and releases go through [Changesets](https://github.com/changesets/changesets). Every pull request that changes what ships to npm needs a changeset. Add one as part of the pull request: -**You don't need a changeset when:** +```sh +pnpm changeset +``` -- Only `docsite/`, `examples/`, CI files, or other non-published files changed. -- The PR is a pure `chore/*` that doesn't touch `package/src/`. +The command asks for: -Don't bump versions or edit `CHANGELOG.md` by hand — Changesets does both during release. +1. The packages that changed, almost always just `@variablelab/env`. +2. The bump: `patch` for a bug fix, `minor` for a backwards-compatible feature, `major` for a breaking change. +3. A description for the people who use env, which lands in the changelog. -## Pull request checklist +It writes a markdown file under `.changeset/`. Commit it with the rest of the change. -- [ ] Branch named `feat/*`, `fix/*`, `docs/*`, `chore/*`, or `test/*` -- [ ] PR title follows Conventional Commits -- [ ] Tests cover the change (unit in `package/src/__tests__/`, e2e in `examples//test/e2e/` if relevant) -- [ ] `mise run lib:test` and `mise run check` pass locally (JS & TS check) -- [ ] `mise run test:e2e` passes locally for affected examples -- [ ] Changeset added (if the published `@variablelab/env` changed) -- [ ] Docs updated (if public API or behavior changed) +A pull request needs no changeset when: -## CI +- only `docsite/`, `examples/`, CI files or other unpublished files changed; +- it is a `chore/*` change that doesn't touch `package/src/`. -Two workflows run on every PR: +Nobody bumps versions or edits `CHANGELOG.md` by hand: Changesets does both during a release. -- **CI** ([`.github/workflows/ci.yml`](../.github/workflows/ci.yml)) — static checks, unit tests, and library build inside the small pnpm workspace (`package` + `docsite`). -- **E2E** ([`.github/workflows/e2e.yml`](../.github/workflows/e2e.yml)) — a matrix job per example, each provisioning its own runtime via [`jdx/mise-action`](https://github.com/jdx/mise-action). +## Releases -Both must be green to merge. +When pull requests with changesets land on `main`, the release job in [`ci.yml`](../.github/workflows/ci.yml) (`changesets/action`, running as `vland-bot`) opens or updates a "chore: update versions" pull request. A maintainer merges it, and that runs the release job again, which publishes the new version to npm and tags the release on GitHub. ## Adding a new example -Examples are runtime-isolated demos. Each one is a real consumer of the published `@variablelab/env` tarball — no shared lockfile, no workspace cheating. To add one: +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. 2. Add a `package.json` that consumes `@variablelab/env` from the local tarball: ```jsonc "@variablelab/env": "file:../../package/.local/variablelab-env.tgz" ``` -3. Add a `mise.toml` with the runtime tools (`node` / `bun` / `deno` / `pnpm`) and the standard tasks (`setup`, `start`, `test:e2e`, `check`). Use any of the existing examples as a template — pick the one with the closest runtime. -4. Add a `biome.json` extending `@rrlab/biome-config` (skip this for Deno-only examples). +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. +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 + e2e and commit the generated lockfile. +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. -The detailed setup mechanics — which package manager fits which runtime, why Deno needs `nodeModulesDir: "manual"`, the depends-on-`//:env:pack` rule — are documented in [development.md](./development.md#working-on-the-examples-examples). +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). -## Preview releases +## Code review -PRs from branches named `feat/*` or `fix/*` publish a preview build of `@variablelab/env` to npm under the `pr-` dist-tag, so reviewers can install the unmerged version: - -```sh -pnpm install @variablelab/env@pr-123 -``` - -The preview is republished on every push to the PR. - -## Release flow - -When PRs with changesets land on `main`, the [release job](../.github/workflows/ci.yml) (`changesets/action`, running as `vland-bot`) opens or updates a "chore: update versions" PR. Merging that PR runs the release job again, which publishes the new versions to npm and tags the release on GitHub. - -Maintainers don't need to manually bump versions or edit `CHANGELOG.md` — both are generated. - -## Code review expectations - -- We aim to leave a first review within a few business days. If you don't hear back in a week, ping the PR. -- Reviews focus on: correctness, type safety at API boundaries, test coverage of failure modes, runtime portability (does this break Deno / Workers?), and docs alignment. -- We're conservative about new public surface area. Smaller, composable additions are easier to land than big new namespaces. +- A first review usually comes within a few business days. If none comes within a week, ping the pull request. +- Reviews look at correctness, type safety at API boundaries, test coverage of failure modes, runtime portability (whether the change breaks Deno or Workers) and docs alignment. +- New public API is added with care. Small, composable additions land more easily than new namespaces. ## Code of conduct -Be kind and assume good faith. We don't have a formal CoC document yet — if your interaction wouldn't fly in a respectful workplace, don't post it here. - ---- +Be kind and assume good faith. There is no formal code of conduct yet: if something wouldn't be said in a respectful workplace, don't post it here. -Questions? Open a [discussion](https://github.com/withvariablelab/env/discussions) or ping us in the PR. +For a question, open an [issue](https://github.com/withvariablelab/env/issues) or ask in your pull request. diff --git a/docs/development.md b/docs/development.md index 3451a6f..8d76dc5 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,43 +1,45 @@ # Development -How to set up, work in, and ship from this monorepo. For contribution conventions (branches, commits, changesets, PR flow), see [contributing.md](./contributing.md). +How to set up, work in and ship from the env monorepo. The [contributing guide](contributing.md) covers how a change gets in: branches, commits, changesets and pull requests. -## What's in here +## Layout ``` env/ -├── package/ # @variablelab/env library — published to npm +├── package/ # @variablelab/env library, published to npm ├── docsite/ # Fumadocs site → lab.variable.land/env/docs (Cloudflare Workers) -├── examples/ # 12 runtime-isolated demos (Node, Bun, Deno, Workers, Edge, Vite, SSR) +├── examples/ # Runtime-isolated demos (Node, Bun, Deno, Workers, Edge, Vite, SSR) ├── docs/ # Repository docs (contributing.md, development.md) ├── mise.toml # Tool versions + task orchestration ├── mise.lock # Locked tool versions + checksums (each example has its own) ├── pnpm-workspace.yaml # Workspace for `package` + `docsite` only -├── lefthook.yml # Git hooks (jscheck on commit, tests on push) +├── lefthook.yml # Git hooks (Biome and the type check on commit, tests on push) └── biome.json # Root biome config (excludes examples/**) ``` There are two layers, by design: -- **The pnpm workspace** (`package` + `docsite`) — these two are siblings and share the catalog'd dependency versions. -- **The runtime-isolated examples** (`examples/*`) — each one declares its own runtime, brings its own package manager (pnpm / bun / deno), has its own lockfile, and consumes `@variablelab/env` from a packed tarball. They are **not** part of the pnpm workspace; they're orchestrated by mise. +- **The pnpm workspace** (`package` + `docsite`): the two are siblings and share the dependency versions in the catalog. +- **The runtime-isolated examples** (`examples/*`): each one declares its own runtime, brings its own package manager (pnpm, bun or deno), has its own lockfile, and consumes `@variablelab/env` from a packed tarball. They are not part of the pnpm workspace: mise orchestrates them. -This split is intentional: the library promises runtime-agnosticism, and the examples have to prove that. An example that secretly resolves through a shared pnpm workspace doesn't actually demonstrate it works in Bun or Deno. +The split is intentional: the library promises to be runtime-agnostic, and the examples have to prove it. An example that resolves through a shared pnpm workspace doesn't show that the library works in Bun or Deno. -## Prerequisites +## Setup -You need [**mise**](https://mise.jdx.dev) (≥ 2025.5.0). It bootstraps everything else — Node, pnpm, bun, deno are all installed and pinned by mise. +### mise + +You need [mise](https://mise.jdx.dev) 2026.6.6 or later, the `min_version` in [`mise.toml`](../mise.toml). It installs and pins everything else: Node and pnpm at the root, and bun or deno in the examples that use them. ```sh curl https://mise.run | sh # or: brew install mise / pacman -S mise / etc. ``` -> **First time using mise?** It activates by adding a hook to your shell rc. Follow the [mise install docs](https://mise.jdx.dev/getting-started.html) — once activated, every project tool magically resolves to the version pinned in the relevant `mise.toml`. +mise activates through a hook in your shell rc: follow the [mise install docs](https://mise.jdx.dev/getting-started.html). Once it is active, each tool resolves to the version pinned in the relevant `mise.toml`. -You'll also be prompted by mise to "trust" the per-example `mise.toml` files the first time you `cd` into them. Run `mise trust` when it asks. +The first time you `cd` into an example, mise asks you to trust its `mise.toml`. Run `mise trust` when it asks. -## Setup from scratch +### From scratch ```sh git clone git@github.com:withvariablelab/env.git @@ -46,15 +48,94 @@ mise install # node + pnpm mise run setup # ↓ runs the bootstrap chain ``` -`mise run setup` runs the following steps in order: +`mise run setup` runs these steps in order: + +1. `mise install`: installs the root tools. +2. `for d in examples/*/; do mise install -C "$d"; done`: installs each example's runtime (bun, deno and so on). `mise install` doesn't accept monorepo globs, so the loop stays. +3. `pnpm install`: the workspace dependencies for `package` + `docsite`. +4. `mise run "//examples/...:setup"`: runs each example's `setup` task in parallel, resolved through `[monorepo].config_roots`. Each `setup` declares `depends = ["//:env:pack"]`, so mise runs `env:pack` once before the examples install. +5. `mise run playwright:install`: fetches Chromium for the SPA and SSR e2e suites. + +At the end, every example has its own `node_modules/`, its own lockfile, and a fresh copy of `@variablelab/env` extracted from the tarball. + +## Tasks + +The everyday commands: + +```sh +mise run docsite # docs site dev server +mise run lib:test # unit tests for @variablelab/env +mise run lib:build # build @variablelab/env +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. + +### Root tasks (in [`mise.toml`](../mise.toml)) + +| Task | What it does | +| --------------------------- | ----------------------------------------------------------------------------- | +| `setup` | Bootstrap everything (tools, root deps, tarball, examples, Playwright). | +| `env:pack` | Build `@variablelab/env` and pack it into `package/.local/variablelab-env.tgz`. | +| `env:watch` | Watch `package/src/`, repack on change, reinstall in every example. | +| `playwright:install` | Install Chromium for Playwright (one-time, idempotent). | +| `docsite` | Start the docs site dev server. | +| `lib:test` | Unit tests for `@variablelab/env`. | +| `lib:build` | Build `@variablelab/env`. | +| `check` | JS & TS check across the workspace (`package` + `docsite`). | +| `test:e2e` | Run e2e for every example. | +| `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`) + +| 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). | +| `test:e2e` | Playwright. | +| `check` | JS & TS check. | + +### Why mise is the only API + +The root `package.json` carries no scripts. Every operation that crosses the repo, such as a workspace check, packing the library, installing every example or running the e2e matrix, is a `mise run `. Two reasons: + +1. **One way to do each thing.** With `pnpm setup` and `mise run setup` doing the same thing, people had to remember which to type, and the docs had to mention both. +2. **CI and people share one contract.** What you type locally is what CI runs (`mise run lib:test`, `mise run test:e2e` and so on). There is no drift between `package.json` scripts and a separate list of CI commands. + +The `package/` and `docsite/` workspaces still have plain scripts in their own `package.json`, such as `pnpm test` or `pnpm dev`, and the mise tasks run the same commands: `docsite` runs `pnpm --filter=docsite dev`, and `lib:test` runs `vitest run`, as `pnpm test` does in `package/`. Those scripts are internal to each subproject: you only use them after you `cd` into it. That is the right API at that scope. -1. `mise install` — installs the root tools. -2. `for d in examples/*/; do mise install -C "$d"; done` — installs each example's runtime (bun, deno, etc.). `mise install` doesn't accept monorepo globs, so the loop stays. -3. `pnpm install` — workspace deps for `package` + `docsite`. -4. `mise run "//examples/...:setup"` — runs each example's `setup` task in parallel (resolved via `[monorepo].config_roots`). Each `setup` declares `depends = ["//:env:pack"]`, so mise dedupes `env:pack` to a single execution before the example installs. -5. `mise run playwright:install` — fetches Chromium for the SPA / SSR e2e suites. +The same goes for each example's `mise.toml`: inside `examples/`, the API is `mise run `, with no `pnpm` scripts that repeat it. -End state: every example has its own `node_modules/`, its own lockfile, and a freshly installed copy of `@variablelab/env` extracted from the tarball. +## CI + +### Workflows + +On every pull request to `main`: + +- **CI** ([`ci.yml`](../.github/workflows/ci.yml)): the library and docs pipeline, inside the pnpm workspace (`package` + `docsite`). It runs `mise run check`, `mise run lib:test` and `mise run lib:build`. On `main`, it also runs the Changesets release job, and for a `feat/*` or `fix/*` branch, or a pull request with the `preview` label, it publishes a [preview release](contributing.md#preview-releases). +- **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`. +- **Workflow audit** ([`workflow-audit.yml`](../.github/workflows/workflow-audit.yml)): zizmor on the workflows in `.github/`. + +CI and E2E 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. + +### Git hooks + +[`lefthook.yml`](../lefthook.yml) runs: + +- **pre-commit**, in parallel: + - `pnpm rr jscheck --fix-staged`: Biome on the staged files. + - `pnpm rr tsc`: the type check, only when staged files match `{package,docsite}/**/*.{ts,tsx}` or `{package,docsite}/tsconfig.json`. +- **pre-push**: `pnpm --filter=@variablelab/env test`. + +The hooks skip the examples on purpose: they take long enough that only CI runs them. ## Working on the library (`package/`) @@ -66,25 +147,25 @@ mise run lib:build # emits dist/ mise run check # JS & TS check across package + docsite ``` -If you're hacking inside `package/`, the library's own `package.json` exposes the underlying scripts — those are what `mise run lib:*` and `mise run check` ultimately call. +Inside `package/`, the library's own `package.json` has the same commands as scripts: `pnpm test` runs `vitest run`, as `mise run lib:test` does, and `prepublishOnly` runs `rr pack`, as `mise run lib:build` does. ### Iterating with the examples -When you change `package/src/`, the examples need to see the new code. The flow: +When you change `package/src/`, the examples need to see the new code: ```sh mise run env:pack # rebuild + repack the tarball mise run examples:bump # reinstall the tarball in every example ``` -Each example's `setup` task declares `depends = ["//:env:pack"]` (with `sources` / `outputs` declared on `env:pack` itself), so any of these will pull in the latest tarball automatically: +Each example's `setup` task declares `depends = ["//:env:pack"]`, with `sources` and `outputs` declared on `env:pack` itself, so any of these pulls in the latest tarball: ```sh mise run //examples/backend-node:test:e2e mise run //examples/backend-bun:start ``` -For an active edit loop (rebuild on every save, push to all examples): +For an edit loop that rebuilds on every save and pushes to all examples: ```sh mise run env:watch @@ -92,7 +173,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`. +`env:watch` runs chokidar on `package/src/`, which calls `env:pack` and then `examples:bump` on every change. ## Working on the docs (`docsite/`) @@ -102,13 +183,13 @@ The docs are a [TanStack Start](https://tanstack.com/start) + [Fumadocs](https:/ mise run docsite # local dev server ``` -Anything beyond dev (production build, deploy, preview) lives as `pnpm` scripts inside `docsite/package.json` — `cd docsite && pnpm build` / `pnpm deploy` / `pnpm preview`. Those are docs-specific deployment workflows; we haven't promoted them to mise tasks because they don't cross repo boundaries. +Anything beyond dev, such as the production build, the deploy or the preview, is a `pnpm` script inside `docsite/package.json`: `cd docsite && pnpm build`, `pnpm run deploy` or `pnpm preview`. They are mise tasks only if they cross the repo, and these don't. -Content lives in `docsite/content/docs/` as MDX. Read [`docsite/README.md`](../docsite/README.md) for the full layout (routes, llms.txt endpoints, the `*.mdx` content negotiation trick, etc.). +Content lives in `docsite/content/docs/` as MDX. [`docsite/README.md`](../docsite/README.md) has the full layout: routes, the `llms.txt` endpoints, the `*.mdx` content negotiation and more. ## Working on the examples (`examples/*`) -Every example has the same task vocabulary, so you can `cd` into any of them and run: +Every example has the same task names, so you can `cd` into any of them and run: ```sh mise run setup # pack env + install deps (uses the example's native PM) @@ -118,14 +199,14 @@ mise run test:e2e # Playwright mise run check # JS & TS check ``` -Or invoke from the repo root with the `//path:task` syntax: +Or run them from the repo root with the `//path:task` syntax: ```sh mise run //examples/spa-vite-plugin:dev mise run //examples/worker-cloudflare:test:e2e ``` -### How `@variablelab/env` is consumed +### How the examples consume the library Every example declares the dependency as a local file tarball: @@ -133,100 +214,40 @@ Every example declares the dependency as a local file tarball: "@variablelab/env": "file:../../package/.local/variablelab-env.tgz" ``` -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. - -### The 12 examples - -| Example | Runtime | PM | Why | -| -------------------------------------------------------- | ---------------------------------------- | ---- | ------------------------------------------------------------------ | -| [`backend-node`](../examples/backend-node) | Node 24 | pnpm | Plain Node server | -| [`backend-bun`](../examples/backend-bun) | Bun 1.2.4 | bun | Native Bun consumer | -| [`backend-deno`](../examples/backend-deno) | Deno 2 (server) + Node 24 (test runner) | bun | Deno can't extract `file:` tarballs natively — we use `bun install` to hydrate `node_modules/` (flat layout) and let Deno read it via `nodeModulesDir: "manual"` | -| [`backend-node-cjs`](../examples/backend-node-cjs) | Node 24 | pnpm | `loadConfig` (sync) — server booted from a CommonJS `require()` entry | -| [`backend-bun-cjs`](../examples/backend-bun-cjs) | Bun 1.2.4 | bun | `loadConfig` (sync) under a Bun CommonJS `require()` entry | -| [`backend-deno-cjs`](../examples/backend-deno-cjs) | Deno 2 (server) + Node 24 (test runner) | bun | `loadConfig` (sync) under a Deno CommonJS `require()` entry | -| [`worker-cloudflare`](../examples/worker-cloudflare) | Cloudflare Workers | pnpm | Validates `runtimeEnv: c.env` per-request; no `nodejs_compat` flag | -| [`edge-nextjs`](../examples/edge-nextjs) | Next.js Edge | pnpm | Validates the Edge runtime path | -| [`spa-vite-plugin`](../examples/spa-vite-plugin) | Vite + React | pnpm | `envConfig()` plugin + `#config` alias | -| [`spa-vite-dynamic`](../examples/spa-vite-dynamic) | Vite + React | pnpm | Dynamic `import()` pattern | -| [`ssr-react-router`](../examples/ssr-react-router) | React Router 7 | pnpm | `` SSR / hydration | -| [`ssr-tanstack-start`](../examples/ssr-tanstack-start) | TanStack Start | pnpm | `` via TanStack Start | - -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 — they need `mise run playwright:install` first (already part of `setup`). - -### Adding a new example - -The full step-by-step is in [contributing.md](./contributing.md#adding-a-new-example). The shape is: +`mise run env:pack` generates the tarball, with `pnpm pack` inside `package/`. So each example consumes the package as published: the same `files`, the same `publishConfig.exports` and the same peer dependencies. -1. New dir under `examples//`. -2. `package.json` with `"@variablelab/env": "file:../../package/.local/variablelab-env.tgz"` and the runtime's flavor of devDeps (e.g. `@types/bun` for Bun, `@types/deno` for Deno). -3. `mise.toml` with `[tools]` and the standard tasks. Copy from the closest existing example. -4. `biome.json` extending `@rrlab/biome-config` (skip for Deno-only). -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. `mise run setup` from the new dir — commit the generated lockfile. -9. `mise lock` for every platform from the new dir — commit its `mise.lock` (CI installs tools with `--locked`). +### The examples -## mise tasks reference +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. -Run `mise tasks` from anywhere in the repo to see what's available. The core ones: +- [`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. -### Root tasks (in [`mise.toml`](../mise.toml)) +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. -| Task | What it does | -| --------------------------- | ----------------------------------------------------------------------------- | -| `setup` | Bootstrap everything (tools, root deps, tarball, examples, Playwright). | -| `env:pack` | Build `@variablelab/env` and pack it into `package/.local/variablelab-env.tgz`. | -| `env:watch` | Watch `package/src/`, repack on change, reinstall in every example. | -| `playwright:install` | Install Chromium for Playwright (one-time, idempotent). | -| `lib:test` | Unit tests for `@variablelab/env`. | -| `lib:build` | Build `@variablelab/env`. | -| `check` | JS & TS check across the workspace (`package` + `docsite`). | -| `test:e2e` | Run e2e for every example. | -| `examples:check` | JS & TS check across every example. | -| `examples:bump` | `reinstall` the tarball in every example. | - -### Per-example tasks (uniform across every `examples/*/mise.toml`) - -| Task | What it does | -| ------------- | ------------------------------------------------------------------------- | -| `setup` | Pack env + install deps with the example's native PM (depends on `//:env:pack`). | -| `reinstall` | Force-reinstall the env tarball. | -| `start` | Run the server (or `dev` / `build` / `preview` for the Vite-based ones). | -| `test:e2e` | Playwright. | -| `check` | JS & TS check. | - -## Why mise is the only API - -The root `package.json` carries no scripts. Every operation that crosses repo boundaries — running a workspace check, packing the lib, installing every example, running the e2e matrix — is a `mise run `. Two reasons: - -1. **One way to do each thing.** `pnpm setup` and `mise run setup` doing the same thing was just noise — devs had to remember which to type, and the docs had to mention both. -2. **CI and devs share a single contract.** What you type locally is the same thing CI runs (`mise run lib:test`, `mise run test:e2e`, …). No drift between `package.json` scripts and a separate CI command list. - -The `package/` and `docsite/` workspaces still expose plain `pnpm test` / `pnpm dev` / etc. inside their own `package.json` — those scripts are what the mise tasks ultimately delegate to. They're internal to each subproject; you only see them if you `cd` into the subproject and run `pnpm