Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
552a9a8
feat: add turborepo skill and ADRs 0007 & 0008 from analyze-zod-expor…
yamcodes Jun 12, 2026
8defbe3
docs: remove ADR 8 (belongs to issue 1200)
yamcodes Jun 13, 2026
1c6fc7f
docs: add ADR 0010 for runtime shared logic strategy
yamcodes Jun 13, 2026
8e35d3c
docs: standardize product name as ArkEnv in ADR and CLI changelog
yamcodes Jun 16, 2026
6d55945
docs: add ADR 0008 to document the migration from createEnv to arkenv…
yamcodes Jun 16, 2026
cc0b227
Please provide the list of changes or the files modified so I can gen…
yamcodes Jun 16, 2026
45d9f4f
Merge branch 'v1' into 1199-distribution-implement-standard-mode-pack…
yamcodes Jun 16, 2026
ba951a2
Merge branch 'v1' into 1199-distribution-implement-standard-mode-pack…
yamcodes Jun 19, 2026
bca2fe0
feat: implement hybrid versioning by decoupling plugins and enforcing…
yamcodes Jun 19, 2026
9f20282
feat: move arkenv to peer dependencies and update changeset documenta…
yamcodes Jun 19, 2026
26663c3
docs: add README for @arkenv/build and clarify runtime strategy in AD…
yamcodes Jun 19, 2026
036127b
docs: clarify independent versioning and fix package name references
yamcodes Jun 20, 2026
185c89a
docs: add rule for future core/standard split in versioning strategy
yamcodes Jun 20, 2026
641b4e2
docs(adr): document peer dependency rationale and CLI onboarding flow
yamcodes Jun 20, 2026
f8ef94b
docs(adr): resolve review feedback on independent versioning examples…
yamcodes Jun 20, 2026
d88cbe8
refactor: decouple plugins from core engine by replacing workspace pe…
yamcodes Jun 20, 2026
0f5e61a
docs: add enterprise precedent for peer dependency strategy in ADR 0011
yamcodes Jun 20, 2026
61340e1
Merge remote-tracking branch 'origin/dev' into 1199-distribution-impl…
yamcodes Jun 23, 2026
a970f14
Merge branch 'v1' into 1199-distribution-implement-standard-mode-pack…
yamcodes Jun 23, 2026
7820207
docs: resolve changeset peerDependency range review comment and AutoT…
yamcodes Jun 23, 2026
a710669
Merge branch 'v1' into 1199-distribution-implement-standard-mode-pack…
yamcodes Jun 23, 2026
57b30a5
refactor: rename nuxt internal file to arkenv-internal
yamcodes Jun 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://unpkg.com/@changesets/config@3.0.5/schema.json",
"changelog": ["changesets-changelog-clean", { "repo": "yamcodes/arkenv" }],
"commit": false,
"fixed": [],
"fixed": [["arkenv", "@arkenv/cli"]],
Comment thread
yamcodes marked this conversation as resolved.
Outdated
"linked": [],
"access": "public",
"baseBranch": "dev",
Expand Down
33 changes: 33 additions & 0 deletions .changeset/epg3nspi.md
Comment thread
yamcodes marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
"@arkenv/nextjs": major
"@arkenv/nuxt": major
"@arkenv/vite-plugin": major
"@arkenv/bun-plugin": major
---

#### Move `arkenv` to peer dependencies in framework plugins

Framework plugins no longer declare `arkenv` as a regular dependency. `arkenv` is now declared as a `peerDependency` with a caret range (`^1.0.0-alpha.1`), ensuring a single shared instance across all plugins and the host application.
Comment thread
pullfrog[bot] marked this conversation as resolved.
Outdated

This change prevents duplicate instances of `arkenv` in `node_modules`, which could break ArkType structural typing and schema validation at runtime.

Plugins affected:

- `@arkenv/nextjs`
- `@arkenv/nuxt`
- `@arkenv/vite-plugin`
- `@arkenv/bun-plugin`

Before:

```bash
npm install @arkenv/nextjs
```

After:

```bash
npm install arkenv @arkenv/nextjs
```

**BREAKING CHANGE:** Users must now install `arkenv` alongside the plugin. Previously, `arkenv` was automatically pulled in as a regular dependency.
40 changes: 40 additions & 0 deletions docs/adr/0007-standard-mode-packaging-strategy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Standard Mode Packaging Strategy

## Context

With the introduction of the "Standard Schema" implementation (which strictly removes ArkType dependencies) and the migration to the `@arkenv` scope, we face a decision on how to distribute the "Standard Mode" across the core library and its framework plugins.

The decision is asymmetric:

1. **The Core Engine:** We are splitting the core into two separate packages (`@arkenv/core` for ArkType, and `@arkenv/standard` for Standard Schema).
2. **The Framework Plugins:** We are **not** splitting the plugins into separate packages (e.g., we will not create `@arkenv/vite-plugin-standard`). Instead, a single plugin package will support both engines via subpath exports (e.g., `import arkenv from "@arkenv/vite-plugin/standard"`).

## Decision: Why the Asymmetry?

We have chosen this asymmetric packaging strategy for three primary reasons:

### 1. The N-Multiplier Effect

If we mandate that every engine requires a separate package, adding support for a new framework requires creating two new packages (e.g., `@arkenv/bun-plugin` and `@arkenv/bun-plugin-standard`). If we ever add a third engine, we'd need three packages per framework. By using subpath exports in the plugins, we maintain a 1:1 ratio between frameworks and plugin packages. This dramatically reduces repository and npm namespace bloat.

### 2. Perfect Sibling Symmetry & Discoverability

By extracting `@arkenv/standard` into its own package, we achieve perfect sibling symmetry: `@arkenv/core` represents the primary ArkType engine, and `@arkenv/standard` represents the alternative Standard Schema engine. This elevates Standard Mode to a first-class citizen, providing a massive discoverability benefit for Zod and Valibot users who no longer have to install a package whose primary identity is built around ArkType.

### 3. Zero Peer Dependency Confusion

The engines have fundamentally different dependency requirements. The core requires `arktype`, while the standard mode is dependency-free. Splitting them allows `@arkenv/core` to list `arktype` as a *required* peer dependency, while `@arkenv/standard` has zero peer dependencies. This is much cleaner for package managers than relying on complex `peerDependenciesMeta: { optional: true }` configurations at a unified core level.

### 4. Shared Internals (Enabler)

This split is made architecturally feasible because we are already adopting an "inlined internal packages" strategy. We can easily extract the shared `parse-standard` and `guards` logic into an internal `@repo/utils` package, and bundle that into both `@arkenv/core` and `@arkenv/standard` at publish time without creating dual-package hazards.

### 5. Plugins are Thin Routers

Framework plugins do not implement parsing or evaluation logic; they are essentially dependency-injectors that bridge the build tool to the engine. Because they are lightweight, a single plugin package can safely list both `@arkenv/core` and `@arkenv/standard` as optional peer dependencies. The user installs the engine they want, imports the correct plugin subpath, and the bundler tree-shakes the unused path. There is no risk of leaking heavy dependencies.

## Consequences

- **Positive:** The npm ecosystem remains clean (fewer packages).
- **Positive:** Users only download the dependencies they actually need (ArkType vs Standard).
- **Negative:** Plugin maintainers must ensure their `package.json` correctly exposes multiple `"exports"` and manages dual optional peer dependencies.
80 changes: 80 additions & 0 deletions docs/adr/0010-runtime-shared-logic-strategy.md
Comment thread
yamcodes marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Runtime Shared Logic Strategy

## Context

ArkEnv relies on several internal utility functions and runtime configurations that must be shared across our core engine (`arkenv`) as well as framework plugins (e.g., `@arkenv/nextjs`, `@arkenv/nuxt`). This creates an architectural decision regarding how to distribute and share this logic effectively without compromising developer experience, type safety, or build environments.

When comparing code-sharing strategies, two traditional approaches are often cited:

1. **The NestJS Peer Dependency Model:** Extensive use of `peerDependencies` where heavy singletons are hoisted to the application root.
2. **The Published Utility Package:** Creating a public `@arkenv/utils` package published to npm that all other packages depend on.

Both of these standard approaches introduce critical flaws for ArkEnv's unique requirements as a structural typing utility.

## Decision

We will avoid peer dependencies for **runtime** code sharing and avoid publishing an external utility package for runtime logic.

Instead, we will adopt an **Inlined Internal Packages Strategy** (`@repo/*`) combined with **Subpath Exports** for sharing runtime logic.

We distinguish between two categories of shared logic:

### Runtime Shared Logic

Runtime logic must remain 100% dependency-free and must not suffer from version or singleton skew.

1. **Stateless Logic (Helpers/Parsers):** Shared stateless logic will live in an internal monorepo package (e.g., `@repo/utils`). Using our bundler (`tsdown`), this logic will be physically inlined into the distributables of the consuming published packages (`arkenv`, `@arkenv/cli`).
2. **Stateful Logic (Singletons/Schemas):** Any stateful logic or singleton configuration will be managed via Subpath Exports directly from the core `arkenv` package, rather than using peer dependencies to enforce a single instance.

### Build-Time Shared Logic

Build-time logic is Node-only and does not impact the runtime footprint. This includes AST parsing, file-watching, and codegen utilities.

Build-time shared logic is centralized in the published `@arkenv/build` package (see [ADR 0009: Shared Build Package](./0009-shared-build-package.md)). Framework plugins list `@arkenv/build` as a regular dependency. Because it is consumed only during development and build phases, it does not compromise Edge runtime compatibility.

## Rationale

### 1. Why Not The NestJS Peer Dependency Model?

NestJS is a heavy, opinionated framework. It extensively uses `peerDependencies` (e.g., `@nestjs/core`, `@nestjs/common`) and relies on the user's package manager (npm/pnpm/yarn) to hoist these dependencies to the root of the project, theoretically ensuring a single instance of the framework's IoC container across the app.

This works for NestJS because users expect a rigid, complex dependency graph within a tightly controlled application boundary.

However, this is fundamentally incompatible with ArkEnv for several reasons:

- **Version Skew and Phantom Dependencies:** Monorepo package managers notoriously struggle with strict peer dependency hoisting. If a user installs a slightly different version of a sub-package, the package manager may duplicate the dependency in `node_modules`.
- **Fatal for Structural Typing:** ArkEnv relies heavily on ArkType, which evaluates schemas using structural typing, `instanceof` checks, and strict object identity under the hood. If a duplicated instance of the core engine or ArkType is loaded due to peer dependency failure, schema evaluation and type scoping break silently at runtime. We cannot afford to trust hoisting for singleton integrity.

### 2. Why Not a Published `@arkenv/utils` Package?

The standard alternative to peer dependencies is a centralized utility package. We could create an `@arkenv/utils` package and publish it to npm, having `arkenv` and framework plugins depend on it normally.

This introduces unacceptable friction:

- **Public API Surface Bloat:** We would be forced to publish purely internal implementation details to npm.
- **Dependency Graph Bloat:** Users must download additional packages, slowing down install times and increasing the overall node_modules footprint.
- **Cascading Version Bumps:** Updating a single internal helper function would require bumping `@arkenv/utils`, which then requires bumping `arkenv` and all framework plugins, turning minor internal refactors into massive versioning chores.

### 3. The Power of Inlined Internal Packages

By keeping `@repo/utils` purely as a local workspace package, we achieve the best of both worlds:

- **Zero Distribution Cost:** Using `tsdown`, we inline the specific functions we need directly into the published bundles. At runtime, the user downloads zero external utility packages. The logic is fully self-contained.
- **Excellent Monorepo DX:** We maintain the developer experience of sharing typed code across our monorepo during development.
- **Build Tool Stability:** Inlined code avoids the issue where "build tools crash on Edge runtimes." If we used external packages that inadvertently pulled in Node-native modules (like `fs` or `path`), Edge deployment platforms (like Vercel) would crash. Inlining allows us to strictly control the exact code entering the runtime payload.

### 4. Separation of Build-Time Concerns

By routing build-time shared logic through `@arkenv/build` (a published but internal-only package), we:

- Keep the core `arkenv` runtime 100% dependency-free and Edge-compatible.
- Avoid duplicating AST parsers, file watchers, and codegen logic across framework plugins.
- Allow build utilities to depend on Node-native modules (`fs`, `path`, `chokidar`) without risking runtime crashes.

## Consequences

- **Positive:** No version skew or runtime duplication risks for core logic.
- **Positive:** Faster user installs with fewer runtime packages.
- **Positive:** Reduced version bump cascading for internal runtime helpers.
- **Positive:** Clean separation between runtime and build-time concerns.
- **Negative:** The compiled bundle size of the core packages might be trivially larger due to inlined helpers, but this is mitigated by tree-shaking and the lightweight nature of the utilities.
80 changes: 80 additions & 0 deletions docs/adr/0011-hybrid-versioning-strategy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Hybrid Versioning Strategy

## Context

ArkEnv is a monorepo containing a core engine, a CLI codegen tool, multiple framework plugins, and build utilities. Choosing a single versioning strategy for all packages leads to unnecessary releases and coupling.

For example:

- A bug fix in `@arkenv/nextjs` should not force a new version of `arkenv`.
- A breaking change in `arkenv` should be reflected immediately in `@arkenv/cli` to prevent schema/codegen version mismatches.

## Decision

We will adopt a **hybrid versioning model** that combines fixed (locked) versioning for the core inner circle with independent versioning for the outer ring of plugins and utilities.

### Inner Circle (Fixed / Locked)

The following packages are always kept in version lockstep:

- `arkenv` (core engine)
- `@arkenv/cli` (codegen tool)

**Rationale:** `@arkenv/cli` generates code based on `arkenv`'s schema internals. A version mismatch between the CLI and the core engine can produce incompatible or broken generated files. Keeping them fixed eliminates this class of errors.

### Outer Ring (Independent)

The following packages float independently:

- Framework plugins: `@arkenv/nextjs`, `@arkenv/nuxt`, `@arkenv/vite-plugin`, `@arkenv/bun-plugin`
- Build utilities: `@arkenv/build`, `@arkenv/fumadocs-ui`

**Rationale:** These packages integrate with external frameworks and tools that evolve on their own schedules. A local integration fix (e.g., adapting to a new Next.js API) should not trigger a core engine release. Independent versioning allows us to ship plugin-specific fixes without cascading version bumps to the entire ecosystem.

### The Glue (Strict Peer Dependencies)

All framework plugins declare `arkenv` as a **strict peer dependency**:

- This enforces compatibility at the package manager level.
- Users are warned (or blocked) if they install an incompatible plugin/core combination.
- It prevents silent version skew that could break structural typing at runtime.

#### Monorepo Protocol: `workspace:*` vs `workspace:^`

Because this is a monorepo, plugins reference the local `arkenv` package during development. We use two different workspace protocols depending on the dependency field:

**`devDependencies`: `"arkenv": "workspace:*"`**

- During local development, this links directly to the workspace package (the folder in `packages/arkenv`), regardless of its current version.
- When published to npm, pnpm rewrites `workspace:*` to the **exact current version** (e.g., `"1.0.0-alpha.1"`).
- This is correct for `devDependencies` because they are not installed by end users.

**`peerDependencies`: `"arkenv": "workspace:^"`**

- During local development, this also links to the workspace package.
- When published to npm, pnpm rewrites `workspace:^` to a **caret range** (e.g., `"^1.0.0-alpha.1"`).
- This is **mandatory** for `peerDependencies`. Using `workspace:*` here would publish an exact pin, meaning every patch release of `arkenv` would trigger an unmet peer dependency warning for all users until they manually update every plugin. The caret range allows users to upgrade `arkenv` within the major version without friction.

**`dependencies` (Highly Coupled Internals): `"arkenv": "workspace:~"`**

- For internal packages that rely on undocumented or deep-level APIs of `arkenv` (e.g., a future `@arkenv/parser` or codegen utility that reaches into internal schema shapes), `workspace:~` is the appropriate protocol.
- When published, pnpm rewrites `workspace:~` to a **tilde range** (e.g., `"~1.0.0-alpha.1"`), which allows patch updates but locks the minor version.
- This ensures that a minor feature update to `arkenv` cannot silently break the coupled internal tool. If `arkenv` bumps its minor version, the dependent package must also be released and bumped.
- **Not currently used** in the ArkEnv monorepo, but reserved for future highly-coupled internal tools.

| Field | Protocol | Local Dev | Published |
| ---------------------- | ------------- | ------------------ | ------------------------------- |
| `devDependencies` | `workspace:*` | Links to workspace | Exact version (`1.0.0-alpha.1`) |
| `peerDependencies` | `workspace:^` | Links to workspace | Caret range (`^1.0.0-alpha.1`) |
| `dependencies` (tight) | `workspace:~` | Links to workspace | Tilde range (`~1.0.0-alpha.1`) |

> **Reference:** See [pnpm documentation on publishing workspace packages](https://pnpm.io/workspaces#publishing-workspace-packages) for full details on how `workspace:` protocols are rewritten during publish.

## Consequences

- **Positive:** Core engine stability. Inner-circle releases are deliberate and reflect genuine engine or codegen changes.
- **Positive:** Plugin agility. Framework integrations can iterate and release quickly without coordinating with core releases.
- **Positive:** Clear contract. Users understand that `arkenv` and `@arkenv/cli` move together, while plugins move at their own pace.
- **Positive:** Peer dependency enforcement catches mismatched installations early.
- **Negative:** Slightly more complex release management (Changesets handles this via the `fixed` and `independent` configuration mix).
- **Negative:** Plugin authors must ensure peer dependency ranges accurately reflect tested compatibility.
5 changes: 2 additions & 3 deletions packages/bun-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@
},
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"dependencies": {
"arkenv": "workspace:*"
},
"devDependencies": {
"@repo/types": "workspace:*",
"@size-limit/esbuild-why": "catalog:",
"@size-limit/preset-small-lib": "catalog:",
"@types/bun": "catalog:",
"arkenv": "workspace:*",
"arktype": "catalog:",
"bun": "catalog:",
"rimraf": "catalog:",
Expand All @@ -25,6 +23,7 @@
"vitest": "catalog:"
},
"peerDependencies": {
"arkenv": "workspace:^",
"arktype": "^2.1.22",
"bun": "^1.0.0"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@

- #### Respect `tsconfig.json` for path resolution and scaffolding _[`#1013`](https://github.com/yamcodes/arkenv/pull/1013) [`0a18edd`](https://github.com/yamcodes/arkenv/commit/0a18edd97564b5b178bd20235a1bb0c20ed375ab) [@yamcodes](https://github.com/yamcodes)_

The Arkenv CLI now dynamically resolves configuration paths and scans project files by respecting `tsconfig.json` settings (`rootDir`, `paths`, `baseUrl`).
The ArkEnv CLI now dynamically resolves configuration paths and scans project files by respecting `tsconfig.json` settings (`rootDir`, `paths`, `baseUrl`).

Key improvements include:

Expand Down
3 changes: 2 additions & 1 deletion packages/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"dependencies": {
"arkenv": "workspace:*",
"chokidar": "^4.0.3",
"server-only": "^0.0.1"
},
"devDependencies": {
"@repo/scope": "workspace:*",
"@repo/types": "workspace:*",
"@size-limit/preset-small-lib": "catalog:",
"arkenv": "workspace:*",
"arktype": "catalog:",
"next": "catalog:",
"rimraf": "catalog:",
Expand All @@ -26,6 +26,7 @@
"vitest": "catalog:"
},
"peerDependencies": {
"arkenv": "workspace:^",
"arktype": "^2.1.22",
"next": "^13.4.0 || ^14.0.0 || ^15.0.0 || ^16.0.0-0"
},
Expand Down
5 changes: 3 additions & 2 deletions packages/nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"dependencies": {
"@arkenv/build": "workspace:*",
"arkenv": "workspace:*"
"@arkenv/build": "workspace:*"
},
"devDependencies": {
"@nuxt/kit": "^4.4.8",
"@nuxt/schema": "^4.4.8",
"@repo/scope": "workspace:*",
"@repo/types": "workspace:*",
"@size-limit/preset-small-lib": "catalog:",
"arkenv": "workspace:*",
"arktype": "catalog:",
"nuxt": "^4.4.8",
"rimraf": "catalog:",
Expand All @@ -27,6 +27,7 @@
"vitest": "catalog:"
},
"peerDependencies": {
"arkenv": "workspace:^",
"arktype": "^2.1.22",
"nuxt": "^3.0.0 || ^4.0.0-0"
},
Expand Down
5 changes: 2 additions & 3 deletions packages/vite-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@
},
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"dependencies": {
"arkenv": "workspace:*"
},
"devDependencies": {
"@repo/scope": "workspace:*",
"@repo/types": "workspace:*",
"@size-limit/preset-small-lib": "catalog:",
"arkenv": "workspace:*",
"arktype": "catalog:",
"rimraf": "catalog:",
"size-limit": "catalog:",
Expand All @@ -24,6 +22,7 @@
"vitest": "catalog:"
},
"peerDependencies": {
"arkenv": "workspace:^",
"arktype": "^2.1.22",
"vite": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
},
Expand Down
Loading
Loading