Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

- **Added** `dependsOn` can now run the specified task in each direct workspace package listed in `dependencies`, `devDependencies`, or `peerDependencies`; for example, `{ "task": "build", "from": ["dependencies", "devDependencies"] }` runs `build` in each direct dependency and dev dependency ([#467](https://github.com/voidzero-dev/vite-task/pull/467), [#469](https://github.com/voidzero-dev/vite-task/pull/469)).
- **Added** First-party support for caching `vite build` with zero cache config, giving Vite projects correct cache hits out of the box ([vitejs/vite#22453](https://github.com/vitejs/vite/pull/22453)).
- **Added** [`@voidzero-dev/vite-task-client`](https://npmx.dev/package/@voidzero-dev/vite-task-client), allowing tools to report cache information to Vite Task at runtime so users do not need to configure it manually ([#441](https://github.com/voidzero-dev/vite-task/pull/441), [#454](https://github.com/voidzero-dev/vite-task/pull/454), [#449](https://github.com/voidzero-dev/vite-task/pull/449), [#450](https://github.com/voidzero-dev/vite-task/pull/450), [#458](https://github.com/voidzero-dev/vite-task/pull/458), [#431](https://github.com/voidzero-dev/vite-task/pull/431), [#459](https://github.com/voidzero-dev/vite-task/pull/459)).
- **Added** [`@voidzero-dev/vite-task-client`](https://npmx.dev/package/@voidzero-dev/vite-task-client), allowing tools to report cache information to Vite Task at runtime so users do not need to configure it manually, including literal-prefix env lookups with `getEnvs({ prefix: "..." })` ([#441](https://github.com/voidzero-dev/vite-task/pull/441), [#454](https://github.com/voidzero-dev/vite-task/pull/454), [#449](https://github.com/voidzero-dev/vite-task/pull/449), [#450](https://github.com/voidzero-dev/vite-task/pull/450), [#458](https://github.com/voidzero-dev/vite-task/pull/458), [#431](https://github.com/voidzero-dev/vite-task/pull/431), [#459](https://github.com/voidzero-dev/vite-task/pull/459), [#472](https://github.com/voidzero-dev/vite-task/pull/472)).
- **Changed** Cached tasks now restore automatically tracked output files by default; use `output: []` to disable restoration ([#460](https://github.com/voidzero-dev/vite-task/pull/460), [#461](https://github.com/voidzero-dev/vite-task/pull/461)).
- **Changed** Environment values in task cache fingerprints are now stored only as SHA-256 digests, and env-related cache miss details report names without values ([#455](https://github.com/voidzero-dev/vite-task/pull/455)).
- **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))
Expand Down
3 changes: 3 additions & 0 deletions crates/vite_task/src/session/execute/cache_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,9 @@ fn collect_tracked_env_queries(reports: &Reports) -> anyhow::Result<TrackedEnvQu
vite_task_server::EnvQuery::Glob(pattern) => {
TrackedEnvQuery::Glob(Str::from(pattern.as_ref()))
}
vite_task_server::EnvQuery::Prefix(prefix) => {
TrackedEnvQuery::Prefix(Str::from(prefix.as_ref()))
}
};
tracked_env_queries.insert(query, matches);
}
Expand Down
58 changes: 55 additions & 3 deletions crates/vite_task/src/session/execute/fingerprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use crate::{
)]
pub enum TrackedEnvQuery {
Glob(Str),
Prefix(Str),
Comment thread
wan9chi marked this conversation as resolved.
}

/// Path read access info
Expand Down Expand Up @@ -230,9 +231,15 @@ fn match_env_query(
query: &TrackedEnvQuery,
envs: &FxHashMap<Arc<OsStr>, Arc<OsStr>>,
) -> anyhow::Result<EnvQueryValidation> {
let TrackedEnvQuery::Glob(pattern) = query;
let glob = vite_glob::env::EnvGlob::new(pattern.as_str())?;
Ok(collect_matching_envs(envs, |name| glob.is_match(name)))
Ok(match query {
TrackedEnvQuery::Glob(pattern) => {
let glob = vite_glob::env::EnvGlob::new(pattern.as_str())?;
collect_matching_envs(envs, |name| glob.is_match(name))
}
TrackedEnvQuery::Prefix(prefix) => {
collect_matching_envs(envs, |name| env_name_starts_with(name, prefix.as_str()))
}
})
}

fn collect_matching_envs(
Expand Down Expand Up @@ -262,6 +269,25 @@ enum EnvQueryValidation {
NonUtf8Value(EnvMismatch),
}

#[cfg(not(windows))]
fn env_name_starts_with(name: &str, prefix: &str) -> bool {
name.starts_with(prefix)
}

#[cfg(windows)]
fn env_name_starts_with(name: &str, prefix: &str) -> bool {
let mut name_chars = name.chars();
for prefix_char in prefix.chars() {
let Some(name_char) = name_chars.next() else {
return false;
};
if !name_char.eq_ignore_ascii_case(&prefix_char) {
return false;
}
}
true
}

/// Find the first deterministic difference between stored and current env
/// glob match-sets.
fn first_env_glob_mismatch(
Expand Down Expand Up @@ -557,6 +583,32 @@ mod tests {
}
}

#[test]
fn validate_tracked_env_prefix_treats_star_literally() {
let mut tracked_env_queries = BTreeMap::new();
let mut stored_matches = BTreeMap::new();
stored_matches.insert(Str::from("PROBE_*A"), EnvValueHash::new("literal"));
tracked_env_queries.insert(TrackedEnvQuery::Prefix(Str::from("PROBE_*")), stored_matches);
let fingerprint =
PostRunFingerprint { tracked_env_queries, ..PostRunFingerprint::default() };

let mut unfiltered_envs = FxHashMap::default();
unfiltered_envs.insert(
Arc::<OsStr>::from(OsStr::new("PROBE_*A")),
Arc::<OsStr>::from(OsStr::new("literal")),
);
unfiltered_envs.insert(
Arc::<OsStr>::from(OsStr::new("PROBE_XA")),
Arc::<OsStr>::from(OsStr::new("wildcard if interpreted as glob")),
);

let workspace_root = vite_path::current_dir().expect("cwd");
let mismatch =
fingerprint.validate(&workspace_root, &unfiltered_envs).expect("validation succeeds");

assert!(mismatch.is_none());
}

#[test]
fn validate_ignores_non_utf8_tracked_env_glob_names() {
let mut tracked_env_queries = BTreeMap::new();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { getEnvs } from '@voidzero-dev/vite-task-client';

const tracked = process.argv[2] === '--untracked' ? false : true;
const matches = getEnvs('PROBE_*', { tracked });
const args = process.argv.slice(2);
const tracked = !args.includes('--untracked');
const prefixIndex = args.indexOf('--prefix');
const query = prefixIndex === -1 ? 'PROBE_*' : { prefix: args[prefixIndex + 1] ?? 'PROBE_' };
const matches = getEnvs(query, { tracked });
const sorted = Object.entries(matches).sort(([a], [b]) => a.localeCompare(b));

for (const [key, value] of sorted) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,64 @@ steps = [
], comment = "runner serves only envs matching PROBE_*" },
]

[[e2e]]
name = "fetch_envs_prefix_reads_match_set"
comment = """
Exercises `getEnvs({ prefix: "PROBE_" })`: the tool asks the runner for every env whose name starts with `PROBE_` and prints the served match set.
"""
ignore = true
steps = [
{ argv = [
"vt",
"run",
"fetch-envs-prefix",
], envs = [
[
"PROBE_A",
"a",
],
[
"PROBE_B",
"b",
],
[
"PROBEX",
"not-a-prefix-match",
],
[
"UNRELATED",
"noise",
],
], comment = "runner serves only envs with the literal PROBE_ prefix" },
]

[[e2e]]
name = "fetch_envs_prefix_treats_star_literally"
comment = """
Exercises `getEnvs({ prefix: "PROBE_*" })`: `*` is part of the prefix string, not a glob wildcard.
"""
ignore = true
steps = [
{ argv = [
"vt",
"run",
"fetch-envs-star-prefix",
], envs = [
[
"PROBE_*A",
"literal",
],
[
"PROBE_XA",
"wildcard-if-glob",
],
[
"PROBE_A",
"also-wildcard-if-glob",
],
], comment = "runner serves only envs whose name starts with literal PROBE_*" },
]

[[e2e]]
name = "fetch_envs_tracks_glob_match_set"
comment = """
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# fetch_envs_prefix_reads_match_set

Exercises `getEnvs({ prefix: "PROBE_" })`: the tool asks the runner for every env whose name starts with `PROBE_` and prints the served match set.

## `PROBE_A=a PROBE_B=b PROBEX=not-a-prefix-match UNRELATED=noise vt run fetch-envs-prefix`

runner serves only envs with the literal PROBE_ prefix

```
$ node scripts/fetch_envs.mjs --prefix
PROBE_A=a
PROBE_B=b
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# fetch_envs_prefix_treats_star_literally

Exercises `getEnvs({ prefix: "PROBE_*" })`: `*` is part of the prefix string, not a glob wildcard.

## `PROBE_*A=literal PROBE_XA=wildcard-if-glob PROBE_A=also-wildcard-if-glob vt run fetch-envs-star-prefix`

runner serves only envs whose name starts with literal PROBE_*

```
$ node scripts/fetch_envs.mjs --prefix PROBE_*
PROBE_*A=literal
```
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@
"command": "node scripts/fetch_envs.mjs",
"cache": true
},
"fetch-envs-prefix": {
"command": "node scripts/fetch_envs.mjs --prefix",
"cache": true
},
"fetch-envs-star-prefix": {
"command": "node scripts/fetch_envs.mjs --prefix PROBE_*",
"cache": true
},
"fetch-env-untracked": {
"command": "node scripts/fetch_env.mjs --untracked PROBE_ENV",
"cache": true
Expand Down
20 changes: 4 additions & 16 deletions crates/vite_task_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,7 @@ pub struct Client {
#[derive(Debug, Clone, Copy)]
pub enum GetEnvsQuery<'a> {
Glob(&'a str),
}

impl<'a> GetEnvsQuery<'a> {
#[must_use]
pub const fn glob(pattern: &'a str) -> Self {
Self::Glob(pattern)
}
}

impl<'a> From<&'a str> for GetEnvsQuery<'a> {
fn from(pattern: &'a str) -> Self {
Self::Glob(pattern)
}
Prefix(&'a str),
}

impl Client {
Expand Down Expand Up @@ -128,14 +116,14 @@ impl Client {
///
/// Returns an error if the request or response fails, or if the server
/// rejects a glob query as an invalid glob.
pub fn get_envs<'a>(
pub fn get_envs(
&self,
query: impl Into<GetEnvsQuery<'a>>,
query: GetEnvsQuery<'_>,
tracked: bool,
) -> io::Result<FxHashMap<Arc<OsStr>, Arc<OsStr>>> {
let query = query.into();
let query = match query {
GetEnvsQuery::Glob(pattern) => IpcEnvQuery::Glob(pattern),
GetEnvsQuery::Prefix(prefix) => IpcEnvQuery::Prefix(prefix),
};
self.send(&Request::GetEnvs { query, tracked })?;
let response: GetEnvsResponse = self.recv()?;
Expand Down
28 changes: 18 additions & 10 deletions crates/vite_task_client_napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
)]
use std::{collections::HashMap, ffi::OsStr};

use napi::{Error, Result};
use napi::{Either, Error, Result};
use napi_derive::napi;
use vite_task_client::Client;
use vite_task_client::{Client, GetEnvsQuery};

/// Options for [`RunnerClient::get_env`] and [`RunnerClient::get_envs`].
///
Expand All @@ -51,6 +51,11 @@ pub struct GetEnvOptions {
pub tracked: Option<bool>,
}

#[napi(object)]
pub struct GetEnvsPrefixQuery {
pub prefix: String,
}

/// Handle returned by [`load`]. Holds the IPC connection and exposes the
/// runner-side operations as instance methods.
#[napi]
Expand Down Expand Up @@ -96,20 +101,23 @@ impl RunnerClient {
#[napi]
pub fn get_envs(
&self,
pattern: String,
query: Either<String, GetEnvsPrefixQuery>,
options: Option<GetEnvOptions>,
) -> Result<HashMap<String, String>> {
let tracked = options.and_then(|o| o.tracked).unwrap_or(true);
let matches = self
.client
.get_envs(pattern.as_str(), tracked)
.map_err(|err| err_string(vite_str::format!("{err}")))?;
let matches = match &query {
Either::A(pattern) => {
self.client.get_envs(GetEnvsQuery::Glob(pattern.as_str()), tracked)
}
Either::B(prefix) => {
self.client.get_envs(GetEnvsQuery::Prefix(&prefix.prefix), tracked)
}
}
.map_err(|err| err_string(vite_str::format!("{err}")))?;
let mut result = HashMap::with_capacity(matches.len());
for (name, value) in matches {
let name = name.to_str().ok_or_else(|| {
err_string(vite_str::format!(
"env name matched by pattern {pattern} is not valid UTF-8"
))
err_static("env name matched by getEnvs query is not valid UTF-8")
})?;
let value = value.to_str().ok_or_else(|| {
err_string(vite_str::format!("env value for {name} is not valid UTF-8"))
Expand Down
1 change: 1 addition & 0 deletions crates/vite_task_ipc_shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub enum Request<'a> {
#[derive(Debug, Clone, Copy, SchemaWrite, SchemaRead)]
pub enum EnvQuery<'a> {
Glob(&'a str),
Prefix(&'a str),
}

#[derive(Debug, SchemaWrite, SchemaRead)]
Expand Down
Loading
Loading