Skip to content

feat: XDG directory layout via VpDirs - #2346

Open
forehalo wants to merge 6 commits into
voidzero-dev:mainfrom
forehalo:feat/dirs-path-resolution
Open

feat: XDG directory layout via VpDirs#2346
forehalo wants to merge 6 commits into
voidzero-dev:mainfrom
forehalo:feat/dirs-path-resolution

Conversation

@forehalo

@forehalo forehalo commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Close #827

Summary

Centralize on-disk paths in vp_shared::VpDirs and default fresh installs to the split XDG/platform layout. Existing default installs under ~/.vite-plus remain on the legacy monolithic root for now (grandfathered via existence-gated resolution). Automatic layout migration is out of scope here and tracked as follow-up work.

Resolution (high level)

Ordered sources (per category): deprecated VP_HOME (custom full-root pin) → existing ~/.vite-plus (exist-gated legacy mapping) → VP_BIN_DIR / VP_DATA_DIR / VP_CACHE_DIRXDG_* → platform defaults.

Category Split default (Unix) Legacy (~/.vite-plus)
bin ~/.local/bin <root>/bin
data ~/.local/share/vite-plus <root>
cache ~/.cache/vite-plus <root>/cache
config ~/.config/vite-plus <root>
state ~/.local/state/vite-plus <root>

Windows split uses %LOCALAPPDATA%\vite-plus\{bin,data,cache,state} and %APPDATA%\vite-plus for config. Relative VP_* / XDG_* values are treated as unset.

Commits (review order)

  1. docs(rfc): document split directory layout via VpDirs

    • Adds rfcs/directory-layout.md describing the resolution chain, grandfathering, installer alignment, and follow-ups (VP_HOME cleanup, migrate-on-upgrade).
    • Historical RFCs (e.g. env-command.md) are left unchanged.
  2. feat(shared): introduce VpDirs with strategy-gated path resolution

    • Replaces get_vp_home / home.rs with VpDirs + dirs/resolution.rs strategy chain.
    • Category roots only; first-level data subdirs (current, js_runtime, …) stay as joins on the owning feature.
    • Wires EnvConfig so tests isolate layout via with_vars / scoped without process-env leaks.
    • Updates AGENTS.md pointer to the central path API.
  3. refactor(cli): route on-disk paths through VpDirs

    • Migrates global CLI, shims, js_runtime, package manager, implode, env setup/doctor, and related helpers onto EnvConfig::get().dirs.
    • Env setup scripts re-export captured layout overrides (dir_envs); implode deletes deduped {data, cache, config, state} and never rm -rfs a shared bin dir.
    • Snapshot runner and fixtures still pin install roots explicitly (layout cleanup is chore: remove remaining VP_HOME usage (especially snapshot tests) #2371).
  4. feat(install): default installers to the split XDG layout

    • install.sh / install.ps1 / vp-setup / trampoline / install-global-cli install into the roots EnvConfig already resolved. Fresh install → split; existing ~/.vite-plus or --install-dir → monolithic root.
    • Directory env vars stay owned by EnvConfig. vp-setup does not read VP_HOME / VP_*_DIR / XDG_* itself. A local vp binary reports <DATA> / <BIN> / <CONFIG> via VP_DUMP_DIRS=1 so scripts and install-global-cli do not re-implement the chain.
    • Unix vp shims target <DATA>/current/bin/vp (not a version dir) after env setup --refresh. The Windows trampoline finds ../current or ../data/current from disk layout only.
    • Installer env docs and standalone-install CI: published-CLI jobs stay VP_HOME-pinned; a local-build job covers fresh split + grandfather.

User impact

Install Impact
Existing ~/.vite-plus Unchanged path (grandfathered until migrate follow-up)
Custom VP_HOME Still works (deprecated pin)
Fresh Split layout; typically only ~/.local/bin needs to be on PATH

Follow-up

Test plan

  • cargo check / unit tests for dirs resolution and layout helpers
  • Snapshot fixtures adapted for layout isolation (pin VP_HOME / disable shared seed where fakes are used)
  • Installer unification (single install.sh / install.ps1)
  • Formatting for RFC + install-global-cli
  • vp-setup / trampoline / Unix shim unit tests for fresh split vs grandfather
  • CI: test-standalone-install (released VP_HOME-pinned + local-build split/grandfather jobs)
  • Manual: fresh install lands on split paths; existing ~/.vite-plus still works without moving

@netlify

netlify Bot commented Aug 5, 2026

Copy link
Copy Markdown

Deploy Preview for viteplus-preview ready!

Name Link
🔨 Latest commit 1de13b3
🔍 Latest deploy log https://app.netlify.com/projects/viteplus-preview/deploys/6a7eb23ad4376823d6f43852
😎 Deploy Preview https://deploy-preview-2346--viteplus-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@forehalo
forehalo force-pushed the feat/dirs-path-resolution branch 4 times, most recently from b8069a5 to 2f8be03 Compare August 5, 2026 17:13
@forehalo forehalo changed the title feat: introduce Dirs for unified XDG-aware path resolution feat: XDG-compliant directory layout via unified Dirs resolution Aug 6, 2026
@forehalo
forehalo force-pushed the feat/dirs-path-resolution branch 2 times, most recently from 08b7589 to 4e2abdf Compare August 6, 2026 17:25
@forehalo forehalo changed the title feat: XDG-compliant directory layout via unified Dirs resolution feat: XDG directory layout via VpDirs Aug 6, 2026
@fengmk2

fengmk2 commented Aug 7, 2026

Copy link
Copy Markdown
Member

@liangmiQwQ Your refactor needs to wait until this merge is completed first, as the changes are expected to be quite significant.

@socket-security

socket-security Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedcargo/​temp-env@​0.3.68710093100100

View full report

@forehalo
forehalo force-pushed the feat/dirs-path-resolution branch 3 times, most recently from 6b26e69 to ca9dcca Compare August 7, 2026 12:17
@fengmk2 fengmk2 self-assigned this Aug 7, 2026
@fengmk2

fengmk2 commented Aug 7, 2026

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ca9dcca689

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/vp_trampoline/src/main.rs Outdated
Comment thread packages/cli/install.ps1 Outdated
Comment thread crates/vp_global_cli/src/commands/env/setup.rs Outdated
Comment thread packages/cli/src/config/hooks.ts Outdated
Comment thread crates/vp_shared/src/dirs/resolution.rs Outdated
Comment thread crates/vp_global_cli/src/commands/implode.rs Outdated
Comment thread crates/vp_shared/src/dirs/resolution.rs Outdated
@liangmiQwQ

liangmiQwQ commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

IMO, I would be a little bit curious about the benefit for this move. The current VP_HOME layout has been working well, the similar single-root approaches are also used by tools like Cargo, Bun, and Volta. Moving to a new layout is not a low-cost change: it affects existing installations, upgrade paths, user workflows, and debugging. For a released tool, I think we should carefully evaluate the trade-offs.

For example, I help maintain the Vite+'s global package installations in several, I'm concerned that using ~/.local/bin may introduce heavier binary conflicts and overlapping issues. I'm not sure if ~/.local/bin, a public bin location, should be managed (even partially) by Vite+.

I'm not against this direction, I just want to understand the motivation and the problem it solves. If it does help Vite+ improve, then now is really a good time to implement it (before RC).

Comment thread rfcs/directory-layout.md Outdated
@fengmk2

fengmk2 commented Aug 10, 2026

Copy link
Copy Markdown
Member

@liangmiQwQ Following the XDG directory convention will allow Vite+ to be more widely accepted, and many mature developer tools follow this convention, such as Claude Code and uv astral-sh/uv#8420

@forehalo
forehalo force-pushed the feat/dirs-path-resolution branch 2 times, most recently from ad9d7dd to 5cc3b6c Compare August 10, 2026 06:07
@fengmk2 fengmk2 added the preview-build Publish this PR's commits to the registry bridge as preview builds label Aug 10, 2026
@forehalo
forehalo marked this pull request as draft August 10, 2026 06:33
fengmk2 added a commit that referenced this pull request Aug 10, 2026
Production viteplus.dev deploys on every push to main today. Docs for
unreleased features go live at merge time, and install-script rewrites
(for example #2346) would be served behind https://vite.plus before a
release with matching binaries exists.

This PR adds `rfcs/deploy-docs-on-release.md` and implements it:

- New `.github/actions/deploy-docs` composite action holds the shared
build and deploy steps (setup-vp, Vite Task cache, `vp run build`, `vpx
void deploy`), with `void-project` / `void-token` inputs and optional
cache-key inputs.
- `deploy-docs.yml` loses its push trigger and becomes the manual
(`workflow_dispatch`) production deploy, the escape hatch for urgent
updates.
- `release.yml` gains a `deploy-docs` job that deploys production from
the release commit after the Release job publishes npm and the GitHub
release. Prereleases skip it. `discord-notify` now waits for it
(success, or the prerelease skip) before announcing.
- `deploy-docs-main.yml` (new) takes over the push trigger and deploys
main to main.viteplus.dev (the `viteplus-main` void.app project), a
standing preview of the latest docs on main.
- `deploy-docs-preview.yml` keeps its trigger, staging target, and PR
comment step, and now runs the same composite with per-PR cache keys.

Setup needed before merge, outside this repo:

- Create the `viteplus-main` project on the void platform (same
`VOID_TOKEN`).
- Add DNS CNAME `main.viteplus.dev` -> `viteplus-main.void.app` and
attach the custom domain to the project.

Not included: `noindex` for the preview sites, which needs theme or
platform support.
@forehalo
forehalo force-pushed the feat/dirs-path-resolution branch 4 times, most recently from 80e2dc7 to ff9fe11 Compare August 12, 2026 16:52
Capture the resolution design, grandfathering of existing ~/.vite-plus
installs, installer alignment, and follow-ups for VP_HOME cleanup and
migrate-on-upgrade.
Replace get_vp_home / the monolithic home module with VpDirs: category
roots (bin, data, cache, config, state) come from an ordered resolution
chain in dirs/resolution.rs (legacy Exist-gated grandfathering, VP_* and
XDG Set overrides, then platform defaults), and first-level data subdirs
(current, js_runtime, package_manager, packages, bins) are pure joins.
Files and deeper trees stay with their features.

Wire EnvConfig so test_guard/for_test_with_home isolate install roots
without process-env leaks. Groundwork for voidzero-dev#827.
Replace every get_vp_home()-era dependency with EnvConfig::get().dirs.
Business directory helpers stay in their owning modules but are
reimplemented on the category roots, so no call site constructs install
paths ad hoc.

- env setup scripts re-export the captured layout overrides
  (EnvConfig::env_exports) instead of hardcoding a VP_HOME line;
  vp-use.cmd does the same on Windows
- env doctor reports all five category roots; bin/config are hard
  errors when missing, data/cache/state are reported as lazily created
- implode deletes the deduped {data, cache, config, state} roots and
  removes only vp-owned files from the bin directory, which may be a
  shared ~/.local/bin
- upgrade-check cache moves to the state root; shim resolve cache
  lives under the cache root
@forehalo
forehalo force-pushed the feat/dirs-path-resolution branch from ff9fe11 to 4c84ea3 Compare August 13, 2026 02:20
Fresh installs write the split platform roots; an existing ~/.vite-plus
or --install-dir stays on the monolithic tree. Directory env vars stay
owned by EnvConfig: vp-setup reads only EnvConfig::get().dirs, and a
local vp binary reports those roots via VP_DUMP_DIRS so install.sh /
install.ps1 / install-global-cli do not re-implement the chain.

- Unix shims target <DATA>/current/bin/vp after env setup --refresh
- Windows trampoline finds ../current or ../data/current without
  reading VP_HOME / VP_*_DIR
- Standalone-install CI pins VP_HOME for published pre-split CLIs and
  adds a local-build job for fresh split + grandfather
CI still looks for $HOME/.vite-plus/bin/vp after bootstrap-cli:ci.
Create that directory before EnvConfig resolves so the existing-install
probe wins, without setting VP_HOME.

Also clone rolldown/vite before the local-build layout job so
`cargo build -p vp_global_cli` can load the workspace.
The test fetches the Node version index after scanning the cache. Without
an EnvConfig::with_vars_async pin it can observe a concurrent test's
VP_NODE_DIST_MIRROR=https://mirror.example/node and fail DNS lookup.
@forehalo
forehalo marked this pull request as ready for review August 13, 2026 05:20
@fengmk2 fengmk2 added test: e2e Auto run e2e tests test: install-e2e run vite install e2e test test: create-e2e Run `vp create` e2e tests test: sfw preview-build Publish this PR's commits to the registry bridge as preview builds and removed preview-build Publish this PR's commits to the registry bridge as preview builds labels Aug 14, 2026
@fengmk2

fengmk2 commented Aug 14, 2026

Copy link
Copy Markdown
Member

@codex review

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Registry bridge build (1de13b3)

Warning

This build is from the fork forehalo/vite-plus and has not been reviewed.
Installing it runs that code on your machine. Build log

This commit build is published to the registry bridge, which serves these as ordinary npm versions (every other package proxies to npmjs):

Package Version
vite-plus 0.0.0-commit.1de13b3edde82237625ed88078fc17a6a8e20ed5
@voidzero-dev/vite-plus-core 0.0.0-commit.1de13b3edde82237625ed88078fc17a6a8e20ed5

Install the Vite+ CLI built from this commit, then migrate a project:

# macOS / Linux
curl -fsSL https://deploy-preview-2346--viteplus-preview.netlify.app/install.sh | VP_PR_VERSION=2346 bash
# Windows (PowerShell)
$env:VP_PR_VERSION="2346"; irm https://deploy-preview-2346--viteplus-preview.netlify.app/install.ps1 | iex

After installing, upgrade the current project's vite-plus to this test build with:

vp migrate

Or point your package manager at the bridge registry https://registry-bridge.viteplus.dev/:

Package manager Registry config
npm / pnpm / Bun .npmrc: registry=https://registry-bridge.viteplus.dev/
Yarn (v2+) .yarnrc.yml: npmRegistryServer: "https://registry-bridge.viteplus.dev/"

Then pin the build (vite aliases to vite-plus-core; pnpm can use a catalog, npm an overrides entry):

{
  "devDependencies": {
    "vite-plus": "0.0.0-commit.1de13b3edde82237625ed88078fc17a6a8e20ed5",
    "vite": "npm:@voidzero-dev/vite-plus-core@0.0.0-commit.1de13b3edde82237625ed88078fc17a6a8e20ed5"
  }
}

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1de13b3edd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +114 to +115
for name in names {
match std::fs::remove_file(dirs.bin.join(&name).as_path()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Verify shim ownership before deleting shared-bin entries

With the fresh Unix layout, <BIN> is the shared ~/.local/bin, but remove_shim_files unconditionally deletes every SHIM_TOOLS name. If the user installed with VP_NODE_MANAGER=no, Vite+ created only vp; a later vp implode will still remove unrelated pre-existing node, npm, npx, corepack, vpx, or vpr files from that directory. Only remove entries whose target/metadata confirms they belong to Vite+.

AGENTS.md reference: AGENTS.md:L47-L47

Useful? React with 👍 / 👎.

Comment on lines +105 to +108
[env_vars::VP_BIN_DIR, env_vars::VP_DATA_DIR, env_vars::VP_CACHE_DIR]
.into_iter()
.filter_map(|name| Some((name, present(name)?)))
.collect()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve XDG roots in generated shell contexts

When vp env setup runs with XDG_DATA_HOME, XDG_BIN_HOME, or another XDG root set, those variables determine VpDirs but are omitted from dir_envs. The generated env file therefore preserves only the resolved PATH; if it is sourced later without the same XDG variables, the executable reached through that PATH resolves data, config, cache, and state back to the platform defaults, so upgrades, runtimes, and configuration diverge from the installation that created the file. Capture the effective XDG inputs alongside the VP_* overrides.

AGENTS.md reference: AGENTS.md:L47-L47

Useful? React with 👍 / 👎.

Comment thread packages/cli/install.ps1
exit /b %ERRORLEVEL%
"@
Set-Content -Path "$InstallDir\bin\vp.cmd" -Value $wrapperContent -NoNewline
Set-Content -Path (Join-Path $ShimDir "vp.cmd") -Value $wrapperContent -NoNewline

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Point legacy Windows wrappers at the split data directory

When an explicitly selected pre-trampoline release lacks vp-shim.exe, this branch now writes the legacy wrappers into the split $ShimDir, but their contents still treat the parent of that directory as VP_HOME and invoke <parent>\current\bin\vp.exe. On a fresh default Windows install the payload is actually under <parent>\data\current, so both vp.cmd and the Git Bash wrapper are broken. Render $InstallDir into these wrappers and pin VP_HOME to that data root for the older CLI.

AGENTS.md reference: AGENTS.md:L47-L47

Useful? React with 👍 / 👎.

Comment on lines 92 to +94
Some(latest) => {
let new_cache = UpgradeCheckCache { latest, checked_at: now, prompted_at };
write_cache(&install_dir, &new_cache);
write_cache(state_dir, &new_cache);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Create the state directory before caching update checks

On a fresh split install, <STATE> does not exist after installer setup, and write_cache writes directly without creating its parent. Consequently these calls silently fail, read_cache returns None on every subsequent eligible invocation, and each command starts another registry request and can incur the 500 ms wait in main.rs instead of honoring the intended 24-hour backoff. Create the state directory before writing the cache.

AGENTS.md reference: AGENTS.md:L47-L47

Useful? React with 👍 / 👎.

Comment on lines +147 to +149
fn resolver(home: &AbsolutePath) -> SingleRoot {
let root = home.join(VP_HOME_DIR_NAME);
SingleRoot { root: root.as_path().is_dir().then_some(root) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Prevent caches from triggering legacy grandfathering

After a fresh split install, the organization-template path in packages/cli/src/create/org-tarball.ts still defaults its cache to ~/.vite-plus/tmp/create-org and creates that directory recursively. On the next vp process, this existence-only gate mistakes the cache for a legacy installation and switches every category away from the installed split roots to the incomplete ~/.vite-plus tree. Fresh evidence beyond the earlier regular-file artifact is the production ensureOrgPackageExtracted mkdir path; route that cache through <CACHE> or require an actual legacy-install marker before grandfathering.

AGENTS.md reference: AGENTS.md:L47-L47

Useful? React with 👍 / 👎.

@fengmk2 fengmk2 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the PR preview on Windows using VP_PR_VERSION=2346 / 0.0.0-commit.1de13b3edde82237625ed88078fc17a6a8e20ed5.

Default fresh split install and existing ~\.vite-plus grandfathering both installed successfully and vp --version ran from an empty cwd. I found two Windows-specific issues below around non-default split roots and installer/runtime path alignment.

if monolithic.exists() {
return (monolithic, Some(parent.to_path_buf()));
}
let split = parent.join("data").join("current").join("bin").join("vp.exe");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Support independent VP_BIN_DIR / VP_DATA_DIR roots in the trampoline

Windows installs with separate category roots break here. install.ps1 accepts independent absolute VP_BIN_DIR and VP_DATA_DIR, copies vp-shim.exe to $ShimDir\vp.exe, and writes env files containing those overrides. The trampoline only probes paths derived from the shim parent (<bin>\..\current or <bin>\..\data\current) and exits before the child vp.exe can use the recorded env.

Repro on the preview build:

  • VP_BIN_DIR=<tmp>\custom-bin
  • VP_DATA_DIR=<tmp>\custom-data
  • VP_CACHE_DIR=<tmp>\custom-cache
  • install with VP_PR_VERSION=2346, VP_NODE_MANAGER=no
  • run <tmp>\custom-bin\vp.exe --version

Observed: exit 1 with vite-plus: failed to execute <tmp>\current\bin\vp.exe.

Expected: execute <tmp>\custom-data\current\bin\vp.exe, or reject independent bin/data roots on Windows instead of producing a broken shim.

Comment thread packages/cli/install.ps1
}
}

$localApp = if (-not [string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Keep Windows app-data resolution aligned with EnvConfig

The installer reads process %LOCALAPPDATA% / %APPDATA% here, but the Rust resolver uses Windows known folders through directories::BaseDirs. Those can disagree. In the preview verification, I redirected LOCALAPPDATA and APPDATA into a throwaway profile; the installer wrote the split tree there, but VP_DUMP_DIRS=1 from the installed binary resolved back under %USERPROFILE%\AppData\Local / Roaming.

That leaves the installer-created shim/config under one root while subsequent vp processes manage another. Either the installer and EnvConfig should use the same Windows source, or the installer should avoid honoring app-data env overrides that the runtime will not honor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview-build Publish this PR's commits to the registry bridge as preview builds test: create-e2e Run `vp create` e2e tests test: e2e Auto run e2e tests test: install-e2e run vite install e2e test test: sfw

Projects

None yet

Development

Successfully merging this pull request may close these issues.

use xdg config

3 participants