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
5 changes: 5 additions & 0 deletions .changeset/drop-type-fest-dep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@vlandoss/env": patch
---

Drop the `type-fest` dependency. `PartialDeep` is now defined locally in `src/lib/types.ts` with an equivalent shape, so the public surface of `Config<S>` and `Defaults<S>` is unchanged. One fewer transitive dep for consumers.
27 changes: 25 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ jobs:
- name: 📦 Install root workspace
run: pnpm install --frozen-lockfile

- name: 💅 Test static (biome + tsc)
run: mise run test:static
- name: 💅 Check (biome + tsc)
run: mise run check

- name: 🎭 Install Playwright browser (lib browser tests)
run: mise run playwright:install
Expand Down Expand Up @@ -92,3 +92,26 @@ jobs:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
LEFTHOOK: 0

preview:
runs-on: ubuntu-latest
if: ${{ startsWith(github.head_ref, 'feat') || startsWith(github.head_ref, 'fix') || contains(github.event.pull_request.labels.*.name, 'preview') }}
permissions:
id-token: write
contents: read
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4

- name: 🟢 Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .node-version

- name: ⌛ Setup CI
uses: variableland/gh-actions/actions/setup-pnpm@main

- name: 🚀 Preview release
uses: variableland/gh-actions/actions/monorepo-preview-release@main
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
39 changes: 0 additions & 39 deletions .github/workflows/preview.yml

This file was deleted.

14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ pnpm add @vlandoss/env

This is the monorepo. Three things live here:

| Path | What it is |
| -------------------------- | ----------------------------------------------------------------------------------------------------- |
| [`package/`](./package) | The [`@vlandoss/env`](./package) library — published to npm |
| [`docsite/`](./docsite) | The Fumadocs site behind [env.oss.variable.land](https://env.oss.variable.land), 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 |
| Path | What it is |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| [`package/`](./package) | The [`@vlandoss/env`](./package) library — published to npm |
| [`docsite/`](./docsite) | The Fumadocs site behind [env.oss.variable.land](https://env.oss.variable.land), 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

Expand All @@ -40,7 +40,7 @@ Day-to-day:
mise run docsite # docs site dev server
mise run lib:test # unit tests for @vlandoss/env
mise run lib:build # build @vlandoss/env
mise run test:static # JS & TS check across package + docsite
mise run check # JS & TS check across package + docsite
```

Run `mise tasks` for the full list.
Expand All @@ -53,4 +53,4 @@ Issues, ideas, and PRs welcome. Read [CONTRIBUTING.md](./docs/CONTRIBUTING.md) b

## License

[MIT](./LICENSE) © [Variableland](https://variable.land)
[MIT](./LICENSE) © [Variable Land](https://variable.land)
4 changes: 2 additions & 2 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Don't bump versions or edit `CHANGELOG.md` by hand — Changesets does both duri
- [ ] 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/<name>/test/e2e/` if relevant)
- [ ] `mise run lib:test` and `mise run test:static` pass locally (JS & TS check)
- [ ] `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 `@vlandoss/env` changed)
- [ ] Docs updated (if public API or behavior changed)
Expand All @@ -97,7 +97,7 @@ Examples are runtime-isolated demos. Each one is a real consumer of the publishe
```jsonc
"@vlandoss/env": "file:../../package/.local/vlandoss-env.tgz"
```
3. Add a `mise.toml` with the runtime tools (`node` / `bun` / `deno` / `pnpm`) and the standard tasks (`install`, `start`, `test:e2e`, `test:types`, `test:static`). Use any of the existing examples as a template — pick the one with the closest runtime.
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 `@vlandoss/config/biome` (skip this for Deno-only examples).
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).
Expand Down
41 changes: 19 additions & 22 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,10 @@ mise run setup # ↓ runs the bootstrap chain
`mise run setup` runs the following 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, etc.).
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 env:pack` — builds `@vlandoss/env` and packs it into `package/.local/vlandoss-env.tgz`.
5. `for d in examples/*/; do mise -C "$d" run install; done` — installs each example.
6. `mise run playwright:install` — fetches Chromium for the SPA / SSR e2e suites.
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.

End state: every example has its own `node_modules/`, its own lockfile, and a freshly installed copy of `@vlandoss/env` extracted from the tarball.

Expand All @@ -63,10 +62,10 @@ The library uses [tsdown](https://tsdown.dev) for the build and [vitest](https:/
```sh
mise run lib:test # unit tests
mise run lib:build # emits dist/
mise run test:static # JS & TS check across package + docsite
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 test:static` ultimately call.
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.

### Iterating with the examples

Expand All @@ -77,7 +76,7 @@ mise run env:pack # rebuild + repack the tarball
mise run examples:bump # reinstall the tarball in every example
```

Each example's `install` task prepends `mise run //: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` / `outputs` declared on `env:pack` itself), so any of these will pull in the latest tarball automatically:

```sh
mise run //examples/backend-node:test:e2e
Expand Down Expand Up @@ -111,11 +110,11 @@ Content lives in `docsite/content/docs/` as MDX. Read [`docsite/README.md`](../d
Every example has the same task vocabulary, so you can `cd` into any of them and run:

```sh
mise run install # install deps (uses the example's native PM)
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 test:e2e # Playwright
mise run test:static # JS & TS check
mise run check # JS & TS check
```

Or invoke from the repo root with the `//path:task` syntax:
Expand Down Expand Up @@ -162,7 +161,7 @@ The full step-by-step is in [CONTRIBUTING.md](./CONTRIBUTING.md#adding-a-new-exa
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 install` from the new dir — commit the generated lockfile.
8. `mise run setup` from the new dir — commit the generated lockfile.

## mise tasks reference

Expand All @@ -178,20 +177,20 @@ Run `mise tasks` from anywhere in the repo to see what's available. The core one
| `playwright:install` | Install Chromium for Playwright (one-time, idempotent). |
| `lib:test` | Unit tests for `@vlandoss/env`. |
| `lib:build` | Build `@vlandoss/env`. |
| `test:static` | JS & TS check across the workspace (`package` + `docsite`). |
| `check` | JS & TS check across the workspace (`package` + `docsite`). |
| `test:e2e` | Run e2e for every example. |
| `examples:test:static` | JS & TS check across 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 |
| ------------- | ------------------------------------------------------------------------- |
| `install` | Install deps with the example's native PM. Prepends `mise run //:env:pack`. |
| `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. |
| `test:static` | JS & TS check. |
| `check` | JS & TS check. |

## Why mise is the only API

Expand Down Expand Up @@ -244,21 +243,19 @@ depends = ["//:env:pack"]

If you're writing a new task and hit this error, use the `//:` prefix.

### `task not found: env:pack` (when running across examples)
### Fanning out a task across every example

If you write a root task that loops over examples, use `mise -C path run task` rather than `cd path && mise run task`. The latter inherits a stale shell context that can confuse the resolver:
Use mise's monorepo glob — it resolves against `[monorepo].config_roots` and runs in parallel (controlled by `MISE_JOBS`):

```toml
# good
run = "for d in examples/*/; do mise -C \"$d\" run test:e2e || exit 1; done"

# bad — fails inside the orchestrator task context
run = "for d in examples/*/; do (cd \"$d\" && mise run test:e2e) || exit 1; done"
run = "mise run \"//examples/...:test:e2e\""
```

Avoid `for d in examples/*/; do ...; done` — it's sequential and predates this feature. The one place a bash loop still makes sense is `mise install -C "$d"` for per-example tool installs (bun, deno, etc.), since `mise install` doesn't accept the monorepo glob.

### pnpm tries to install across the whole monorepo from inside an example

Because the example dirs sit *inside* the same directory tree as `pnpm-workspace.yaml`, pnpm thinks they're workspace members. They're not — but pnpm needs an explicit `--ignore-workspace` to honor that. The 6 pnpm-based examples already pass it in their `install` tasks; if you're adding a new pnpm-based example, do the same.
Because the example dirs sit *inside* the same directory tree as `pnpm-workspace.yaml`, pnpm thinks they're workspace members. They're not — but pnpm needs an explicit `--ignore-workspace` to honor that. The 6 pnpm-based examples already pass it in their `setup` tasks; if you're adding a new pnpm-based example, do the same.

### Deno can't import `@vlandoss/env` from the tarball

Expand Down
11 changes: 9 additions & 2 deletions docsite/src/components/landing/close.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,22 @@ export function Close() {
<span className="text-landing-border-strong">·</span>
<span>made at</span>
<a
href="https://variable.land"
href={LANDING_META.vlandUrl}
target="_blank"
rel="noreferrer"
className="text-landing-dim-2 transition-colors hover:text-landing-accent"
>
Variable Land
</a>
<span className="text-landing-border-strong">·</span>
<span>{LANDING_META.version}</span>
<a
href={LANDING_META.npmUrl}
target="_blank"
rel="noreferrer"
className="text-landing-dim-2 transition-colors hover:text-landing-accent"
>
{LANDING_META.version}
</a>
<span className="text-landing-border-strong">·</span>
<span>{LANDING_META.publishDate}</span>
</div>
Expand Down
2 changes: 2 additions & 0 deletions docsite/src/components/landing/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ export const WHY_POINTS = [

export const LANDING_META = {
version: "v0.2.0",
vlandUrl: "https://variable.land",
githubUrl: "https://github.com/variableland/env",
npmUrl: "https://npmx.dev/package/@vlandoss/env",
docsHref: "/docs",
publishDate: "2026",
} as const;
26 changes: 13 additions & 13 deletions docsite/src/components/landing/nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ export function LandingNav() {
className="
relative z-10 flex items-center justify-between
gap-3 border-b border-landing-border
px-16 py-[26px]
@max-[1100px]/dirA:px-10 @max-[1100px]/dirA:py-[22px]
@max-[760px]/dirA:px-7 @max-[760px]/dirA:py-[18px]
px-16 py-6.5
@max-[1100px]/dirA:px-10 @max-[1100px]/dirA:py-5.5
@max-[760px]/dirA:px-7 @max-[760px]/dirA:py-4.5
@max-[480px]/dirA:px-5 @max-[480px]/dirA:py-4
"
>
Expand All @@ -32,21 +32,21 @@ export function LandingNav() {
<span className="text-[15px] font-semibold tracking-[-0.01em] text-landing-text">
@vlandoss<span className="mx-px text-landing-dim-2">/</span>env
</span>
<span
className="
ml-1.5 rounded-[4px] border border-landing-border-strong
px-[7px] py-[3px] font-landing-mono text-[11px] text-landing-dim
@max-[480px]/dirA:hidden
"
>
{LANDING_META.version}
</span>
<a href={LANDING_META.npmUrl} target="_blank" rel="noreferrer">
<span
className="ml-1.5 rounded-sm border border-landing-border-strong
px-1.75 py-0.75 font-landing-mono text-[11px] text-landing-dim transition-colors hover:text-landing-accent
@max-[480px]/dirA:hidden hover:border-landing-accent"
>
{LANDING_META.version}
</span>
</a>
</div>

<nav
className="
flex items-center gap-7
@max-[760px]/dirA:gap-[18px]
@max-[760px]/dirA:gap-4.5
@max-[480px]/dirA:hidden
"
>
Expand Down
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ mise run start

## Iterating on `package/` while developing

Each example's `install` task `depends = ["env:pack"]`, and `env:pack` declares `sources` / `outputs` so it skips when nothing changed. So a regular `mise run //examples/<name>:test:e2e` already does the right thing from cold.
Each example's `setup` task depends on `//:env:pack`, and `env:pack` declares `sources` / `outputs` so it skips when nothing changed. So a regular `mise run //examples/<name>:test:e2e` already does the right thing from cold.

For an active edit loop:

Expand Down
26 changes: 11 additions & 15 deletions examples/backend-bun/mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,30 @@ node = "24" # for `rr` and `playwright` (node-based tooling)
[env]
_.path = ["{{config_root}}/node_modules/.bin"]

[tasks.install]
description = "Install deps (bun)"
[tasks.setup]
description = "Pack env + install deps (bun)"
depends = ["//:env:pack"]
sources = [
"package.json",
"bun.lock",
"{{config_root}}/../../package/.local/vlandoss-env.tgz",
]
run = [
"mise run //:env:pack",
"bun install",
]
run = "bun install"

[tasks.reinstall]
description = "Force-reinstall the env tarball"
run = [
"mise run //:env:pack",
"bun install --force",
]
depends = ["//:env:pack"]
run = "bun install --force"

[tasks.start]
depends = ["install"]
depends = ["setup"]
run = "bun src/server.ts"

[tasks."test:e2e"]
depends = ["install"]
depends = ["setup"]
run = "playwright test"

[tasks."test:static"]
[tasks.check]
description = "JS & TS check"
depends = ["install"]
run = "rr test:static"
depends = ["setup"]
run = "rr check"
Loading
Loading