fix(cli): scaffold Valibot PORT as v.number() - #1663
Conversation
init already imports @arkenv/standard/valibot, so string transforms fight the wrapper instead of letting it coerce env strings. Co-authored-by: Cursor <cursoragent@cursor.com>
🦋 Changeset detectedLatest commit: 7114998 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@arkenv/agent-plugin
arkenv
@arkenv/build
@arkenv/bun-plugin
@arkenv/core
@arkenv/fumadocs-ui
@arkenv/nextjs
@arkenv/nuxt
@arkenv/standard
@arkenv/vite-plugin
commit: |
There was a problem hiding this comment.
ℹ️ No critical issues — one minor suggestion inline.
Reviewed changes
- Valibot scaffold
PORTpipeline —packages/arkenv/src/features/scaffold/validators/dialects/valibot.tsnow generatesPORT: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1), v.maxValue(65535)), 3000), dropping thev.string()+v.transform(Number)front-end in both the server field anddefaultSimpleSchemaFieldspaths. Same range and default as before. - Scaffold template assertion —
validators.test.tsupdated to pin the new numeric pipeline. - Loader test fixture —
jiti-schema-loader.adapter.test.tsupdated fromv.fallback(v.pipe(v.string(), v.transform(Number)), 3000)tov.optional(v.number(), 3000)for the "defaulted keys" case. - Changeset — adds a
patchchangeset forarkenvdescribing the change.
I verified the fix behaviorally against @arkenv/standard/valibot (built from source): the new schema coerces PORT=3000 to a number, falls back to 3000 when unset, and rejects abc, out-of-range, and fractional values. The old scaffold was genuinely broken — with PORT unset it threw Expected string but received 3000 because Valibot routes the v.optional default through the pipe, so v.string() rejected the numeric default. Both affected test files pass locally.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
Cover the scaffolded v.optional(v.pipe(v.number(), …), 3000) schema with an empty env and PORT=3000 so the default-through-pipe regression cannot slip past a template-string assertion. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Added a runtime regression pin in
@arkenv/standard— the new commit boots the exact CLIPORTschema (v.optional(v.pipe(v.number(), v.integer(), v.minValue(1), v.maxValue(65535)), 3000)) with an empty env (expects default3000) and withPORT="3000"(expects coerced number3000), closing out the prior review's minor suggestion and the two still-unchecked boxes in the test plan. I confirmed its regression-catchy: a scratch pin using the old string-transform schema still throws on the empty-env path, so this test would have caught the original default-crash. All 48 CLI scaffold/loader tests and the 5subpaths.test.tstests pass against a fresh build.
No concerns with the incremental delta. The behavioral fix itself was already validated in the prior review.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to v1, this PR will be updated.⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ `v1` is currently in **pre mode** so this branch has prereleases rather than normal releases. If you want to exit prereleases, run `changeset pre exit` on `v1`.⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ # Releases ## arkenv@1.0.0-alpha.15 ### Major Changes - #### Replace `--json` payloads with settlement envelopes _[`#1633`](#1633) [`02a5a32`](02a5a32) [@yamcodes](https://github.com/yamcodes)_ `--json` / `--agent` now write an `ok`-discriminated settlement document to stdout instead of `{ status, code, message, retryWith }`. Human-readable output is unchanged. JSON stays on stdout; logs stay on stderr. Secret values are redacted in `meta`, `summary`, and `why`. Completed runs use `ok: true` (including `arkenv check` findings, which still set `exitCode: 4`). Aborts use `ok: false` with a dotted `CLI.*` or `ENV.*` code. `nextActions` is always present (`[]` when there is nothing to do). Commands in `nextActions` resolve to the invoked runner (`pnpm arkenv`, `npx arkenv`, `bunx arkenv`, and so on). Example refusal: ```json { "ok": false, "commandId": "init", "error": { "code": "CLI.GIT_TREE_DIRTY", "severity": "error", "summary": "Git working tree is not clean.", "nextActions": [ { "kind": "run-command", "label": "Re-run with --force to bypass this check", "command": "npx arkenv init --force" } ] }, "diagnostics": [], "nextActions": [ { "kind": "run-command", "label": "Re-run with --force to bypass this check", "command": "npx arkenv init --force" } ] } ``` **BREAKING CHANGE**: Agents must switch from `status` / `retryWith` to `ok` / `error.code` / `nextActions`. Flat codes such as `GIT_TREE_DIRTY` are now dotted (`CLI.GIT_TREE_DIRTY`). ```diff - { "status": "error", "code": "GIT_TREE_DIRTY", "retryWith": ["--force"] } + { "ok": false, "error": { "code": "CLI.GIT_TREE_DIRTY", "nextActions": [{ "kind": "run-command", "command": "npx arkenv init --force" }] } } ``` ### Minor Changes - #### Add `arkenv check` command and cross-module validation detection _[`#1632`](#1632) [`01a028d`](01a028d) [@yamcodes](https://github.com/yamcodes)_ - Add `arkenv check` CLI command to validate the active environment against the project's env schema, supporting `--schema` (`-s`), repeatable `--env-file`, `--json`, `--quiet`, and `--agent` with CI-friendly exit codes. - Add structural `ArkErrors` verification in `@arkenv/core` so cross-module-instance schema evaluation reliably surfaces validation issues. - #### Add `arkenv example` to update `.env.example` from the schema _[`#1643`](#1643) [`de8add3`](de8add3) [@yamcodes](https://github.com/yamcodes)_ `arkenv example` now loads the project schema and writes `.env.example` with every declared key. Existing comments and values are preserved for keys that remain in the schema; removed keys are dropped; new keys are appended. `arkenv init` reuses the same path after scaffolding an existing project. Usage: ```sh npx arkenv@latest example npx arkenv@latest example --schema ./src/env.ts --json ``` ### Patch Changes - #### Drop default `z.coerce` from Zod product samples and scaffold templates _[`#1631`](#1631) [`ba9f903`](ba9f903) [@yamcodes](https://github.com/yamcodes)_ Scaffolded Zod templates, `@arkenv/standard` JSDoc examples, and official example projects now declare numeric and boolean fields with `z.number()` and `z.boolean()` instead of `z.coerce.number()` or `z.coerce.boolean()`, reflecting ArkEnv's built-in pre-coercion for Standard Schema validators. ```ts import arkenv from "@arkenv/standard"; import { z } from "zod"; export const env = arkenv({ PORT: z.number().default(3000), DATABASE_URL: z.string().url(), DEBUG: z.boolean().default(false), }); ``` - #### Align Next.js onboarding with `@/.arkenv` _[`#1639`](#1639) [`cc87d51`](cc87d51) [@yamcodes](https://github.com/yamcodes)_ `arkenv init` now scaffolds Next.js schemas that import the codegen factory from `@/.arkenv`, writes that factory to `.arkenv/env.gen.ts`, gitignores `.arkenv/`, and maps the specifier in `tsconfig.json`. Docs, READMEs, and examples match that path instead of `./generated/env.gen`. ```ts import arkenv from "@/.arkenv"; export const env = arkenv({ DATABASE_URL: "string", NEXT_PUBLIC_API_URL: "string", }); ``` Existing `outputPath` overrides still work. Keep importing `@/.arkenv`. `withArkEnv` aliases the specifier for bundlers, and codegen keeps `.arkenv/index.ts` re-exporting the file so `tsc --noEmit` resolves it too. - #### Scaffold Valibot `PORT` as `v.number()` instead of a string transform _[`#1663`](#1663) [`0cf0e74`](0cf0e74) [@yamcodes](https://github.com/yamcodes)_ `arkenv init` with Valibot already imports `@arkenv/standard/valibot`, which binds `@valibot/to-json-schema` for coercion. Generate `PORT` as a numeric schema so the wrapper can coerce `"3000"` instead of requiring `v.transform(Number)`. ```ts import { arkenv } from "@arkenv/standard/valibot"; import * as v from "valibot"; export const env = arkenv({ PORT: v.optional( v.pipe(v.number(), v.integer(), v.minValue(1), v.maxValue(65535)), 3000 ), }); ``` ## @arkenv/core@1.0.0-alpha.7 ### Patch Changes - #### Add `arkenv check` command and cross-module validation detection _[`#1632`](#1632) [`01a028d`](01a028d) [@yamcodes](https://github.com/yamcodes)_ - Add `arkenv check` CLI command to validate the active environment against the project's env schema, supporting `--schema` (`-s`), repeatable `--env-file`, `--json`, `--quiet`, and `--agent` with CI-friendly exit codes. - Add structural `ArkErrors` verification in `@arkenv/core` so cross-module-instance schema evaluation reliably surfaces validation issues. ## @arkenv/nextjs@1.0.0-alpha.13 ### Patch Changes - #### Align Next.js onboarding with `@/.arkenv` _[`#1639`](#1639) [`cc87d51`](cc87d51) [@yamcodes](https://github.com/yamcodes)_ `arkenv init` now scaffolds Next.js schemas that import the codegen factory from `@/.arkenv`, writes that factory to `.arkenv/env.gen.ts`, gitignores `.arkenv/`, and maps the specifier in `tsconfig.json`. Docs, READMEs, and examples match that path instead of `./generated/env.gen`. ```ts import arkenv from "@/.arkenv"; export const env = arkenv({ DATABASE_URL: "string", NEXT_PUBLIC_API_URL: "string", }); ``` Existing `outputPath` overrides still work. Keep importing `@/.arkenv`. `withArkEnv` aliases the specifier for bundlers, and codegen keeps `.arkenv/index.ts` re-exporting the file so `tsc --noEmit` resolves it too. <details><summary>Updated 2 dependencies</summary> <small> [`01a028d`](01a028d) [`ba9f903`](ba9f903) </small> - `@arkenv/core@1.0.0-alpha.7` - `@arkenv/standard@1.0.0-alpha.7` </details> ## @arkenv/standard@1.0.0-alpha.7 ### Patch Changes - #### Drop default `z.coerce` from Zod product samples and scaffold templates _[`#1631`](#1631) [`ba9f903`](ba9f903) [@yamcodes](https://github.com/yamcodes)_ Scaffolded Zod templates, `@arkenv/standard` JSDoc examples, and official example projects now declare numeric and boolean fields with `z.number()` and `z.boolean()` instead of `z.coerce.number()` or `z.coerce.boolean()`, reflecting ArkEnv's built-in pre-coercion for Standard Schema validators. ```ts import arkenv from "@arkenv/standard"; import { z } from "zod"; export const env = arkenv({ PORT: z.number().default(3000), DATABASE_URL: z.string().url(), DEBUG: z.boolean().default(false), }); ``` --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

Summary
arkenv initwith Valibot already imports@arkenv/standard/valibot, which binds@valibot/to-json-schemasov.number()coerces env strings.PORTfield still usedv.pipe(v.string(), v.transform(Number), …), which fights that wrapper. ScaffoldPORTas a numeric schema instead (same range/default as before).examples/with-valibotexample onv1already used the wrapper +v.number(); this aligns the CLI with it.Test plan
pnpm --filter arkenv exec vitest run src/features/scaffold/validators.test.tsarkenv initenv file imports@arkenv/standard/valibotand hasPORT: v.optional(v.pipe(v.number(), …), 3000)PORT=3000boots without a manual transformMade with Cursor