feat: XDG directory layout via VpDirs - #2346
Conversation
✅ Deploy Preview for viteplus-preview ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
b8069a5 to
2f8be03
Compare
08b7589 to
4e2abdf
Compare
|
@liangmiQwQ Your refactor needs to wait until this merge is completed first, as the changes are expected to be quite significant. |
78a23b6 to
a433ed4
Compare
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
6b26e69 to
ca9dcca
Compare
|
@codex review |
There was a problem hiding this comment.
💡 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".
|
IMO, I would be a little bit curious about the benefit for this move. The current For example, I help maintain the Vite+'s global package installations in several, I'm concerned that using 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). |
|
@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 |
ad9d7dd to
5cc3b6c
Compare
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.
80e2dc7 to
ff9fe11
Compare
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
ff9fe11 to
4c84ea3
Compare
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.
|
@codex review |
Registry bridge build (
|
| 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 | iexAfter installing, upgrade the current project's vite-plus to this test build with:
vp migrateOr 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"
}
}There was a problem hiding this comment.
💡 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".
| for name in names { | ||
| match std::fs::remove_file(dirs.bin.join(&name).as_path()) { |
There was a problem hiding this comment.
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 👍 / 👎.
| [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() |
There was a problem hiding this comment.
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 👍 / 👎.
| exit /b %ERRORLEVEL% | ||
| "@ | ||
| Set-Content -Path "$InstallDir\bin\vp.cmd" -Value $wrapperContent -NoNewline | ||
| Set-Content -Path (Join-Path $ShimDir "vp.cmd") -Value $wrapperContent -NoNewline |
There was a problem hiding this comment.
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 👍 / 👎.
| Some(latest) => { | ||
| let new_cache = UpgradeCheckCache { latest, checked_at: now, prompted_at }; | ||
| write_cache(&install_dir, &new_cache); | ||
| write_cache(state_dir, &new_cache); |
There was a problem hiding this comment.
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 👍 / 👎.
| fn resolver(home: &AbsolutePath) -> SingleRoot { | ||
| let root = home.join(VP_HOME_DIR_NAME); | ||
| SingleRoot { root: root.as_path().is_dir().then_some(root) } |
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
[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-binVP_DATA_DIR=<tmp>\custom-dataVP_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.
| } | ||
| } | ||
|
|
||
| $localApp = if (-not [string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) { |
There was a problem hiding this comment.
[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.
Close #827
Summary
Centralize on-disk paths in
vp_shared::VpDirsand default fresh installs to the split XDG/platform layout. Existing default installs under~/.vite-plusremain 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_DIR→XDG_*→ platform defaults.~/.vite-plus)~/.local/bin<root>/bin~/.local/share/vite-plus<root>~/.cache/vite-plus<root>/cache~/.config/vite-plus<root>~/.local/state/vite-plus<root>Windows split uses
%LOCALAPPDATA%\vite-plus\{bin,data,cache,state}and%APPDATA%\vite-plusfor config. RelativeVP_*/XDG_*values are treated as unset.Commits (review order)
docs(rfc): document split directory layout via VpDirsrfcs/directory-layout.mddescribing the resolution chain, grandfathering, installer alignment, and follow-ups (VP_HOMEcleanup, migrate-on-upgrade).env-command.md) are left unchanged.feat(shared): introduce VpDirs with strategy-gated path resolutionget_vp_home/home.rswithVpDirs+dirs/resolution.rsstrategy chain.current,js_runtime, …) stay as joins on the owning feature.EnvConfigso tests isolate layout viawith_vars/scopedwithout process-env leaks.AGENTS.mdpointer to the central path API.refactor(cli): route on-disk paths through VpDirsjs_runtime, package manager, implode, env setup/doctor, and related helpers ontoEnvConfig::get().dirs.dir_envs); implode deletes deduped{data, cache, config, state}and neverrm -rfs a shared bin dir.feat(install): default installers to the split XDG layoutinstall.sh/install.ps1/vp-setup/ trampoline /install-global-cliinstall into the rootsEnvConfigalready resolved. Fresh install → split; existing~/.vite-plusor--install-dir→ monolithic root.EnvConfig.vp-setupdoes not readVP_HOME/VP_*_DIR/XDG_*itself. A localvpbinary reports<DATA>/<BIN>/<CONFIG>viaVP_DUMP_DIRS=1so scripts andinstall-global-clido not re-implement the chain.vpshims target<DATA>/current/bin/vp(not a version dir) afterenv setup --refresh. The Windows trampoline finds../currentor../data/currentfrom disk layout only.VP_HOME-pinned; a local-build job covers fresh split + grandfather.User impact
~/.vite-plusVP_HOME~/.local/binneeds to be on PATHFollow-up
VP_HOMEusage in the repo (especially PTY snapshot tests); preferVP_*_DIR/ XDGvp upgrade, migrate default~/.vite-pluscontents into splitVP_*_DIR/XDG_*/ platform dirs and remove the legacy rootTest plan
cargo check/ unit tests fordirsresolution and layout helpersVP_HOME/ disable shared seed where fakes are used)install.sh/install.ps1)install-global-clivp-setup/ trampoline / Unix shim unit tests for fresh split vs grandfathertest-standalone-install(releasedVP_HOME-pinned + local-build split/grandfather jobs)~/.vite-plusstill works without moving