Skip to content

Commit a25bdcd

Browse files
committed
feat(client): serve getEnvs over runner IPC
Motivation: Tools need a bulk env read path for APIs like Vite loadEnv that discover an env prefix rather than a single name. Before this change, getEnvs existed on the JS surface but always returned an empty match set. Scope: Add the GetEnvs request/response frame, Rust client support, NAPI getEnvs implementation, server-side env glob matching, invalid-glob error surfacing, and print-only e2e coverage. This PR intentionally does not add getEnvs match sets to cache fingerprints. Verification: - cargo test -p vite_task_server --test integration - UPDATE_SNAPSHOTS=1 cargo test -p vite_task_bin --test e2e_snapshots fetch_envs_reads_match_set -- --ignored - cargo test -p vite_task_bin --test e2e_snapshots fetch_envs_reads_match_set -- --ignored
1 parent ded4204 commit a25bdcd

15 files changed

Lines changed: 218 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Changelog
22

33
- **Changed** Tracked environment values in task cache fingerprints are now stored only as SHA-256 digests, and env-related cache miss details report names without values.
4+
- **Added** Runner-aware `getEnvs` calls now return env values served by the runner for matching env glob patterns ([#449](https://github.com/voidzero-dev/vite-task/pull/449)).
45
- **Fixed** Runner-aware `getEnv` reads now affect task cache fingerprints, so changing a tool-served env value invalidates cached output and names the env var in the miss message ([#454](https://github.com/voidzero-dev/vite-task/pull/454)).
56
- **Added** Runner-aware tools can now opt the current task run out of caching through the new IPC channel; Vite dev server integration uses this automatically ([#441](https://github.com/voidzero-dev/vite-task/pull/441))
67
- **Fixed** Prefix environment assignments like `PATH=... command` now affect executable lookup during task planning, so tools provided only by the prefixed `PATH` can be resolved correctly ([#440](https://github.com/voidzero-dev/vite-task/pull/440))

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/native_str/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ use wincode::{
3737
/// **Not portable across platforms.** The binary representation is platform-specific.
3838
/// Deserializing a `NativeStr` serialized on a different platform leads to unspecified
3939
/// behavior (garbage data), but is not unsafe. Designed for same-platform IPC only.
40-
#[derive(TransparentWrapper, PartialEq, Eq)]
40+
#[derive(TransparentWrapper, PartialEq, Eq, Hash)]
4141
#[repr(transparent)]
4242
pub struct NativeStr {
4343
// On unix, this is the raw bytes of the OsStr.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { getEnvs } from '@voidzero-dev/vite-task-client';
2+
3+
const matches = getEnvs('PROBE_*');
4+
const sorted = Object.entries(matches).sort(([a], [b]) => a.localeCompare(b));
5+
6+
for (const [key, value] of sorted) {
7+
console.log(`${key}=${value}`);
8+
}

crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,30 @@ steps = [
171171
],
172172
], comment = "cache miss: tracked env changed despite input auto-inference being disabled" },
173173
]
174+
175+
[[e2e]]
176+
name = "fetch_envs_reads_match_set"
177+
comment = """
178+
Exercises `getEnvs(pattern)`: the tool asks the runner for every env matching `PROBE_*` and prints the served match set. This verifies the bulk env IPC round trip before match sets are added to cache fingerprints.
179+
"""
180+
ignore = true
181+
steps = [
182+
{ argv = [
183+
"vt",
184+
"run",
185+
"fetch-envs",
186+
], envs = [
187+
[
188+
"PROBE_A",
189+
"a",
190+
],
191+
[
192+
"PROBE_B",
193+
"b",
194+
],
195+
[
196+
"UNRELATED",
197+
"noise",
198+
],
199+
], comment = "runner serves only envs matching PROBE_*" },
200+
]
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# fetch_envs_reads_match_set
2+
3+
Exercises `getEnvs(pattern)`: the tool asks the runner for every env matching `PROBE_*` and prints the served match set. This verifies the bulk env IPC round trip before match sets are added to cache fingerprints.
4+
5+
## `PROBE_A=a PROBE_B=b UNRELATED=noise vt run fetch-envs`
6+
7+
runner serves only envs matching PROBE_*
8+
9+
```
10+
$ node scripts/fetch_envs.mjs
11+
PROBE_A=a
12+
PROBE_B=b
13+
```

crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/vite-task.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
"output": [],
2121
"cache": true
2222
},
23+
"fetch-envs": {
24+
"command": "node scripts/fetch_envs.mjs",
25+
"cache": true
26+
},
2327
"fetch-prefixed-env": {
2428
"command": "PREFIXED_ENV=from-command node scripts/fetch_env.mjs PREFIXED_ENV",
2529
"cache": true

crates/vite_task_client/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ rust-version.workspace = true
88

99
[dependencies]
1010
native_str = { workspace = true }
11+
rustc-hash = { workspace = true }
1112
vite_task_ipc_shared = { workspace = true }
1213
wincode = { workspace = true, features = ["derive"] }
1314

crates/vite_task_client/src/lib.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use std::{
66
};
77

88
use native_str::NativeStr;
9-
use vite_task_ipc_shared::{GetEnvResponse, IPC_ENV_NAME, Request};
9+
use rustc_hash::FxHashMap;
10+
use vite_task_ipc_shared::{GetEnvResponse, GetEnvsResponse, IPC_ENV_NAME, Request};
1011
use wincode::{SchemaRead, config::DefaultConfig};
1112

1213
#[cfg(unix)]
@@ -73,6 +74,30 @@ impl Client {
7374
.map(|env_value| Arc::<OsStr>::from(env_value.to_cow_os_str().as_ref())))
7475
}
7576

77+
/// Requests every env whose name matches `pattern` from the runner. The
78+
/// returned map is keyed by env name with its value.
79+
///
80+
/// # Errors
81+
///
82+
/// Returns an error if the request or response fails, or if the server
83+
/// rejects the pattern as an invalid glob.
84+
// TODO(env-track): A later PR in this stack adds a tracked flag so matched
85+
// env sets can participate in cache fingerprints instead of being IPC-only.
86+
pub fn get_envs(&self, pattern: &str) -> io::Result<FxHashMap<Arc<OsStr>, Arc<OsStr>>> {
87+
self.send(&Request::GetEnvs { pattern })?;
88+
let response: GetEnvsResponse = self.recv()?;
89+
Ok(response
90+
.entries
91+
.into_iter()
92+
.map(|(name, value)| {
93+
(
94+
Arc::<OsStr>::from(name.to_cow_os_str().as_ref()),
95+
Arc::<OsStr>::from(value.to_cow_os_str().as_ref()),
96+
)
97+
})
98+
.collect())
99+
}
100+
76101
fn send(&self, request: &Request<'_>) -> io::Result<()> {
77102
let bytes = wincode::serialize(request)
78103
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;

crates/vite_task_client_napi/src/lib.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,27 @@ impl RunnerClient {
105105
})
106106
}
107107

108-
/// No-op for now: always returns an empty match-set — see
109-
/// [`Self::get_env`].
110108
#[napi]
111109
pub fn get_envs(
112110
&self,
113-
_pattern: String,
111+
pattern: String,
114112
_options: Option<GetEnvOptions>,
115113
) -> Result<HashMap<String, String>> {
116-
Ok(HashMap::new())
114+
let matches =
115+
self.client.get_envs(&pattern).map_err(|err| err_string(vite_str::format!("{err}")))?;
116+
let mut result = HashMap::with_capacity(matches.len());
117+
for (name, value) in matches {
118+
let name = name.to_str().ok_or_else(|| {
119+
err_string(vite_str::format!(
120+
"env name matched by pattern {pattern} is not valid UTF-8"
121+
))
122+
})?;
123+
let value = value.to_str().ok_or_else(|| {
124+
err_string(vite_str::format!("env value for {name} is not valid UTF-8"))
125+
})?;
126+
result.insert(name.to_owned(), value.to_owned());
127+
}
128+
Ok(result)
117129
}
118130
}
119131

0 commit comments

Comments
 (0)