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
156 changes: 8 additions & 148 deletions packages/cli/binding/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
//! It handles argument parsing, command dispatching, and orchestration of the task execution.

use std::{
borrow::Cow, env, ffi::OsStr, future::Future, io::IsTerminal, iter, path::PathBuf, pin::Pin,
process::Stdio, sync::Arc, time::Instant,
borrow::Cow, env, ffi::OsStr, future::Future, io::IsTerminal, iter, pin::Pin, process::Stdio,
sync::Arc, time::Instant,
};

use clap::{
Expand All @@ -15,7 +15,6 @@ use clap::{
use owo_colors::OwoColorize;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use tokio::fs::write;
use vite_error::Error;
use vite_path::{AbsolutePath, AbsolutePathBuf};
use vite_shared::{PrependOptions, output, prepend_to_path_env};
Expand Down Expand Up @@ -185,144 +184,27 @@ impl ResolvedSubcommand {
pub struct SubcommandResolver {
cli_options: Option<CliOptions>,
workspace_path: Arc<AbsolutePath>,
/// Track temporary config files created during resolution for cleanup
temp_config_files: Vec<AbsolutePathBuf>,
}

impl std::fmt::Debug for SubcommandResolver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SubcommandResolver")
.field("has_cli_options", &self.cli_options.is_some())
.field("workspace_path", &self.workspace_path)
.field("temp_config_files_count", &self.temp_config_files.len())
.finish()
}
}

impl SubcommandResolver {
pub fn new(workspace_path: Arc<AbsolutePath>) -> Self {
Self { cli_options: None, workspace_path, temp_config_files: Vec::new() }
Self { cli_options: None, workspace_path }
}

pub fn with_cli_options(mut self, cli_options: CliOptions) -> Self {
self.cli_options = Some(cli_options);
self
}

/// Clean up temporary config files created during resolution.
/// Should be called after command execution completes (success or failure).
pub async fn cleanup_temp_files(&mut self) {
for path in self.temp_config_files.drain(..) {
if let Err(e) = tokio::fs::remove_file(&path).await {
if e.kind() != std::io::ErrorKind::NotFound {
tracing::warn!(
"Failed to cleanup temp config file {}: {}",
path.as_path().display(),
e
);
}
}
}
}

/// Write a temporary TS config file that re-exports a field from vite.config.
/// The temp file imports the vite config and re-exports the specified field,
/// so the tool (e.g. oxlint) picks it up via `-c <path>`.
/// The `config_file_path` must be an absolute path.
async fn write_temp_ts_config_import(
&mut self,
config_file_path: &str,
temp_filename: &str,
field_name: &str,
args: &mut Vec<String>,
) -> anyhow::Result<()> {
let path = PathBuf::from(config_file_path);
if !path.is_absolute() {
anyhow::bail!("config_file_path must be an absolute path, got: {config_file_path}");
}

let config_basename = path
.file_name()
.and_then(|n| n.to_str())
.ok_or_else(|| {
anyhow::anyhow!("Failed to get file name of config file: {config_file_path}")
})?
.to_string();

let config_dir = AbsolutePathBuf::new(path)
.and_then(|p| p.parent().map(|p| p.to_absolute_path_buf()))
.ok_or_else(|| {
anyhow::anyhow!("Failed to get parent directory of config file: {config_file_path}")
})?;

let config_path = config_dir.join(temp_filename);
let content = format!(
"import {{ defineConfig }} from 'vite-plus/lint';\nimport viteConfig from './{config_basename}';\nexport default defineConfig(viteConfig.{field_name} as any);\n"
);
write(&config_path, content).await?;

self.temp_config_files.push(config_path.clone());

let config_path_str = config_path
.as_path()
.to_str()
.ok_or_else(|| anyhow::anyhow!("config path is not valid UTF-8"))?;
args.insert(0, config_path_str.to_string());
args.insert(0, "-c".to_string());
// Prevent oxlint from linting the temp config file itself
args.push("--ignore-pattern".to_string());
args.push(temp_filename.to_string());
Ok(())
}

/// Write a temporary JSON config file and prepend `-c <path>` to args.
/// The file will be tracked for cleanup after command execution.
/// The `config_file_path` must be an absolute path.
async fn write_temp_json_config_file(
&mut self,
config: &serde_json::Value,
config_file_path: &str,
temp_filename: &str,
args: &mut Vec<String>,
) -> anyhow::Result<()> {
let mut config = config.clone();

// Add temp file to ignorePatterns to prevent self-checking
if let Some(obj) = config.as_object_mut() {
if let Some(patterns) = obj.get_mut("ignorePatterns") {
if let Some(array) = patterns.as_array_mut() {
array.push(serde_json::json!(temp_filename));
}
} else {
obj.insert("ignorePatterns".to_string(), serde_json::json!([temp_filename]));
}
}

let path = PathBuf::from(config_file_path);
if !path.is_absolute() {
anyhow::bail!("config_file_path must be an absolute path, got: {config_file_path}");
}

let config_dir = AbsolutePathBuf::new(path)
.and_then(|p| p.parent().map(|p| p.to_absolute_path_buf()))
.ok_or_else(|| {
anyhow::anyhow!("Failed to get parent directory of config file: {config_file_path}")
})?;

let config_path = config_dir.join(temp_filename);
write(&config_path, serde_json::to_string(&config)?).await?;

self.temp_config_files.push(config_path.clone());

let config_path_str = config_path
.as_path()
.to_str()
.ok_or_else(|| anyhow::anyhow!("config path is not valid UTF-8"))?;
args.insert(0, config_path_str.to_string());
args.insert(0, "-c".to_string());
Ok(())
}

/// Resolve a synthesizable subcommand to a concrete program, args, cache config, and envs.
async fn resolve(
&mut self,
Expand Down Expand Up @@ -358,13 +240,8 @@ impl SubcommandResolver {
if let (Some(_), Some(config_file)) =
(&resolved_vite_config.lint, &resolved_vite_config.config_file)
{
self.write_temp_ts_config_import(
config_file,
".vite-plus-lint.tmp.mts",
"lint",
&mut args,
)
.await?;
args.insert(0, "-c".to_string());
args.insert(1, config_file.clone());
}

Ok(ResolvedSubcommand {
Expand Down Expand Up @@ -405,16 +282,11 @@ impl SubcommandResolver {
tracing::error!("Failed to parse vite config: {vite_config_json}");
})?;

if let (Some(fmt_config), Some(config_file)) =
if let (Some(_), Some(config_file)) =
(&resolved_vite_config.fmt, &resolved_vite_config.config_file)
{
self.write_temp_json_config_file(
fmt_config,
config_file,
".vite-plus-fmt.tmp.json",
&mut args,
)
.await?;
args.insert(0, "-c".to_string());
args.insert(1, config_file.clone());
}

Ok(ResolvedSubcommand {
Expand Down Expand Up @@ -649,10 +521,6 @@ impl VitePlusCommandHandler {
pub fn new(resolver: SubcommandResolver) -> Self {
Self { resolver }
}

pub async fn cleanup_temp_files(&mut self) {
self.resolver.cleanup_temp_files().await;
}
}

#[async_trait::async_trait(?Send)]
Expand Down Expand Up @@ -1053,7 +921,6 @@ async fn execute_direct_subcommand(
print_summary_line(
"`vp check` did not run because both `--no-fmt` and `--no-lint` were set",
);
resolver.cleanup_temp_files().await;
return Ok(ExitStatus(1));
}

Expand Down Expand Up @@ -1130,7 +997,6 @@ async fn execute_direct_subcommand(
);
}
if status != ExitStatus::SUCCESS {
resolver.cleanup_temp_files().await;
return Ok(status);
}
}
Expand Down Expand Up @@ -1206,7 +1072,6 @@ async fn execute_direct_subcommand(
}
}
if status != ExitStatus::SUCCESS {
resolver.cleanup_temp_files().await;
return Ok(status);
}
}
Expand Down Expand Up @@ -1242,7 +1107,6 @@ async fn execute_direct_subcommand(
print_stdout_block(&combined_output);
}
print_summary_line("Formatting failed after lint fixes were applied");
resolver.cleanup_temp_files().await;
return Ok(status);
}
if let Some(started) = fmt_fix_started {
Expand Down Expand Up @@ -1275,8 +1139,6 @@ async fn execute_direct_subcommand(
}
};

resolver.cleanup_temp_files().await;

Ok(status)
}

Expand Down Expand Up @@ -1321,8 +1183,6 @@ async fn execute_vite_task_command(
// Main execution (consumes session)
let result = session.main(command).await.map_err(|e| Error::Anyhow(e));

command_handler.cleanup_temp_files().await;

result
}

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@
},
"./test/config": {
"types": "./dist/test/config.d.ts",
"require": "./dist/test/config.cjs",
"default": "./dist/test/config.js"
"default": "./dist/test/config.js",
"require": "./dist/test/config.cjs"
},
"./test/coverage": {
"types": "./dist/test/coverage.d.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
export default {
lint: {
rules: {
'no-eval': 'error',
},
},
fmt: {},
staged: {
'*.ts': 'vp check --fix',
'*.js': 'vp lint',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default defineConfig({
staged: {
"*": "vp check --fix",
},
fmt: {},
lint: { options: { typeAware: true, typeCheck: true } },
});

Expand All @@ -17,6 +18,7 @@ export default defineConfig({
staged: {
"*": "vp check --fix",
},
fmt: {},
lint: { options: { typeAware: true, typeCheck: true } },
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default defineConfig({
staged: {
"*": "vp check --fix"
},
fmt: {},
lint: {
"rules": {
"no-unused-vars": "error"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ VITE+ - The Unified Toolchain for the Web
import { defineConfig } from 'vite-plus';

export default defineConfig({
fmt: {},
lint: {"options":{"typeAware":true,"typeCheck":true}},
staged: {
"*.js": "vp lint --fix"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ VITE+ - The Unified Toolchain for the Web
import { defineConfig } from 'vite-plus';

export default defineConfig({
fmt: {},
lint: {"options":{"typeAware":true,"typeCheck":true}},
staged: {
"*.js": "vp lint --fix"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ VITE+ - The Unified Toolchain for the Web
import { defineConfig } from 'vite-plus';

export default defineConfig({
fmt: {},
lint: {
"plugins": [
"oxc",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ cat: .lintstagedrc.json: No such file or directory
import { defineConfig } from 'vite-plus';

export default defineConfig({
fmt: {},
lint: {
"plugins": [
"oxc",
Expand Down
1 change: 1 addition & 0 deletions packages/cli/snap-tests-global/migration-eslint/snap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export default defineConfig({
staged: {
"*": "vp check --fix"
},
fmt: {},
lint: {
"plugins": [
"oxc",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ VITE+ - The Unified Toolchain for the Web
import { defineConfig } from 'vite-plus';

export default defineConfig({
fmt: {},
lint: {"options":{"typeAware":true,"typeCheck":true}},
staged: {
"*.js": "vp lint --fix"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ cat: .lintstagedrc.json: No such file or directory
import { defineConfig } from 'vite-plus';

export default defineConfig({
fmt: {},
lint: {"options":{"typeAware":true,"typeCheck":true}},
staged: {
"*.ts": "vp lint --fix"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ VITE+ - The Unified Toolchain for the Web
import { defineConfig } from 'vite-plus';

export default defineConfig({
fmt: {},
lint: {"options":{"typeAware":true,"typeCheck":true}},
staged: {
"*.js": "vp lint --fix"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default defineConfig({
"cwd": "./src"
}
},
fmt: {},
lint: {"options":{"typeAware":true,"typeCheck":true}},
server: {
port: 3000,
Expand Down Expand Up @@ -74,6 +75,7 @@ export default defineConfig({
"cwd": "./src"
}
},
fmt: {},
lint: {"options":{"typeAware":true,"typeCheck":true}},
server: {
port: 3000,
Expand Down
Loading
Loading