From e7ddb9789e14b9577f324b049d46e8a17cde1491 Mon Sep 17 00:00:00 2001 From: MK Date: Sat, 4 Jul 2026 22:23:03 +0800 Subject: [PATCH] fix(client): return undefined for missing env values Co-authored-by: GPT-5 Codex --- CHANGELOG.md | 1 + .../fixtures/vite_build_cache/snapshots.toml | 35 +++++++++++++++++++ ...e_build_defaults_node_env_to_production.md | 11 ++++++ .../vite_build_preserves_explicit_node_env.md | 11 ++++++ .../fixtures/vite_build_cache/vite-task.json | 8 +++++ .../fixtures/vite_build_cache/vite.config.js | 13 +++++++ crates/vite_task_client_napi/src/lib.rs | 12 ++++--- 7 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_build_defaults_node_env_to_production.md create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_build_preserves_explicit_node_env.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c112e278..55c901c7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +- **Fixed** Missing environment variables returned by `@voidzero-dev/vite-task-client` are now JavaScript `undefined` instead of `null`, preventing tools such as Vite from assigning `"null"` to `process.env.NODE_ENV` ([vite-plus#2047](https://github.com/voidzero-dev/vite-plus/issues/2047)). - **Fixed** Windows builds no longer hang on CI when a `node_modules/.bin` `.cmd` shim is routed through PowerShell: the npm/pnpm/yarn `.ps1` wrappers read stdin and block forever on a non-TTY pipe, so the PowerShell rewrite is now skipped when stdin is not an interactive terminal, falling back to the `.cmd` (which never reads stdin) ([#491](https://github.com/voidzero-dev/vite-task/pull/491)). - **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** Support for specifying tasks from dependency packages in `dependsOn`, such as `dependsOn: [{ "task": "build", "from": "dependencies" }]` ([#479](https://github.com/voidzero-dev/vite-task/pull/479)). diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots.toml index 4248aa961..2c0a6e4d3 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots.toml +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots.toml @@ -34,6 +34,41 @@ steps = [ ], comment = "restored from the cache archive" }, ] +[[e2e]] +name = "vite_build_defaults_node_env_to_production" +comment = """ +When the runner has no `NODE_ENV`, Vite's `getEnv('NODE_ENV')` call must return JavaScript `undefined`. This lets Vite apply its normal production-build default instead of assigning the string `"null"` to `process.env.NODE_ENV`. +""" +ignore = true +steps = [ + { argv = [ + "vt", + "run", + "--cache", + "build-production-node-env", + ], comment = "Vite config hook observes NODE_ENV=production" }, +] + +[[e2e]] +name = "vite_build_preserves_explicit_node_env" +comment = """ +When the runner has an explicit `NODE_ENV`, Vite's `getEnv('NODE_ENV')` call must return that value instead of applying the production-build default. +""" +ignore = true +steps = [ + { argv = [ + "vt", + "run", + "--cache", + "build-test-node-env", + ], envs = [ + [ + "NODE_ENV", + "test", + ], + ], comment = "Vite config hook observes NODE_ENV=test" }, +] + [[e2e]] name = "vite_prefix_env_change_invalidates_cache" comment = """ diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_build_defaults_node_env_to_production.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_build_defaults_node_env_to_production.md new file mode 100644 index 000000000..bfe341b8b --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_build_defaults_node_env_to_production.md @@ -0,0 +1,11 @@ +# vite_build_defaults_node_env_to_production + +When the runner has no `NODE_ENV`, Vite's `getEnv('NODE_ENV')` call must return JavaScript `undefined`. This lets Vite apply its normal production-build default instead of assigning the string `"null"` to `process.env.NODE_ENV`. + +## `vt run --cache build-production-node-env` + +Vite config hook observes NODE_ENV=production + +``` +$ ASSERT_NODE_ENV=production vite build +``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_build_preserves_explicit_node_env.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_build_preserves_explicit_node_env.md new file mode 100644 index 000000000..900b330c0 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_build_preserves_explicit_node_env.md @@ -0,0 +1,11 @@ +# vite_build_preserves_explicit_node_env + +When the runner has an explicit `NODE_ENV`, Vite's `getEnv('NODE_ENV')` call must return that value instead of applying the production-build default. + +## `NODE_ENV=test vt run --cache build-test-node-env` + +Vite config hook observes NODE_ENV=test + +``` +$ ASSERT_NODE_ENV=test vite build +``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/vite-task.json index 81571aeaf..dee8e3a69 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/vite-task.json +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/vite-task.json @@ -6,6 +6,14 @@ // every `VITE_*` env via `getEnvs`, so the glob + match-set are // fingerprinted automatically. "cache": true + }, + "build-production-node-env": { + "command": "ASSERT_NODE_ENV=production vite build", + "cache": true + }, + "build-test-node-env": { + "command": "ASSERT_NODE_ENV=test vite build", + "cache": true } } } diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/vite.config.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/vite.config.js index cc83efec1..1e019a40b 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/vite.config.js +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/vite.config.js @@ -2,6 +2,19 @@ import { defineConfig } from 'vite'; export default defineConfig({ logLevel: 'silent', + plugins: [ + { + name: 'assert-node-env', + config() { + const expectedNodeEnv = process.env.ASSERT_NODE_ENV; + if (expectedNodeEnv !== undefined && process.env.NODE_ENV !== expectedNodeEnv) { + throw new Error( + `Expected NODE_ENV to be ${JSON.stringify(expectedNodeEnv)}, received ${JSON.stringify(process.env.NODE_ENV)}`, + ); + } + }, + }, + ], build: { rollupOptions: { output: { diff --git a/crates/vite_task_client_napi/src/lib.rs b/crates/vite_task_client_napi/src/lib.rs index 81f4a0058..b8d53f09c 100644 --- a/crates/vite_task_client_napi/src/lib.rs +++ b/crates/vite_task_client_napi/src/lib.rs @@ -30,7 +30,7 @@ )] use std::{collections::HashMap, ffi::OsStr}; -use napi::{Either, Error, Result}; +use napi::{Either, Error, Result, bindgen_prelude::Undefined}; use napi_derive::napi; use vite_task_client::{Client, GetEnvsQuery}; @@ -85,14 +85,18 @@ impl RunnerClient { } #[napi] - pub fn get_env(&self, name: String, options: Option) -> Result> { + pub fn get_env( + &self, + name: String, + options: Option, + ) -> Result> { let tracked = options.and_then(|o| o.tracked).unwrap_or(true); let value = self .client .get_env(OsStr::new(&name), tracked) .map_err(|err| err_string(vite_str::format!("{err}")))?; - value.map_or(Ok(None), |value| { - value.to_str().map(|s| Some(s.to_owned())).ok_or_else(|| { + value.map_or(Ok(Either::B(())), |value| { + value.to_str().map(|s| Either::A(s.to_owned())).ok_or_else(|| { err_string(vite_str::format!("env value for {name} is not valid UTF-8")) }) })