From 4a506f07021372715f0c4209aa351d5fb44dc8a5 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Mon, 11 Aug 2025 16:10:56 +0800 Subject: [PATCH 1/9] [WIP]feat: support vite lint --- .gitignore | 3 +- Cargo.lock | 2 + crates/vite_error/src/lib.rs | 3 + crates/vite_task/src/config/mod.rs | 69 +++- crates/vite_task/src/config/workspace.rs | 44 +++ crates/vite_task/src/execute.rs | 37 +- crates/vite_task/src/lib.rs | 41 ++- crates/vite_task/src/lint.rs | 23 ++ crates/vite_task/src/main.rs | 4 +- crates/vite_task/src/schedule.rs | 64 ++-- package.json | 5 +- packages/cli/bin/vite-plus | 4 +- packages/cli/binding/Cargo.toml | 2 + packages/cli/binding/index.d.ts | 12 +- packages/cli/binding/index.js | 167 +++++++-- packages/cli/binding/src/lib.rs | 57 ++- packages/cli/package.json | 3 +- packages/cli/src/bin.ts | 7 + packages/cli/src/lint.ts | 22 ++ packages/cli/tsconfig.json | 4 +- packages/cli/tsdown.config.ts | 6 - pnpm-lock.yaml | 447 +++++++++++++---------- pnpm-workspace.yaml | 11 +- 23 files changed, 755 insertions(+), 282 deletions(-) create mode 100644 crates/vite_task/src/lint.rs create mode 100644 packages/cli/src/bin.ts create mode 100644 packages/cli/src/lint.ts delete mode 100644 packages/cli/tsdown.config.ts diff --git a/.gitignore b/.gitignore index 2e52a5537e..4eb6e56cfb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target node_modules dist -.claude/settings.local.json \ No newline at end of file +.claude/settings.local.json +*.tsbuildinfo \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 9ba5d2ce27..fc0b2c4927 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3027,9 +3027,11 @@ name = "vite-plus-cli" version = "0.0.1" dependencies = [ "clap", + "futures", "napi", "napi-build", "napi-derive", + "vite_error", "vite_task", ] diff --git a/crates/vite_error/src/lib.rs b/crates/vite_error/src/lib.rs index b1efaa625f..610480537e 100644 --- a/crates/vite_error/src/lib.rs +++ b/crates/vite_error/src/lib.rs @@ -79,6 +79,9 @@ pub enum Error { #[error(transparent)] SerdeYmlError(#[from] serde_yml::Error), + #[error("Lint failed")] + LintFailed { status: String, reason: String }, + #[error(transparent)] AnyhowError(#[from] anyhow::Error), } diff --git a/crates/vite_task/src/config/mod.rs b/crates/vite_task/src/config/mod.rs index 220b7b088b..fd350940d2 100644 --- a/crates/vite_task/src/config/mod.rs +++ b/crates/vite_task/src/config/mod.rs @@ -3,16 +3,20 @@ mod task_command; mod task_graph_builder; mod workspace; -use std::{ffi::OsStr, sync::Arc}; +use std::{ffi::OsStr, future::Future, sync::Arc}; use bincode::{Decode, Encode}; use compact_str::ToCompactString; use diff::Diff; use serde::{Deserialize, Serialize}; +use vite_error::Error; use crate::{ + ResolveCommandResult, + cmd::TaskParsedCommand, collections::{HashMap, HashSet}, config::name::TaskName, + execute::TaskEnvs, str::Str, }; @@ -90,6 +94,49 @@ impl ResolvedTask { pub fn display_name(&self) -> Str { self.name.to_compact_string().into() } + + #[tracing::instrument(skip(workspace, resolve_command, args))] + /// Resolve a built-in task, like `vite lint`, `vite build` + pub(crate) async fn resolve_from_built_in< + Resolved: Future>, + ResolveFn: Fn() -> Resolved, + >( + workspace: &Workspace, + resolve_command: ResolveFn, + task_name: &str, + args: &Vec, + ) -> Result { + let ResolveCommandResult { bin_path, envs } = resolve_command().await?; + let link_task = TaskCommand::Parsed(TaskParsedCommand { + args: args.iter().map(|arg| arg.as_str().into()).collect(), + envs: envs.into_iter().map(|(k, v)| (k.into(), v.into())).collect(), + program: bin_path.into(), + }); + let task_config: TaskConfig = link_task.clone().into(); + let resolved_task_config = ResolvedTaskConfig { + config_dir: workspace.dir.as_path().to_string_lossy().as_ref().into(), + config: task_config, + }; + let resolved_envs = TaskEnvs::resolve(workspace.dir.as_path(), &resolved_task_config)?; + let resolved_command = ResolvedTaskCommand { + fingerprint: CommandFingerprint { + cwd: workspace.dir.as_path().to_string_lossy().as_ref().into(), + command: link_task.clone().into(), + envs_without_pass_through: resolved_envs.envs_without_pass_through, + }, + all_envs: resolved_envs.all_envs, + }; + Ok(ResolvedTask { + name: TaskName { + package_name: workspace.package_json.name.as_str().into(), + task_group_name: task_name.into(), + subcommand_index: None, + }, + args: args.iter().map(|arg| arg.as_str().into()).collect(), + resolved_config: resolved_task_config, + resolved_command, + }) + } } #[derive(Clone)] @@ -112,11 +159,31 @@ impl std::fmt::Debug for ResolvedTaskCommand { } } +/// Fingerprint for command execution that affects caching. +/// +/// # Environment Variable Impact on Cache +/// +/// The `envs_without_pass_through` field is crucial for cache correctness: +/// - Only includes envs explicitly declared in the task's `envs` array +/// - Does NOT include pass-through envs (PATH, CI, etc.) +/// - These envs become part of the cache key +/// +/// When a task runs: +/// 1. All envs (including pass-through) are available to the process +/// 2. Only declared envs affect the cache key +/// 3. If a declared env changes value, cache will miss +/// 4. If a pass-through env changes, cache will still hit +/// +/// For built-in tasks (lint, build, etc): +/// - The resolver provides envs which become part of the fingerprint +/// - If resolver provides different envs between runs, cache breaks +/// - Each built-in task type must have unique task name to avoid cache collision #[derive(Encode, Decode, Debug, Serialize, PartialEq, Eq, Diff, Clone)] #[diff(attr(#[derive(Debug)]))] pub struct CommandFingerprint { pub cwd: Str, pub command: TaskCommand, + /// Environment variables that affect caching (excludes pass-through envs) pub envs_without_pass_through: HashMap, } diff --git a/crates/vite_task/src/config/workspace.rs b/crates/vite_task/src/config/workspace.rs index 4e79c7db0d..7d58cea59d 100644 --- a/crates/vite_task/src/config/workspace.rs +++ b/crates/vite_task/src/config/workspace.rs @@ -40,6 +40,50 @@ impl Workspace { Self::load_with_cache_path(dir, None, topological_run) } + pub fn partial_load(dir: PathBuf) -> Result { + Self::partial_load_with_cache_path(dir, None) + } + + pub fn partial_load_with_cache_path( + dir: PathBuf, + cache_path: Option, + ) -> Result { + let cache_path = cache_path.unwrap_or_else(|| { + if let Ok(env_cache_path) = std::env::var("VITE_CACHE_PATH") { + PathBuf::from(env_cache_path) + } else { + dir.join("node_modules/.vite/task-cache.db") + } + }); + + if !cache_path.exists() + && let Some(cache_dir) = cache_path.parent() + { + tracing::info!("Creating task cache directory at {}", cache_dir.display()); + std::fs::create_dir_all(cache_dir)?; + } + let task_cache = TaskCache::load_from_file(&cache_path)?; + + let package_json_path = dir.join("package.json"); + let package_json = if package_json_path.exists() { + let file = File::open(&package_json_path)?; + let reader = BufReader::new(file); + serde_json::from_reader(reader)? + } else { + PackageJson::default() + }; + + Ok(Self { + package_graph: Graph::new(), + dir, + task_cache, + fs: CachedFileSystem::default(), + package_json, + task_graph: StableDiGraph::new(), + topological_run: false, + }) + } + pub fn load_with_cache_path( dir: PathBuf, cache_path: Option, diff --git a/crates/vite_task/src/execute.rs b/crates/vite_task/src/execute.rs index d8ee5bf9d2..64b694e651 100644 --- a/crates/vite_task/src/execute.rs +++ b/crates/vite_task/src/execute.rs @@ -20,7 +20,7 @@ use wax::Glob; use crate::{ Error, collections::{HashMap, HashSet}, - config::{ResolvedTask, ResolvedTaskConfig, TaskCommand}, + config::{ResolvedTask, ResolvedTaskCommand, ResolvedTaskConfig, TaskCommand}, maybe_str::MaybeString, str::Str, }; @@ -85,9 +85,38 @@ async fn collect_std_outputs( } } +/// Environment variables for task execution. +/// +/// # How Environment Variables Affect Caching +/// +/// Vite-plus distinguishes between two types of environment variables: +/// +/// 1. **Declared envs** (in task config's `envs` array): +/// - Explicitly declared as dependencies of the task +/// - Included in `envs_without_pass_through` +/// - Changes to these invalidate the cache +/// - Example: NODE_ENV, API_URL, BUILD_MODE +/// +/// 2. **Pass-through envs** (in task config's `pass_through_envs` or defaults like PATH): +/// - Available to the task but don't affect caching +/// - Only in `all_envs`, NOT in `envs_without_pass_through` +/// - Changes to these don't invalidate cache +/// - Example: PATH, HOME, USER, CI +/// +/// ## Cache Key Generation +/// - Only `envs_without_pass_through` is included in the cache key +/// - This ensures tasks are re-run when important envs change +/// - But allows cache reuse when only incidental envs change +/// +/// ## Common Issues +/// - If a built-in resolver provides different envs, cache will be polluted +/// - Missing important envs from `envs` array = stale cache on env changes +/// - Including volatile envs in `envs` array = unnecessary cache misses #[derive(Debug)] pub struct TaskEnvs { + /// All environment variables available to the task (declared + pass-through) pub all_envs: HashMap>, + /// Only declared envs that affect the cache key (excludes pass-through) pub envs_without_pass_through: HashMap, } @@ -141,8 +170,10 @@ impl TaskEnvs { } } -pub async fn execute_task(task: &ResolvedTask, base_dir: &Path) -> Result { - let resolved_command = &task.resolved_command; +pub async fn execute_task( + resolved_command: &ResolvedTaskCommand, + base_dir: &Path, +) -> Result { let spy = Spy::global()?; let mut cmd = match &resolved_command.fingerprint.command { diff --git a/crates/vite_task/src/lib.rs b/crates/vite_task/src/lib.rs index aa327ac95e..18b6d85e9b 100644 --- a/crates/vite_task/src/lib.rs +++ b/crates/vite_task/src/lib.rs @@ -5,6 +5,7 @@ mod config; mod execute; mod fingerprint; mod fs; +mod lint; mod maybe_str; mod schedule; mod str; @@ -12,7 +13,9 @@ mod str; #[cfg(test)] mod test_utils; +use std::collections::HashMap; use std::path::PathBuf; +use std::pin::Pin; use std::sync::Arc; use clap::{Parser, Subcommand}; @@ -70,6 +73,11 @@ pub enum Commands { #[clap(long, conflicts_with = "topological")] no_topological: bool, }, + Lint { + #[clap(last = true)] + /// Arguments to pass to oxlint + args: Vec, + }, } /// Resolve boolean flag value considering both positive and negative forms. @@ -79,6 +87,20 @@ const fn resolve_bool_flag(positive: bool, negative: bool) -> bool { if negative { false } else { positive } } +pub struct CliOptions< + Lint: Future> = Pin< + Box>>, + >, + LintFn: Fn() -> Lint = Box Lint>, +> { + pub lint: LintFn, +} + +pub struct ResolveCommandResult { + pub bin_path: String, + pub envs: HashMap, +} + /// Main entry point for vite-plus task execution. /// /// # Execution Flow @@ -108,8 +130,15 @@ const fn resolve_bool_flag(positive: bool, negative: bool) -> bool { /// 4. Execute plan /// - For each task: check cache → execute/replay → update cache /// ``` -#[tracing::instrument] -pub async fn main(cwd: PathBuf, args: Args) -> Result<(), Error> { +#[tracing::instrument(skip(options))] +pub async fn main< + Lint: Future>, + LintFn: Fn() -> Lint, +>( + cwd: PathBuf, + args: Args, + options: Option>, +) -> Result<(), Error> { let mut recursive_run = false; let mut parallel_run = false; let (tasks, mut workspace, task_args) = match &args.commands { @@ -138,6 +167,14 @@ pub async fn main(cwd: PathBuf, args: Args) -> Result<(), Error> { let workspace = Workspace::load(cwd, topological_run)?; (tasks, workspace, Arc::<[Str]>::from(task_args.clone())) } + Some(Commands::Lint { args }) => { + let mut workspace = Workspace::partial_load(cwd)?; + if let Some(lint_fn) = options.map(|o| o.lint) { + lint::lint(lint_fn, &mut workspace, args).await?; + workspace.unload().await?; + } + return Ok(()); + } None => { let workspace = Workspace::load(cwd, false)?; // in implicit mode, vite-plus will run the task in the current package, replace the `pnpm/yarn/npm run` command. diff --git a/crates/vite_task/src/lint.rs b/crates/vite_task/src/lint.rs new file mode 100644 index 0000000000..8770762395 --- /dev/null +++ b/crates/vite_task/src/lint.rs @@ -0,0 +1,23 @@ +use std::future::Future; + +use petgraph::stable_graph::StableGraph; + +use crate::config::ResolvedTask; +use crate::schedule::ExecutionPlan; +use crate::{Error, ResolveCommandResult, Workspace}; + +pub async fn lint< + Lint: Future>, + LintFn: Fn() -> Lint, +>( + resolve_lint_command: LintFn, + workspace: &mut Workspace, + args: &Vec, +) -> Result<(), Error> { + let resolved_task = + ResolvedTask::resolve_from_built_in(workspace, resolve_lint_command, "lint", args).await?; + let mut task_graph: StableGraph = Default::default(); + task_graph.add_node(resolved_task); + ExecutionPlan::plan(task_graph, false)?.execute(workspace).await?; + Ok(()) +} diff --git a/crates/vite_task/src/main.rs b/crates/vite_task/src/main.rs index fff3951a78..7980d16b51 100644 --- a/crates/vite_task/src/main.rs +++ b/crates/vite_task/src/main.rs @@ -1,7 +1,7 @@ use std::env::current_dir; use clap::Parser as _; -use vite_task::{Args, init_tracing}; +use vite_task::{Args, CliOptions, init_tracing}; use vite_error::Error; @@ -10,5 +10,5 @@ async fn main() -> Result<(), Error> { init_tracing(); let args = Args::parse(); - vite_task::main(current_dir()?, args).await + vite_task::main(current_dir()?, args, None::).await } diff --git a/crates/vite_task/src/schedule.rs b/crates/vite_task/src/schedule.rs index 50e580a964..73dd298934 100644 --- a/crates/vite_task/src/schedule.rs +++ b/crates/vite_task/src/schedule.rs @@ -67,37 +67,41 @@ impl ExecutionPlan { #[tracing::instrument(skip(self, workspace))] pub async fn execute(self, workspace: &mut Workspace) -> anyhow::Result<()> { for step in self.steps { - tracing::debug!("Executing task {}", step.display_name()); - - let command = step.resolved_command.fingerprint.command.clone(); - - // Check cache and prepare execution - let (cache_miss, execute_or_replay) = get_cached_or_execute( - step, - &mut workspace.task_cache, - &workspace.fs, - &workspace.dir, - ) - .await?; - - // Print cache status - match cache_miss { - Some(CacheMiss::NotFound) => { - println!("Cache Not Found, executing task"); - println!("> {command}"); - } - Some(CacheMiss::FingerprintMismatch(mismatch)) => { - println!("{mismatch}, executing task"); - println!("> {command}"); - } - None => { - println!("Cache hit, replaying previously executed task"); - } - } + Self::execute_resolved_task(step, workspace).await?; + } + Ok(()) + } - // Execute or replay the task - execute_or_replay.await?; + pub async fn execute_resolved_task( + step: ResolvedTask, + workspace: &mut Workspace, + ) -> anyhow::Result<()> { + tracing::debug!("Executing task {}", step.display_name()); + + let command = step.resolved_command.fingerprint.command.clone(); + + // Check cache and prepare execution + let (cache_miss, execute_or_replay) = + get_cached_or_execute(step, &mut workspace.task_cache, &workspace.fs, &workspace.dir) + .await?; + + // Print cache status + match cache_miss { + Some(CacheMiss::NotFound) => { + println!("Cache Not Found, executing task"); + println!("> {command}"); + } + Some(CacheMiss::FingerprintMismatch(mismatch)) => { + println!("{mismatch}, executing task"); + println!("> {command}"); + } + None => { + println!("Cache hit, replaying previously executed task"); + } } + + // Execute or replay the task + execute_or_replay.await?; Ok(()) } } @@ -133,7 +137,7 @@ async fn get_cached_or_execute<'a>( Err(cache_miss) => ( Some(cache_miss), async move { - let executed_task = execute_task(&task, base_dir).await?; + let executed_task = execute_task(&task.resolved_command, base_dir).await?; let cached_task = CachedTask::create(task.clone(), executed_task, fs, base_dir)?; cache.update(&task, cached_task).await?; Ok(()) diff --git a/package.json b/package.json index 84fed211c8..635674f0a2 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,12 @@ { "name": "vite-plus-monorepo", "private": true, - "packageManager": "pnpm@10.13.1", + "packageManager": "pnpm@10.14.0", "volta": { "node": "22.17.0" }, "scripts": { "bootstrap-cli": "cargo build -p vite_task && ./target/debug/vt run @vite-plus/global#prepack && npm install -g ./packages/global", - "lint": "oxlint", "typecheck": "tsc -b tsconfig.json", "prepare": "husky" }, @@ -17,7 +16,7 @@ "@types/node": "catalog:", "husky": "^9.1.7", "lint-staged": "^16.1.2", - "oxlint": "^1.8.0", + "oxlint": "catalog:", "typescript": "catalog:", "vite-plus": "workspace:*" }, diff --git a/packages/cli/bin/vite-plus b/packages/cli/bin/vite-plus index 03e7cfb17c..d906e14d0b 100755 --- a/packages/cli/bin/vite-plus +++ b/packages/cli/bin/vite-plus @@ -1,5 +1,3 @@ #!/usr/bin/env node -import { run } from '../binding/index.js' - -run() +import '../dist/bin.js' diff --git a/packages/cli/binding/Cargo.toml b/packages/cli/binding/Cargo.toml index c188623cc3..eee1321bb3 100644 --- a/packages/cli/binding/Cargo.toml +++ b/packages/cli/binding/Cargo.toml @@ -5,8 +5,10 @@ edition = "2024" [dependencies] clap = { workspace = true } +futures = { workspace = true } napi = { workspace = true } napi-derive = { workspace = true } +vite_error = { workspace = true } vite_task = { workspace = true } [build-dependencies] diff --git a/packages/cli/binding/index.d.ts b/packages/cli/binding/index.d.ts index c670b08928..fa29bf67e2 100644 --- a/packages/cli/binding/index.d.ts +++ b/packages/cli/binding/index.d.ts @@ -1,3 +1,13 @@ /* auto-generated by NAPI-RS */ /* eslint-disable */ -export declare function run(cwd?: string | undefined | null): Promise +export interface CliOptions { + lint: ((err: Error | null, ) => Promise) + cwd?: string +} + +export interface JsCommandResolvedResult { + binPath: string + envs: Record +} + +export declare function run(options: CliOptions): Promise diff --git a/packages/cli/binding/index.js b/packages/cli/binding/index.js index 1e5b52b76e..8f9894ed01 100644 --- a/packages/cli/binding/index.js +++ b/packages/cli/binding/index.js @@ -79,7 +79,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-android-arm64') + const binding = require('@vite-plus-android-arm64') + const bindingPackageVersion = require('@vite-plus-android-arm64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -90,7 +95,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-android-arm-eabi') + const binding = require('@vite-plus-android-arm-eabi') + const bindingPackageVersion = require('@vite-plus-android-arm-eabi/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -105,7 +115,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-win32-x64-msvc') + const binding = require('@vite-plus-win32-x64-msvc') + const bindingPackageVersion = require('@vite-plus-win32-x64-msvc/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -116,7 +131,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-win32-ia32-msvc') + const binding = require('@vite-plus-win32-ia32-msvc') + const bindingPackageVersion = require('@vite-plus-win32-ia32-msvc/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -127,7 +147,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-win32-arm64-msvc') + const binding = require('@vite-plus-win32-arm64-msvc') + const bindingPackageVersion = require('@vite-plus-win32-arm64-msvc/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -141,7 +166,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-darwin-universal') + const binding = require('@vite-plus-darwin-universal') + const bindingPackageVersion = require('@vite-plus-darwin-universal/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -152,7 +182,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-darwin-x64') + const binding = require('@vite-plus-darwin-x64') + const bindingPackageVersion = require('@vite-plus-darwin-x64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -163,7 +198,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-darwin-arm64') + const binding = require('@vite-plus-darwin-arm64') + const bindingPackageVersion = require('@vite-plus-darwin-arm64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -178,7 +218,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-freebsd-x64') + const binding = require('@vite-plus-freebsd-x64') + const bindingPackageVersion = require('@vite-plus-freebsd-x64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -189,7 +234,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-freebsd-arm64') + const binding = require('@vite-plus-freebsd-arm64') + const bindingPackageVersion = require('@vite-plus-freebsd-arm64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -205,7 +255,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-linux-x64-musl') + const binding = require('@vite-plus-linux-x64-musl') + const bindingPackageVersion = require('@vite-plus-linux-x64-musl/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -216,7 +271,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-linux-x64-gnu') + const binding = require('@vite-plus-linux-x64-gnu') + const bindingPackageVersion = require('@vite-plus-linux-x64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -229,7 +289,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-linux-arm64-musl') + const binding = require('@vite-plus-linux-arm64-musl') + const bindingPackageVersion = require('@vite-plus-linux-arm64-musl/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -240,7 +305,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-linux-arm64-gnu') + const binding = require('@vite-plus-linux-arm64-gnu') + const bindingPackageVersion = require('@vite-plus-linux-arm64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -253,7 +323,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-linux-arm-musleabihf') + const binding = require('@vite-plus-linux-arm-musleabihf') + const bindingPackageVersion = require('@vite-plus-linux-arm-musleabihf/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -264,7 +339,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-linux-arm-gnueabihf') + const binding = require('@vite-plus-linux-arm-gnueabihf') + const bindingPackageVersion = require('@vite-plus-linux-arm-gnueabihf/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -277,7 +357,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-linux-riscv64-musl') + const binding = require('@vite-plus-linux-riscv64-musl') + const bindingPackageVersion = require('@vite-plus-linux-riscv64-musl/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -288,7 +373,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-linux-riscv64-gnu') + const binding = require('@vite-plus-linux-riscv64-gnu') + const bindingPackageVersion = require('@vite-plus-linux-riscv64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -300,7 +390,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-linux-ppc64-gnu') + const binding = require('@vite-plus-linux-ppc64-gnu') + const bindingPackageVersion = require('@vite-plus-linux-ppc64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -311,7 +406,12 @@ function requireNative() { loadErrors.push(e) } try { - return require('@vite-plus-linux-s390x-gnu') + const binding = require('@vite-plus-linux-s390x-gnu') + const bindingPackageVersion = require('@vite-plus-linux-s390x-gnu/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } @@ -321,34 +421,49 @@ function requireNative() { } else if (process.platform === 'openharmony') { if (process.arch === 'arm64') { try { - return require('./vite-plus.linux-arm64-ohos.node') + return require('./vite-plus.openharmony-arm64.node') } catch (e) { loadErrors.push(e) } try { - return require('@vite-plus-linux-arm64-ohos') + const binding = require('@vite-plus-openharmony-arm64') + const bindingPackageVersion = require('@vite-plus-openharmony-arm64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } } else if (process.arch === 'x64') { try { - return require('./vite-plus.linux-x64-ohos.node') + return require('./vite-plus.openharmony-x64.node') } catch (e) { loadErrors.push(e) } try { - return require('@vite-plus-linux-x64-ohos') + const binding = require('@vite-plus-openharmony-x64') + const bindingPackageVersion = require('@vite-plus-openharmony-x64/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } } else if (process.arch === 'arm') { try { - return require('./vite-plus.linux-arm-ohos.node') + return require('./vite-plus.openharmony-arm.node') } catch (e) { loadErrors.push(e) } try { - return require('@vite-plus-linux-arm-ohos') + const binding = require('@vite-plus-openharmony-arm') + const bindingPackageVersion = require('@vite-plus-openharmony-arm/package.json').version + if (bindingPackageVersion !== '0.0.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding } catch (e) { loadErrors.push(e) } diff --git a/packages/cli/binding/src/lib.rs b/packages/cli/binding/src/lib.rs index cc371e0ca5..c9b5df28d9 100644 --- a/packages/cli/binding/src/lib.rs +++ b/packages/cli/binding/src/lib.rs @@ -1,23 +1,66 @@ +use std::collections::HashMap; use std::env::current_dir; use std::path::PathBuf; +use std::sync::Arc; use clap::Parser as _; -use napi::bindgen_prelude::*; +use napi::{bindgen_prelude::*, threadsafe_function::ThreadsafeFunction}; use napi_derive::napi; -use vite_task::Args; +use vite_error::Error; +use vite_task::{Args, CliOptions as ViteTaskCliOptions, ResolveCommandResult}; #[napi_derive::module_init] pub fn init() { vite_task::init_tracing(); } +#[napi(object, object_to_js = false)] +pub struct CliOptions { + pub lint: Arc>>, + pub cwd: Option, +} + +#[napi(object, object_to_js = false)] +pub struct JsCommandResolvedResult { + pub bin_path: String, + pub envs: HashMap, +} + +impl From for ResolveCommandResult { + fn from(value: JsCommandResolvedResult) -> Self { + ResolveCommandResult { bin_path: value.bin_path, envs: value.envs } + } +} + #[napi] -pub async fn run(cwd: Option) -> Result<()> { +pub async fn run(options: CliOptions) -> Result<()> { let args = Args::parse_from(std::env::args_os().skip(1)); - let cwd = if let Some(cwd) = cwd { PathBuf::from(cwd) } else { current_dir()? }; - vite_task::main(cwd, args) - .await - .map_err(|err| napi::Error::new(Status::GenericFailure, err.to_string()))?; + let cwd = if let Some(cwd) = options.cwd { PathBuf::from(cwd) } else { current_dir()? }; + let lint = options.lint; + + if let Err(e) = vite_task::main( + cwd, + args, + Some(ViteTaskCliOptions { + lint: || async { + let resolved = lint + .call_async(Ok(())) + .await + .map_err(js_error_to_lint_error)? + .await + .map_err(js_error_to_lint_error)?; + Ok(resolved.into()) + }, + }), + ) + .await + { + return Err(napi::Error::new(Status::GenericFailure, e.to_string())); + } Ok(()) } + +fn js_error_to_lint_error(err: napi::Error) -> Error { + Error::LintFailed { status: err.status.to_string(), reason: err.to_string() } +} diff --git a/packages/cli/package.json b/packages/cli/package.json index 1002439a35..c756624c3a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -17,7 +17,7 @@ "scripts": { "build": "pnpm run build:binding && pnpm run build:ts", "build:binding": "napi build --package-json-path ../package.json --cwd binding --platform --release --esm", - "build:ts": "tsdown --outDir dist src/index.ts" + "build:ts": "tsc -p ./tsconfig.json" }, "files": [ "bin", @@ -25,6 +25,7 @@ "index.d.ts" ], "dependencies": { + "oxlint": "catalog:", "vite": "catalog:", "vitest": "catalog:" }, diff --git a/packages/cli/src/bin.ts b/packages/cli/src/bin.ts new file mode 100644 index 0000000000..245fba2efd --- /dev/null +++ b/packages/cli/src/bin.ts @@ -0,0 +1,7 @@ +import { lint } from './lint.js' + +import { run } from '../binding/index.js' + +run({ + lint, +}) diff --git a/packages/cli/src/lint.ts b/packages/cli/src/lint.ts new file mode 100644 index 0000000000..26a76684e1 --- /dev/null +++ b/packages/cli/src/lint.ts @@ -0,0 +1,22 @@ +import { createRequire } from 'node:module' + +const require = createRequire(import.meta.url) + +export async function lint(): Promise<{ + binPath: string + envs: Record +}> { + const binPath = require.resolve('oxlint/bin/oxlint') + const bin = require.resolve(binPath, { + paths: [require.resolve('oxlint/package.json')], + }) + return { + binPath: bin, + // TODO: provide envs inference API + envs: { + JS_RUNTIME_VERSION: process.versions.node, + JS_RUNTIME_NAME: process.release.name, + NODE_PACKAGE_MANAGER: 'vite-plus', + }, + } +} diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 3521ee5ec3..995512ac6f 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -4,7 +4,9 @@ "module": "NodeNext", "moduleResolution": "NodeNext", "composite": true, - "outDir": "./dist" + "outDir": "./dist", + "noEmit": false, + "rootDir": "./src" }, "include": ["src"], "exclude": ["node_modules", "dist"] diff --git a/packages/cli/tsdown.config.ts b/packages/cli/tsdown.config.ts deleted file mode 100644 index c4dcd03be0..0000000000 --- a/packages/cli/tsdown.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { defineConfig } from 'tsdown'; - -export default defineConfig({ - entry: ['./src'], - dts: true, -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c710b43807..201dfe8d77 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,14 +7,14 @@ settings: catalogs: default: '@napi-rs/cli': - specifier: ^3.0.3 - version: 3.0.3 + specifier: ^3.1.3 + version: 3.1.3 '@oxc-node/cli': - specifier: ^0.0.30 - version: 0.0.30 + specifier: ^0.0.32 + version: 0.0.32 '@oxc-node/core': - specifier: ^0.0.30 - version: 0.0.30 + specifier: ^0.0.32 + version: 0.0.32 '@types/node': specifier: ^22.16.4 version: 22.16.4 @@ -27,6 +27,9 @@ catalogs: next: specifier: ^15.4.3 version: 15.4.3 + oxlint: + specifier: ^1.11.1 + version: 1.11.1 react: specifier: ^19.1.0 version: 19.1.0 @@ -37,11 +40,11 @@ catalogs: specifier: ^0.12.9 version: 0.12.9 typescript: - specifier: ^5.8.3 - version: 5.8.3 + specifier: ^5.9.2 + version: 5.9.2 vite: - specifier: ^7.0.5 - version: 7.0.5 + specifier: ^7.1.1 + version: 7.1.1 vitest: specifier: ^3.2.4 version: 3.2.4 @@ -52,10 +55,10 @@ importers: devDependencies: '@oxc-node/cli': specifier: 'catalog:' - version: 0.0.30 + version: 0.0.32 '@oxc-node/core': specifier: 'catalog:' - version: 0.0.30 + version: 0.0.32 '@types/node': specifier: 'catalog:' version: 22.16.4 @@ -66,11 +69,11 @@ importers: specifier: ^16.1.2 version: 16.1.2 oxlint: - specifier: ^1.8.0 - version: 1.8.0 + specifier: 'catalog:' + version: 1.11.1 typescript: specifier: 'catalog:' - version: 5.8.3 + version: 5.9.2 vite-plus: specifier: workspace:* version: link:packages/cli @@ -79,29 +82,32 @@ importers: devDependencies: vitepress: specifier: ^1.6.3 - version: 1.6.3(@algolia/client-search@5.33.0)(@types/node@22.16.4)(postcss@8.5.6)(search-insights@2.17.3)(typescript@5.8.3) + version: 1.6.3(@algolia/client-search@5.33.0)(@types/node@22.16.4)(postcss@8.5.6)(search-insights@2.17.3)(typescript@5.9.2) packages/cli: dependencies: + oxlint: + specifier: 'catalog:' + version: 1.11.1 vite: specifier: 'catalog:' - version: 7.0.5(@types/node@22.16.4)(jiti@2.4.2)(yaml@2.8.0) + version: 7.1.1(@types/node@22.16.4)(jiti@2.4.2)(yaml@2.8.0) vitest: specifier: 'catalog:' version: 3.2.4(@types/node@22.16.4)(jiti@2.4.2)(yaml@2.8.0) devDependencies: '@napi-rs/cli': specifier: 'catalog:' - version: 3.0.3(@emnapi/runtime@1.4.5)(@types/node@22.16.4) + version: 3.1.3(@emnapi/runtime@1.4.5)(@types/node@22.16.4) tsdown: specifier: 'catalog:' - version: 0.12.9(typescript@5.8.3) + version: 0.12.9(typescript@5.9.2) packages/global: devDependencies: tsdown: specifier: 'catalog:' - version: 0.12.9(typescript@5.8.3) + version: 0.12.9(typescript@5.9.2) packages/global/templates/minimal: devDependencies: @@ -116,7 +122,7 @@ importers: version: 22.16.4 typescript: specifier: 'catalog:' - version: 5.8.3 + version: 5.9.2 vite-plus: specifier: workspace:* version: link:../../../cli @@ -165,10 +171,10 @@ importers: version: 22.16.4 tsdown: specifier: 'catalog:' - version: 0.12.9(typescript@5.8.3) + version: 0.12.9(typescript@5.9.2) typescript: specifier: 'catalog:' - version: 5.8.3 + version: 5.9.2 vite-plus: specifier: workspace:* version: link:../../../../../cli @@ -181,7 +187,7 @@ importers: devDependencies: tsdown: specifier: 'catalog:' - version: 0.12.9(typescript@5.8.3) + version: 0.12.9(typescript@5.9.2) packages: @@ -875,8 +881,8 @@ packages: '@jridgewell/trace-mapping@0.3.29': resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} - '@napi-rs/cli@3.0.3': - resolution: {integrity: sha512-5PxtOcax9PoOriYWYf8BFNgOzUnsogYUDRM8p2leT2VtG/NTMZCyoRQMzSMB0qEaw+Durkefc5jXCpEF6wrWbA==} + '@napi-rs/cli@3.1.3': + resolution: {integrity: sha512-4FOnH3cLPPKaFbngu5K5frlhpPwss3JHhHcwMA/zMWHYSe5krCFiYc0itxLfU2cI//Y0AjIloC+LzoPewBUx1g==} engines: {node: '>= 16'} hasBin: true peerDependencies: @@ -1121,6 +1127,9 @@ packages: '@napi-rs/wasm-runtime@1.0.1': resolution: {integrity: sha512-KVlQ/jgywZpixGCKMNwxStmmbYEMyokZpCf2YuIChhfJA2uqfAKNEM8INz7zzTo55iEXfBhIIs3VqYyqzDLj8g==} + '@napi-rs/wasm-runtime@1.0.3': + resolution: {integrity: sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q==} + '@napi-rs/wasm-tools-android-arm-eabi@1.0.0': resolution: {integrity: sha512-Ks0hplmrYatIjSi8XeTObCi0x13AOQD41IQXpBjrz+UK71gDkbxyLWO7B/ckuels3mC1DW3OCQCv+q0lPnaG/A==} engines: {node: '>= 10'} @@ -1305,97 +1314,97 @@ packages: '@octokit/types@14.1.0': resolution: {integrity: sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==} - '@oxc-node/cli@0.0.30': - resolution: {integrity: sha512-L3eOoZV3m+uxb00UbM1iZIKUqOOmb7EtH0sOD5LBzOwLdrMGo/ePw/IyctmTo/PlqVnz39xt2P0nLnMg3K68jA==} + '@oxc-node/cli@0.0.32': + resolution: {integrity: sha512-D45a6bbKJpWcd7WDqY5Bmq1UXa604wY1wvC/nLfKOCbf4n20fGs5XwUl9wkB7KoloXWOva1GqApHuHaz24DQqg==} hasBin: true - '@oxc-node/core-android-arm-eabi@0.0.30': - resolution: {integrity: sha512-4lEVL1zDCBZXrYlnyRXpdSQJLNgyXzHVEw6fwCNlUeyb+y1/n3OyRevism3TwqcsKJeaMtaTcXLrpBuR3bVI7g==} + '@oxc-node/core-android-arm-eabi@0.0.32': + resolution: {integrity: sha512-Ykkz+xYJ1Hd+vjVSXljyIFRW/ZRMp2tQfPy9T2jj9F2pm5CAchqJHSXEbB3FbhdUXusUqhWLmu70JdVhS4SBcQ==} cpu: [arm] os: [android] - '@oxc-node/core-android-arm64@0.0.30': - resolution: {integrity: sha512-7ON14ZTTxNORzCcOqT2jSL1HscuW+9KeCz21dlHnp7V4umkGSX3i82gFTK1foxWmDGGrAq3YQuryQMyvBIcKcg==} + '@oxc-node/core-android-arm64@0.0.32': + resolution: {integrity: sha512-csPEZPGTlPtkoMnDeG+hrYX2AJIh8dPKW7TW5E0MdNMIkJ8aFIymBH2QF+xSMsFl9Gpbl5ZxYLkRIrth+Mhzhg==} cpu: [arm64] os: [android] - '@oxc-node/core-darwin-arm64@0.0.30': - resolution: {integrity: sha512-N7NKKv8nlVtKJMwNrS91/ZJA6Ross1dvwiinMM0lYhcl9hW4vvG9e/Qoso4Y37fYNfCklK0I5o41licxr9RFkQ==} + '@oxc-node/core-darwin-arm64@0.0.32': + resolution: {integrity: sha512-wlURG+gBge3ovdBtMQ/KvyFsLVXnQ4h5vlIxBYMCTkwCzwvfXbcSOLLZMxypOeXPlH/cdVqOigvh9XBbc3KAfg==} cpu: [arm64] os: [darwin] - '@oxc-node/core-darwin-x64@0.0.30': - resolution: {integrity: sha512-9N3dne3S7oAyycuJb5m8j40Um3dzV6voDEaZy8srlIzSl/d9u0DMnEvI0LJEggHI2RfwRXHfFfhqU+6CpO8ssA==} + '@oxc-node/core-darwin-x64@0.0.32': + resolution: {integrity: sha512-tFlUz54cemNp6N7+suP+uwCXErzol7ibMslxOHbXtGWwRVckV3Bb1rQyCajFSQYCr/du1ZdDCVsTfuPmXQlejg==} cpu: [x64] os: [darwin] - '@oxc-node/core-freebsd-x64@0.0.30': - resolution: {integrity: sha512-g7qAX+ZYBUprkLrdvg57/yO1jAEnxQq3ZvI9y0V80W5u5E72qvMAUW8yDV3xxqaqSjlHNUu4h47NFNAadmHqjQ==} + '@oxc-node/core-freebsd-x64@0.0.32': + resolution: {integrity: sha512-yqikvHq0VbnPasRwOkDiMe5dhLZexvZSBzNroBVGbMWNaHBvkP+GP4EFZ3Y5pcNPC3og3xP4J6+N8bbbEZk3vg==} cpu: [x64] os: [freebsd] - '@oxc-node/core-linux-arm-gnueabihf@0.0.30': - resolution: {integrity: sha512-cpCpfGB2NyWRhj17ctz9bJEEljEhPQtBUz4aTFOOhYskAcBibhRfs1fd1ZDCne6wdJ9fHKYwcXBIAeN+ctpuNg==} + '@oxc-node/core-linux-arm-gnueabihf@0.0.32': + resolution: {integrity: sha512-Qbv937NEH4gNNC1W3Lx6G4Mh5mETMYSf2jbTxCzcfng74BVorpQd0pKB72bvEA0RQtIkXoEfCW24VeyR3yA84Q==} cpu: [arm] os: [linux] - '@oxc-node/core-linux-arm64-gnu@0.0.30': - resolution: {integrity: sha512-hMbwadIi4pvBFGNZHuCQ02h3SBigA9pAqdrfZBxYAga5eWyaFSn8M6wNRs/+82UAgoF6qJ0AAvHvvEYkm3zoHw==} + '@oxc-node/core-linux-arm64-gnu@0.0.32': + resolution: {integrity: sha512-w+6SmMWEaVap5RC9axCvg1ZOFdQsNLlLYrYrKX5siZvcVs6jPNyg3O9rbO85tBFPvuAvSLr66mBJwW70MQJv0w==} cpu: [arm64] os: [linux] - '@oxc-node/core-linux-arm64-musl@0.0.30': - resolution: {integrity: sha512-Cd3ryfct/4oERUFqN5RsCLx0scDEfkmYjgnriwyFQfxxceQbRDJP+DlHr9KbN5Fqce6pg0JHlicR8tXcwMNmaw==} + '@oxc-node/core-linux-arm64-musl@0.0.32': + resolution: {integrity: sha512-AZeVCYaECJbQaj6bxCZK77ApbAQUKNOq4wWoRIkVZnn9/ay5wqHLmkvWdOku0aw2WkGiteLp5nCXSDZjw1g0/A==} cpu: [arm64] os: [linux] - '@oxc-node/core-linux-arm64-ohos@0.0.30': - resolution: {integrity: sha512-m/ja7wRpbrEk2es71ZjUzXTvJUkMdcqHW/mgLWFI5Smll2F80cLYG3CEbNv2ZX9hIn8Oj8r3y88wwy40r0QdKQ==} - cpu: [arm64] - os: [openharmony] - - '@oxc-node/core-linux-ppc64-gnu@0.0.30': - resolution: {integrity: sha512-9ENhCe2Ql83NUT1bHZOgg2cdQRZFAqwll1fHw86qoQKTIt1gHlEG6SR3t6X+eSZMcLvas+mrZsuhOrYkb0HGRA==} + '@oxc-node/core-linux-ppc64-gnu@0.0.32': + resolution: {integrity: sha512-sz8r3BPLWZC5vZ/5UCb2MhcEFT8bRIeo3IFnksh6K9umAyjgByxnBZP/EyL4twcjQqbrDC64Ox0Pm5vtTIiFWw==} cpu: [ppc64] os: [linux] - '@oxc-node/core-linux-s390x-gnu@0.0.30': - resolution: {integrity: sha512-84KrSoXEQ/Un/06LqJXP3Mj/BnarDnrv8wOA+k90iRp5P/NtFTO53fJZ2dgKC0HVYNc2l1AAgdf14+bCpzhFvA==} + '@oxc-node/core-linux-s390x-gnu@0.0.32': + resolution: {integrity: sha512-YLpDVEcsvj03z6SqqcxA1J+ocTR+pP6dv0oIrGXaATwQtx0C+nt6WrVESaXjJpQDAc/5I+LkVDlimeAJb4iLNQ==} cpu: [s390x] os: [linux] - '@oxc-node/core-linux-x64-gnu@0.0.30': - resolution: {integrity: sha512-pXWFGsoh0K6ENr+6udIs3K5RMPj1mSbMz3jtgMWdRtr6KWB+9XyxgZb6GCm+oDJRM5r6RTS1BnJHpJeRksbkXw==} + '@oxc-node/core-linux-x64-gnu@0.0.32': + resolution: {integrity: sha512-FCBaQq4Y08AOLvl83AVzGeRAp/7RI49sDb44sE3/XzxdAVHYyhp0wA84h/NBi8Cj8XhSJdr6KosskHl1cVB0Qg==} cpu: [x64] os: [linux] - '@oxc-node/core-linux-x64-musl@0.0.30': - resolution: {integrity: sha512-i06eWCplD17set/HoGS+OwZyiiATuepCI0eG+sBbBBVxkoHg200fDD6Q7kZt0i9nRfM/jvyYqwuiQnz4hGHMDw==} + '@oxc-node/core-linux-x64-musl@0.0.32': + resolution: {integrity: sha512-QKvj6d7VGKK3udW8TXfB0NYNjCloKxZznl58eaoFMoO+bOwuOWJX/8tN37FjkghH4Sb38vRK24+BdqebAHjBdA==} cpu: [x64] os: [linux] - '@oxc-node/core-wasm32-wasi@0.0.30': - resolution: {integrity: sha512-1prU4mKZuFgmPf02TeTbCYXEjyQP5/s3YN1yN5Kk/+Wh3AT0V4pNG7JMGaZ2Kt17OyitbaeaI+0+4rbM+RdZQA==} + '@oxc-node/core-openharmony-arm64@0.0.32': + resolution: {integrity: sha512-k8wqQZ04J4l44v6iB7VFVNc+tz0BBJV2nxSwhLTHpciAFZnOG4BJUqw+2OsObUreR3z5zarebkVFyEJ3QaUH5g==} + cpu: [arm64] + os: [openharmony] + + '@oxc-node/core-wasm32-wasi@0.0.32': + resolution: {integrity: sha512-O8Bj67iC0lm0xSlpmHseU+tQytrqdhDFUc0MzYmPSvs0i60u3CNGiw/BWuO6xDpBfhp/IQkWSGBrecZZt1hMFg==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@oxc-node/core-win32-arm64-msvc@0.0.30': - resolution: {integrity: sha512-BW8ABii4zS918hD1HO6E1o9GpB696YsfivvNb33RR5SdylVIXAnD/c0sKyhn083Wcfjb2y15FkPBqf4TxBppbg==} + '@oxc-node/core-win32-arm64-msvc@0.0.32': + resolution: {integrity: sha512-mGFu9B3E/jBqvy6FzTZHjUZUOOst1dnN/LIX1Bi8wbSrB+1TdOBykOcNJ3F+BppdaTCWqKb6ieLU6BvvnxboaA==} cpu: [arm64] os: [win32] - '@oxc-node/core-win32-ia32-msvc@0.0.30': - resolution: {integrity: sha512-7Lu1KSj+JlDXKRIXhDhBCakv7nvTEE+URmA14US5VwWnTBtdBxKPnfepd/1Or2QU6QIBYPRJnytGb/oPKEJX4A==} + '@oxc-node/core-win32-ia32-msvc@0.0.32': + resolution: {integrity: sha512-JEk4P/c/x6T4MHPBtty7IdCx/hxqhdhLrrJ0pIQ8Ue1Kkx74Aq6NDPg6TWtHAxmn+4cw6c1TLzGPXW+1dwbFcA==} cpu: [ia32] os: [win32] - '@oxc-node/core-win32-x64-msvc@0.0.30': - resolution: {integrity: sha512-/fsUKcfrDeJSu5kzsF56J+9arvZXMmV1NE9dEW6uC8DbI4OpfO7gCV4CHwodjCkuMP6nZRWq5tmGAHGALAcloA==} + '@oxc-node/core-win32-x64-msvc@0.0.32': + resolution: {integrity: sha512-6G0YmfxjD3Ec8G619VXfJ3luryJb1Fq649H8ZcfLO8810WLwtzZiAXZCsCLs8lPsLTEpTeFR+AWuc8v4IRHekg==} cpu: [x64] os: [win32] - '@oxc-node/core@0.0.30': - resolution: {integrity: sha512-lZeV5b9gtqnKr9YxDoYLglt2d9Hq39r4K4XRDRLPd1UTjNXkSUVRpiFDweB/emhIt03EZzwgoh9RxFwpMTWVCg==} + '@oxc-node/core@0.0.32': + resolution: {integrity: sha512-2lbEquSd7qU5SZwbu2ngxs/vaa5sgRB5FE6TSPIPp6wfy7+11M0OrzD3g9TxH5CcsawecF2pC8nNpRVjBgBhOg==} '@oxc-project/runtime@0.77.0': resolution: {integrity: sha512-cMbHs/DaomWSjxeJ79G10GA5hzJW9A7CZ+/cO+KuPZ7Trf3Rr07qSLauC4Ns8ba4DKVDjd8VSC9nVLpw6jpoGQ==} @@ -1404,43 +1413,67 @@ packages: '@oxc-project/types@0.77.0': resolution: {integrity: sha512-iUQj185VvCPnSba+ltUV5tVDrPX6LeZVtQywnnoGbe4oJ1VKvDKisjGkD/AvVtdm98b/BdsVS35IlJV1m2mBBA==} - '@oxlint/darwin-arm64@1.8.0': - resolution: {integrity: sha512-1juYJF1xqRNkswzDSN1V44NoZ+O2Mkc9LjbkDB/UErb8dxTqFhCZC3CQR6Em55/tys1FtajXgK3B+ykWnY9HNQ==} + '@oxlint-tsgolint/darwin-arm64@0.0.1': + resolution: {integrity: sha512-DhPovgw2MVvQhU4uyrrgBUqRkmh6V66zItbpWu352B0f9LW0tFm2cXcTDR1QkDo1m9B6YV7qR9IPI2/q7gLDeA==} + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@0.0.1': + resolution: {integrity: sha512-/T+STn40ebdHM7Cz6bVITynBHNWnRGKyz8CcbUDdiehhD0CTlwwNeKCKA4beQ1t/Dvc3JG6pV8dwv/2cmd+DYg==} + os: [darwin] + + '@oxlint-tsgolint/linux-arm64@0.0.1': + resolution: {integrity: sha512-xp8KdgxyqLB31ISPep0LDJgyE3ucxcLc8LdlqjO+Z9k2RR0MkFs1bgpXROn5KX1eZGO5OYRdbgVX+s43TI7Dhw==} + os: [linux] + + '@oxlint-tsgolint/linux-x64@0.0.1': + resolution: {integrity: sha512-6OG1DFbg1xP5UmuWynUnVkay5WbH44aiwDhIAIR8+V50Ijzzyx87CGxKbn1k0YsfaQIji5SgZi0WMiJQ5LTR2A==} + os: [linux] + + '@oxlint-tsgolint/win32-arm64@0.0.1': + resolution: {integrity: sha512-szngA1G3b9DyO6NZ7qUBl8EXBt+9F6cLQ4/kdiIUwk+5LELCjFbpg2s7eGPYZaCJjf30Vo9GlTqe5HFjddvrXg==} + os: [win32] + + '@oxlint-tsgolint/win32-x64@0.0.1': + resolution: {integrity: sha512-anzDRE1w3Vl/aSBLiOwnwzz17GJaXJr+X3OTWwwV9cHg+TWEhLpUDo+d2pAoulCHoZTjN/k8A9wHg0IGuqnOfA==} + os: [win32] + + '@oxlint/darwin-arm64@1.11.1': + resolution: {integrity: sha512-/9ohoWJoPHLXDBRQqPn3jIwB2QXt+Pv6dR1r6fmrcm5RiTyqf40zK10gMDl8rbw8ONmzgrevdxweV0iRuC9pcQ==} cpu: [arm64] os: [darwin] - '@oxlint/darwin-x64@1.8.0': - resolution: {integrity: sha512-5b7J/XE2eGhx3+vw6IFuuL0BbIF3wRzo4SWHVXN9rO3WYq2YpoHToY4C5WMWb8toVZcoJlx4Y1lq3IO2V78zTg==} + '@oxlint/darwin-x64@1.11.1': + resolution: {integrity: sha512-snwKjdJO+mGwp2xYBxdwJorad4ZVeZDq2FV3ZcgvqCmf6pg+eJdm/jtlfXb44KfEDlBCvMa0zV48bjY+tg31rA==} cpu: [x64] os: [darwin] - '@oxlint/linux-arm64-gnu@1.8.0': - resolution: {integrity: sha512-pzfk9IZBbYuIYn4sbT//Vox8B8e8hOZPkIQnNAdzhpGtRjV4NYOgNL5/h2QZC+ecmxl8H+Gi9WV6dyKjFrBtcw==} + '@oxlint/linux-arm64-gnu@1.11.1': + resolution: {integrity: sha512-WI3LcUlowzlu2wQb0XRIq1Zv1AMkQDrU7+zHVtiokFbjyioUcSOomwN+ntlZozPwFRbuLUXL+wq5qePnLhE90Q==} cpu: [arm64] os: [linux] - '@oxlint/linux-arm64-musl@1.8.0': - resolution: {integrity: sha512-6rpaeAG271wbUNM+WeJhdvJDDMwfoenm7rPY304dxnC+fcuR8Q0LSv09dGeNWrsqjjZuDP9R10qR154nysBxFg==} + '@oxlint/linux-arm64-musl@1.11.1': + resolution: {integrity: sha512-Fc4gW7nf8dGOvsvTqIJDCvWiDCxQtnX3KDM69G+0d23ljiXdi2/EEcWNcvobBwi0CqlpO+TdQiRYDLHEJxSRfA==} cpu: [arm64] os: [linux] - '@oxlint/linux-x64-gnu@1.8.0': - resolution: {integrity: sha512-qPEF8tKMu+63b58gPfwU3KyJf2z9KyorbrC0yGXFHQLzRPEtrh6bAjf+AzCs3n8WhDR1K6jPgcPT4Sp8bahCyQ==} + '@oxlint/linux-x64-gnu@1.11.1': + resolution: {integrity: sha512-3UNP5PAE/IHWMSfrwybMbjppQXMveFzLMMZjGk8tEAm9aj4yeX6Z3O+ns6fQHStbb6PyrDMncAyNl/qxUfmp7A==} cpu: [x64] os: [linux] - '@oxlint/linux-x64-musl@1.8.0': - resolution: {integrity: sha512-JyErk/LsLg/tA3XkHhU8VIxahOdq56L99mbpMFGLTkOQgtnhY2MDAYULVgOuFFX3v6Q02o4mpIR/SwW/tRnZlg==} + '@oxlint/linux-x64-musl@1.11.1': + resolution: {integrity: sha512-CmRm8rFT6MwKvcpcw1IM+QHtyh3iOKRewJn3VOHYlRzw+VKQuvUbUnWHgROtr6V8UOiRkrSy3Mvo/d5Fc+r/lw==} cpu: [x64] os: [linux] - '@oxlint/win32-arm64@1.8.0': - resolution: {integrity: sha512-QvhtDAU9bBdC2m5xO+ibKyMG4KZR44wB0vDbQ5YkQxJiuXrlleHLyz0+saFzVYQ/Fvc0QgIRTIwiVz9dzxidVw==} + '@oxlint/win32-arm64@1.11.1': + resolution: {integrity: sha512-htSR1NHMnRV5qXDoZJcFxKObgLyBvA4g9eIhzDTsTyw7e2ZDR/SVOli2IZB31ghCwEYcSV7yoZgfOgjNbgvDJA==} cpu: [arm64] os: [win32] - '@oxlint/win32-x64@1.8.0': - resolution: {integrity: sha512-veXJXgF905UOvuxtmvzM328b4Itm8Fyu+lUq4PagXOmyRScevaVUXq6++ui3A/Gxd8yo0SHspHCbYkpuvJkXqQ==} + '@oxlint/win32-x64@1.11.1': + resolution: {integrity: sha512-yjWjx0kf6+osvXdqdkLmSwPxvN9bJ+0sr+KLEgxCAZxfqy9nNTuflIwk6x5sm6lS4+qzCY7RrMYcksQ6Ky+0CA==} cpu: [x64] os: [win32] @@ -2022,6 +2055,9 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-toolkit@1.39.9: + resolution: {integrity: sha512-9OtbkZmTA2Qc9groyA1PUNeb6knVTkvB2RSdr/LcJXDL8IdEakaxwXLHXa7VX/Wj0GmdMJPR3WhnPGhiP3E+qg==} + esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} @@ -2163,9 +2199,6 @@ packages: resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - log-update@6.1.0: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} @@ -2259,8 +2292,12 @@ packages: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} - oxlint@1.8.0: - resolution: {integrity: sha512-kDC3zuplBM35GbrZ/3rRdDrZ6unpUkUjM8P3VSbyLgaYh2xZeg0TLLDbYALNAUyChVonNafXzgHZmbwnHfrTRg==} + oxlint-tsgolint@0.0.1: + resolution: {integrity: sha512-gH6EpIr2oBVperOONVzTTNYmOu6pYPipVyzWB+CfYO13vH+6O9kINUyG4rr3RXDaKdTx8UvxgRORXdSGH1iydA==} + hasBin: true + + oxlint@1.11.1: + resolution: {integrity: sha512-24c8BHX2deW0H7GAjkHP28+W4P3qO+2l0R/wz8jj+D4Sq/eC+K2Zyu6uN3bVXeHfG3z4U8shWD5hE+yPMisPDA==} engines: {node: '>=8.*'} hasBin: true @@ -2550,8 +2587,8 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} hasBin: true @@ -2625,8 +2662,8 @@ packages: terser: optional: true - vite@7.0.5: - resolution: {integrity: sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==} + vite@7.1.1: + resolution: {integrity: sha512-yJ+Mp7OyV+4S+afWo+QyoL9jFWD11QFH0i5i7JypnfTcA1rmgxCbiA8WwAICDEtZ1Z1hzrVhN8R8rGTqkTY8ZQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2713,9 +2750,6 @@ packages: typescript: optional: true - wasm-sjlj@1.0.6: - resolution: {integrity: sha512-pjaKtLJejlWm6+okPV2X1A6nIsRDD4qeK97eCh8DP8KXi3Nzn/HY01vpHhZHlhDri12eZqipjm8HhdTVw+ATxw==} - why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -3298,7 +3332,7 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.4 - '@napi-rs/cli@3.0.3(@emnapi/runtime@1.4.5)(@types/node@22.16.4)': + '@napi-rs/cli@3.1.3(@emnapi/runtime@1.4.5)(@types/node@22.16.4)': dependencies: '@inquirer/prompts': 7.6.0(@types/node@22.16.4) '@napi-rs/cross-toolchain': 1.0.0 @@ -3307,12 +3341,11 @@ snapshots: clipanion: 4.0.0-rc.4(typanion@3.14.0) colorette: 2.0.20 debug: 4.4.1 + es-toolkit: 1.39.9 find-up: 7.0.0 js-yaml: 4.1.0 - lodash-es: 4.17.21 semver: 7.7.2 typanion: 3.14.0 - wasm-sjlj: 1.0.6 optionalDependencies: '@emnapi/runtime': 1.4.5 transitivePeerDependencies: @@ -3489,6 +3522,13 @@ snapshots: '@tybys/wasm-util': 0.10.0 optional: true + '@napi-rs/wasm-runtime@1.0.3': + dependencies: + '@emnapi/core': 1.4.5 + '@emnapi/runtime': 1.4.5 + '@tybys/wasm-util': 0.10.0 + optional: true + '@napi-rs/wasm-tools-android-arm-eabi@1.0.0': optional: true @@ -3634,111 +3674,129 @@ snapshots: dependencies: '@octokit/openapi-types': 25.1.0 - '@oxc-node/cli@0.0.30': + '@oxc-node/cli@0.0.32': dependencies: - '@oxc-node/core': 0.0.30 + '@oxc-node/core': 0.0.32 - '@oxc-node/core-android-arm-eabi@0.0.30': + '@oxc-node/core-android-arm-eabi@0.0.32': optional: true - '@oxc-node/core-android-arm64@0.0.30': + '@oxc-node/core-android-arm64@0.0.32': optional: true - '@oxc-node/core-darwin-arm64@0.0.30': + '@oxc-node/core-darwin-arm64@0.0.32': optional: true - '@oxc-node/core-darwin-x64@0.0.30': + '@oxc-node/core-darwin-x64@0.0.32': optional: true - '@oxc-node/core-freebsd-x64@0.0.30': + '@oxc-node/core-freebsd-x64@0.0.32': optional: true - '@oxc-node/core-linux-arm-gnueabihf@0.0.30': + '@oxc-node/core-linux-arm-gnueabihf@0.0.32': optional: true - '@oxc-node/core-linux-arm64-gnu@0.0.30': + '@oxc-node/core-linux-arm64-gnu@0.0.32': optional: true - '@oxc-node/core-linux-arm64-musl@0.0.30': + '@oxc-node/core-linux-arm64-musl@0.0.32': optional: true - '@oxc-node/core-linux-arm64-ohos@0.0.30': + '@oxc-node/core-linux-ppc64-gnu@0.0.32': optional: true - '@oxc-node/core-linux-ppc64-gnu@0.0.30': + '@oxc-node/core-linux-s390x-gnu@0.0.32': optional: true - '@oxc-node/core-linux-s390x-gnu@0.0.30': + '@oxc-node/core-linux-x64-gnu@0.0.32': optional: true - '@oxc-node/core-linux-x64-gnu@0.0.30': + '@oxc-node/core-linux-x64-musl@0.0.32': optional: true - '@oxc-node/core-linux-x64-musl@0.0.30': + '@oxc-node/core-openharmony-arm64@0.0.32': optional: true - '@oxc-node/core-wasm32-wasi@0.0.30': + '@oxc-node/core-wasm32-wasi@0.0.32': dependencies: - '@napi-rs/wasm-runtime': 1.0.1 + '@napi-rs/wasm-runtime': 1.0.3 optional: true - '@oxc-node/core-win32-arm64-msvc@0.0.30': + '@oxc-node/core-win32-arm64-msvc@0.0.32': optional: true - '@oxc-node/core-win32-ia32-msvc@0.0.30': + '@oxc-node/core-win32-ia32-msvc@0.0.32': optional: true - '@oxc-node/core-win32-x64-msvc@0.0.30': + '@oxc-node/core-win32-x64-msvc@0.0.32': optional: true - '@oxc-node/core@0.0.30': + '@oxc-node/core@0.0.32': dependencies: pirates: 4.0.7 optionalDependencies: - '@oxc-node/core-android-arm-eabi': 0.0.30 - '@oxc-node/core-android-arm64': 0.0.30 - '@oxc-node/core-darwin-arm64': 0.0.30 - '@oxc-node/core-darwin-x64': 0.0.30 - '@oxc-node/core-freebsd-x64': 0.0.30 - '@oxc-node/core-linux-arm-gnueabihf': 0.0.30 - '@oxc-node/core-linux-arm64-gnu': 0.0.30 - '@oxc-node/core-linux-arm64-musl': 0.0.30 - '@oxc-node/core-linux-arm64-ohos': 0.0.30 - '@oxc-node/core-linux-ppc64-gnu': 0.0.30 - '@oxc-node/core-linux-s390x-gnu': 0.0.30 - '@oxc-node/core-linux-x64-gnu': 0.0.30 - '@oxc-node/core-linux-x64-musl': 0.0.30 - '@oxc-node/core-wasm32-wasi': 0.0.30 - '@oxc-node/core-win32-arm64-msvc': 0.0.30 - '@oxc-node/core-win32-ia32-msvc': 0.0.30 - '@oxc-node/core-win32-x64-msvc': 0.0.30 + '@oxc-node/core-android-arm-eabi': 0.0.32 + '@oxc-node/core-android-arm64': 0.0.32 + '@oxc-node/core-darwin-arm64': 0.0.32 + '@oxc-node/core-darwin-x64': 0.0.32 + '@oxc-node/core-freebsd-x64': 0.0.32 + '@oxc-node/core-linux-arm-gnueabihf': 0.0.32 + '@oxc-node/core-linux-arm64-gnu': 0.0.32 + '@oxc-node/core-linux-arm64-musl': 0.0.32 + '@oxc-node/core-linux-ppc64-gnu': 0.0.32 + '@oxc-node/core-linux-s390x-gnu': 0.0.32 + '@oxc-node/core-linux-x64-gnu': 0.0.32 + '@oxc-node/core-linux-x64-musl': 0.0.32 + '@oxc-node/core-openharmony-arm64': 0.0.32 + '@oxc-node/core-wasm32-wasi': 0.0.32 + '@oxc-node/core-win32-arm64-msvc': 0.0.32 + '@oxc-node/core-win32-ia32-msvc': 0.0.32 + '@oxc-node/core-win32-x64-msvc': 0.0.32 '@oxc-project/runtime@0.77.0': {} '@oxc-project/types@0.77.0': {} - '@oxlint/darwin-arm64@1.8.0': + '@oxlint-tsgolint/darwin-arm64@0.0.1': + optional: true + + '@oxlint-tsgolint/darwin-x64@0.0.1': + optional: true + + '@oxlint-tsgolint/linux-arm64@0.0.1': + optional: true + + '@oxlint-tsgolint/linux-x64@0.0.1': + optional: true + + '@oxlint-tsgolint/win32-arm64@0.0.1': + optional: true + + '@oxlint-tsgolint/win32-x64@0.0.1': + optional: true + + '@oxlint/darwin-arm64@1.11.1': optional: true - '@oxlint/darwin-x64@1.8.0': + '@oxlint/darwin-x64@1.11.1': optional: true - '@oxlint/linux-arm64-gnu@1.8.0': + '@oxlint/linux-arm64-gnu@1.11.1': optional: true - '@oxlint/linux-arm64-musl@1.8.0': + '@oxlint/linux-arm64-musl@1.11.1': optional: true - '@oxlint/linux-x64-gnu@1.8.0': + '@oxlint/linux-x64-gnu@1.11.1': optional: true - '@oxlint/linux-x64-musl@1.8.0': + '@oxlint/linux-x64-musl@1.11.1': optional: true - '@oxlint/win32-arm64@1.8.0': + '@oxlint/win32-arm64@1.11.1': optional: true - '@oxlint/win32-x64@1.8.0': + '@oxlint/win32-x64@1.11.1': optional: true '@quansync/fs@0.1.3': @@ -3943,10 +4001,10 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-vue@5.2.4(vite@5.4.19(@types/node@22.16.4))(vue@3.5.17(typescript@5.8.3))': + '@vitejs/plugin-vue@5.2.4(vite@5.4.19(@types/node@22.16.4))(vue@3.5.17(typescript@5.9.2))': dependencies: vite: 5.4.19(@types/node@22.16.4) - vue: 3.5.17(typescript@5.8.3) + vue: 3.5.17(typescript@5.9.2) '@vitest/expect@3.2.4': dependencies: @@ -3956,13 +4014,13 @@ snapshots: chai: 5.2.1 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.0.5(@types/node@22.16.4)(jiti@2.4.2)(yaml@2.8.0))': + '@vitest/mocker@3.2.4(vite@7.1.1(@types/node@22.16.4)(jiti@2.4.2)(yaml@2.8.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 7.0.5(@types/node@22.16.4)(jiti@2.4.2)(yaml@2.8.0) + vite: 7.1.1(@types/node@22.16.4)(jiti@2.4.2)(yaml@2.8.0) '@vitest/pretty-format@3.2.4': dependencies: @@ -4054,28 +4112,28 @@ snapshots: '@vue/shared': 3.5.17 csstype: 3.1.3 - '@vue/server-renderer@3.5.17(vue@3.5.17(typescript@5.8.3))': + '@vue/server-renderer@3.5.17(vue@3.5.17(typescript@5.9.2))': dependencies: '@vue/compiler-ssr': 3.5.17 '@vue/shared': 3.5.17 - vue: 3.5.17(typescript@5.8.3) + vue: 3.5.17(typescript@5.9.2) '@vue/shared@3.5.17': {} - '@vueuse/core@12.8.2(typescript@5.8.3)': + '@vueuse/core@12.8.2(typescript@5.9.2)': dependencies: '@types/web-bluetooth': 0.0.21 '@vueuse/metadata': 12.8.2 - '@vueuse/shared': 12.8.2(typescript@5.8.3) - vue: 3.5.17(typescript@5.8.3) + '@vueuse/shared': 12.8.2(typescript@5.9.2) + vue: 3.5.17(typescript@5.9.2) transitivePeerDependencies: - typescript - '@vueuse/integrations@12.8.2(focus-trap@7.6.5)(typescript@5.8.3)': + '@vueuse/integrations@12.8.2(focus-trap@7.6.5)(typescript@5.9.2)': dependencies: - '@vueuse/core': 12.8.2(typescript@5.8.3) - '@vueuse/shared': 12.8.2(typescript@5.8.3) - vue: 3.5.17(typescript@5.8.3) + '@vueuse/core': 12.8.2(typescript@5.9.2) + '@vueuse/shared': 12.8.2(typescript@5.9.2) + vue: 3.5.17(typescript@5.9.2) optionalDependencies: focus-trap: 7.6.5 transitivePeerDependencies: @@ -4083,9 +4141,9 @@ snapshots: '@vueuse/metadata@12.8.2': {} - '@vueuse/shared@12.8.2(typescript@5.8.3)': + '@vueuse/shared@12.8.2(typescript@5.9.2)': dependencies: - vue: 3.5.17(typescript@5.8.3) + vue: 3.5.17(typescript@5.9.2) transitivePeerDependencies: - typescript @@ -4252,6 +4310,8 @@ snapshots: es-module-lexer@1.7.0: {} + es-toolkit@1.39.9: {} + esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -4437,8 +4497,6 @@ snapshots: dependencies: p-locate: 6.0.0 - lodash-es@4.17.21: {} - log-update@6.1.0: dependencies: ansi-escapes: 7.0.0 @@ -4538,16 +4596,27 @@ snapshots: os-tmpdir@1.0.2: {} - oxlint@1.8.0: + oxlint-tsgolint@0.0.1: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 0.0.1 + '@oxlint-tsgolint/darwin-x64': 0.0.1 + '@oxlint-tsgolint/linux-arm64': 0.0.1 + '@oxlint-tsgolint/linux-x64': 0.0.1 + '@oxlint-tsgolint/win32-arm64': 0.0.1 + '@oxlint-tsgolint/win32-x64': 0.0.1 + optional: true + + oxlint@1.11.1: optionalDependencies: - '@oxlint/darwin-arm64': 1.8.0 - '@oxlint/darwin-x64': 1.8.0 - '@oxlint/linux-arm64-gnu': 1.8.0 - '@oxlint/linux-arm64-musl': 1.8.0 - '@oxlint/linux-x64-gnu': 1.8.0 - '@oxlint/linux-x64-musl': 1.8.0 - '@oxlint/win32-arm64': 1.8.0 - '@oxlint/win32-x64': 1.8.0 + '@oxlint/darwin-arm64': 1.11.1 + '@oxlint/darwin-x64': 1.11.1 + '@oxlint/linux-arm64-gnu': 1.11.1 + '@oxlint/linux-arm64-musl': 1.11.1 + '@oxlint/linux-x64-gnu': 1.11.1 + '@oxlint/linux-x64-musl': 1.11.1 + '@oxlint/win32-arm64': 1.11.1 + '@oxlint/win32-x64': 1.11.1 + oxlint-tsgolint: 0.0.1 p-limit@4.0.0: dependencies: @@ -4621,7 +4690,7 @@ snapshots: rfdc@1.4.1: {} - rolldown-plugin-dts@0.13.14(rolldown@1.0.0-beta.27)(typescript@5.8.3): + rolldown-plugin-dts@0.13.14(rolldown@1.0.0-beta.27)(typescript@5.9.2): dependencies: '@babel/generator': 7.28.0 '@babel/parser': 7.28.0 @@ -4633,7 +4702,7 @@ snapshots: get-tsconfig: 4.10.1 rolldown: 1.0.0-beta.27 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - oxc-resolver - supports-color @@ -4833,7 +4902,7 @@ snapshots: trim-lines@3.0.1: {} - tsdown@0.12.9(typescript@5.8.3): + tsdown@0.12.9(typescript@5.9.2): dependencies: ansis: 4.1.0 cac: 6.7.14 @@ -4843,13 +4912,13 @@ snapshots: empathic: 2.0.0 hookable: 5.5.3 rolldown: 1.0.0-beta.27 - rolldown-plugin-dts: 0.13.14(rolldown@1.0.0-beta.27)(typescript@5.8.3) + rolldown-plugin-dts: 0.13.14(rolldown@1.0.0-beta.27)(typescript@5.9.2) semver: 7.7.2 tinyexec: 1.0.1 tinyglobby: 0.2.14 unconfig: 7.3.2 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - '@typescript/native-preview' - oxc-resolver @@ -4862,7 +4931,7 @@ snapshots: type-fest@0.21.3: {} - typescript@5.8.3: {} + typescript@5.9.2: {} unconfig@7.3.2: dependencies: @@ -4916,7 +4985,7 @@ snapshots: debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.0.5(@types/node@22.16.4)(jiti@2.4.2)(yaml@2.8.0) + vite: 7.1.1(@types/node@22.16.4)(jiti@2.4.2)(yaml@2.8.0) transitivePeerDependencies: - '@types/node' - jiti @@ -4940,7 +5009,7 @@ snapshots: '@types/node': 22.16.4 fsevents: 2.3.3 - vite@7.0.5(@types/node@22.16.4)(jiti@2.4.2)(yaml@2.8.0): + vite@7.1.1(@types/node@22.16.4)(jiti@2.4.2)(yaml@2.8.0): dependencies: esbuild: 0.25.8 fdir: 6.4.6(picomatch@4.0.3) @@ -4954,7 +5023,7 @@ snapshots: jiti: 2.4.2 yaml: 2.8.0 - vitepress@1.6.3(@algolia/client-search@5.33.0)(@types/node@22.16.4)(postcss@8.5.6)(search-insights@2.17.3)(typescript@5.8.3): + vitepress@1.6.3(@algolia/client-search@5.33.0)(@types/node@22.16.4)(postcss@8.5.6)(search-insights@2.17.3)(typescript@5.9.2): dependencies: '@docsearch/css': 3.8.2 '@docsearch/js': 3.8.2(@algolia/client-search@5.33.0)(search-insights@2.17.3) @@ -4963,17 +5032,17 @@ snapshots: '@shikijs/transformers': 2.5.0 '@shikijs/types': 2.5.0 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 5.2.4(vite@5.4.19(@types/node@22.16.4))(vue@3.5.17(typescript@5.8.3)) + '@vitejs/plugin-vue': 5.2.4(vite@5.4.19(@types/node@22.16.4))(vue@3.5.17(typescript@5.9.2)) '@vue/devtools-api': 7.7.7 '@vue/shared': 3.5.17 - '@vueuse/core': 12.8.2(typescript@5.8.3) - '@vueuse/integrations': 12.8.2(focus-trap@7.6.5)(typescript@5.8.3) + '@vueuse/core': 12.8.2(typescript@5.9.2) + '@vueuse/integrations': 12.8.2(focus-trap@7.6.5)(typescript@5.9.2) focus-trap: 7.6.5 mark.js: 8.11.1 minisearch: 7.1.2 shiki: 2.5.0 vite: 5.4.19(@types/node@22.16.4) - vue: 3.5.17(typescript@5.8.3) + vue: 3.5.17(typescript@5.9.2) optionalDependencies: postcss: 8.5.6 transitivePeerDependencies: @@ -5007,7 +5076,7 @@ snapshots: dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.0.5(@types/node@22.16.4)(jiti@2.4.2)(yaml@2.8.0)) + '@vitest/mocker': 3.2.4(vite@7.1.1(@types/node@22.16.4)(jiti@2.4.2)(yaml@2.8.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -5025,7 +5094,7 @@ snapshots: tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.0.5(@types/node@22.16.4)(jiti@2.4.2)(yaml@2.8.0) + vite: 7.1.1(@types/node@22.16.4)(jiti@2.4.2)(yaml@2.8.0) vite-node: 3.2.4(@types/node@22.16.4)(jiti@2.4.2)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: @@ -5044,17 +5113,15 @@ snapshots: - tsx - yaml - vue@3.5.17(typescript@5.8.3): + vue@3.5.17(typescript@5.9.2): dependencies: '@vue/compiler-dom': 3.5.17 '@vue/compiler-sfc': 3.5.17 '@vue/runtime-dom': 3.5.17 - '@vue/server-renderer': 3.5.17(vue@3.5.17(typescript@5.8.3)) + '@vue/server-renderer': 3.5.17(vue@3.5.17(typescript@5.9.2)) '@vue/shared': 3.5.17 optionalDependencies: - typescript: 5.8.3 - - wasm-sjlj@1.0.6: {} + typescript: 5.9.2 why-is-node-running@2.3.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3bd8374fef..c954a9e400 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,17 +6,18 @@ packages: - packages/global/templates/monorepo/packages/* catalog: - "@napi-rs/cli": ^3.0.3 - "@oxc-node/core": ^0.0.30 - "@oxc-node/cli": ^0.0.30 + "@napi-rs/cli": ^3.1.3 + "@oxc-node/core": ^0.0.32 + "@oxc-node/cli": ^0.0.32 "@types/node": ^22.16.4 "@types/react": "^19.1.8" "@types/react-dom": "^19.1.6" next: "^15.4.3" react: "^19.1.0" "react-dom": "^19.1.0" + oxlint: ^1.11.1 rolldown: ^1.0.0-beta.27 tsdown: ^0.12.9 - typescript: ^5.8.3 - vite: ^7.0.5 + typescript: ^5.9.2 + vite: ^7.1.1 vitest: ^3.2.4 From 7aca50ed2d77f52d91f96a9f76fecc1671aa5190 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 14 Aug 2025 12:25:45 +0800 Subject: [PATCH 2/9] Add vite build --- crates/vite_task/src/build.rs | 29 +++++++++++++++++++++++++++++ crates/vite_task/src/config/mod.rs | 6 +++--- crates/vite_task/src/lib.rs | 23 ++++++++++++++++++++++- crates/vite_task/src/lint.rs | 3 ++- package.json | 2 +- packages/cli/binding/index.d.ts | 1 + packages/cli/binding/src/lib.rs | 13 ++++++++++++- packages/cli/src/bin.ts | 9 +++++---- packages/cli/src/build.ts | 20 ++++++++++++++++++++ 9 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 crates/vite_task/src/build.rs create mode 100644 packages/cli/src/build.ts diff --git a/crates/vite_task/src/build.rs b/crates/vite_task/src/build.rs new file mode 100644 index 0000000000..2b4a0df03c --- /dev/null +++ b/crates/vite_task/src/build.rs @@ -0,0 +1,29 @@ +use std::future::Future; +use std::iter; + +use petgraph::stable_graph::StableGraph; + +use crate::config::ResolvedTask; +use crate::schedule::ExecutionPlan; +use crate::{Error, ResolveCommandResult, Workspace}; + +pub async fn build< + Build: Future>, + BuildFn: Fn() -> Build, +>( + resolve_build_command: BuildFn, + workspace: &mut Workspace, + args: &Vec, +) -> Result<(), Error> { + let resolved_task = ResolvedTask::resolve_from_built_in( + workspace, + resolve_build_command, + "build", + iter::once(&"build".to_string()).chain(args.iter()), + ) + .await?; + let mut task_graph: StableGraph = Default::default(); + task_graph.add_node(resolved_task); + ExecutionPlan::plan(task_graph, false)?.execute(workspace).await?; + Ok(()) +} diff --git a/crates/vite_task/src/config/mod.rs b/crates/vite_task/src/config/mod.rs index fd350940d2..ab34a1db21 100644 --- a/crates/vite_task/src/config/mod.rs +++ b/crates/vite_task/src/config/mod.rs @@ -104,11 +104,11 @@ impl ResolvedTask { workspace: &Workspace, resolve_command: ResolveFn, task_name: &str, - args: &Vec, + args: impl Iterator> + Clone, ) -> Result { let ResolveCommandResult { bin_path, envs } = resolve_command().await?; let link_task = TaskCommand::Parsed(TaskParsedCommand { - args: args.iter().map(|arg| arg.as_str().into()).collect(), + args: args.clone().map(|arg| arg.as_ref().into()).collect(), envs: envs.into_iter().map(|(k, v)| (k.into(), v.into())).collect(), program: bin_path.into(), }); @@ -132,7 +132,7 @@ impl ResolvedTask { task_group_name: task_name.into(), subcommand_index: None, }, - args: args.iter().map(|arg| arg.as_str().into()).collect(), + args: args.map(|arg| arg.as_ref().into()).collect(), resolved_config: resolved_task_config, resolved_command, }) diff --git a/crates/vite_task/src/lib.rs b/crates/vite_task/src/lib.rs index 18b6d85e9b..38ff728477 100644 --- a/crates/vite_task/src/lib.rs +++ b/crates/vite_task/src/lib.rs @@ -1,3 +1,4 @@ +mod build; mod cache; mod cmd; mod collections; @@ -78,6 +79,11 @@ pub enum Commands { /// Arguments to pass to oxlint args: Vec, }, + Build { + #[clap(last = true)] + /// Arguments to pass to vite build + args: Vec, + }, } /// Resolve boolean flag value considering both positive and negative forms. @@ -92,8 +98,13 @@ pub struct CliOptions< Box>>, >, LintFn: Fn() -> Lint = Box Lint>, + Build: Future> = Pin< + Box>>, + >, + BuildFn: Fn() -> Build = Box Build>, > { pub lint: LintFn, + pub build: BuildFn, } pub struct ResolveCommandResult { @@ -134,10 +145,12 @@ pub struct ResolveCommandResult { pub async fn main< Lint: Future>, LintFn: Fn() -> Lint, + Build: Future>, + BuildFn: Fn() -> Build, >( cwd: PathBuf, args: Args, - options: Option>, + options: Option>, ) -> Result<(), Error> { let mut recursive_run = false; let mut parallel_run = false; @@ -175,6 +188,14 @@ pub async fn main< } return Ok(()); } + Some(Commands::Build { args }) => { + let mut workspace = Workspace::partial_load(cwd)?; + if let Some(build_fn) = options.map(|o| o.build) { + build::build(build_fn, &mut workspace, args).await?; + workspace.unload().await?; + } + return Ok(()); + } None => { let workspace = Workspace::load(cwd, false)?; // in implicit mode, vite-plus will run the task in the current package, replace the `pnpm/yarn/npm run` command. diff --git a/crates/vite_task/src/lint.rs b/crates/vite_task/src/lint.rs index 8770762395..61d629bcf8 100644 --- a/crates/vite_task/src/lint.rs +++ b/crates/vite_task/src/lint.rs @@ -15,7 +15,8 @@ pub async fn lint< args: &Vec, ) -> Result<(), Error> { let resolved_task = - ResolvedTask::resolve_from_built_in(workspace, resolve_lint_command, "lint", args).await?; + ResolvedTask::resolve_from_built_in(workspace, resolve_lint_command, "lint", args.iter()) + .await?; let mut task_graph: StableGraph = Default::default(); task_graph.add_node(resolved_task); ExecutionPlan::plan(task_graph, false)?.execute(workspace).await?; diff --git a/package.json b/package.json index 635674f0a2..3d89c530a8 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "dprint fmt --staged" ], "*.@(js|ts|tsx)": [ - "pnpm lint -- --fix" + "oxlint -- --fix" ], "*.rs": [ "cargo fmt --" diff --git a/packages/cli/binding/index.d.ts b/packages/cli/binding/index.d.ts index fa29bf67e2..158e9f27d3 100644 --- a/packages/cli/binding/index.d.ts +++ b/packages/cli/binding/index.d.ts @@ -2,6 +2,7 @@ /* eslint-disable */ export interface CliOptions { lint: ((err: Error | null, ) => Promise) + build: ((err: Error | null, ) => Promise) cwd?: string } diff --git a/packages/cli/binding/src/lib.rs b/packages/cli/binding/src/lib.rs index c9b5df28d9..b3957207b9 100644 --- a/packages/cli/binding/src/lib.rs +++ b/packages/cli/binding/src/lib.rs @@ -17,6 +17,7 @@ pub fn init() { #[napi(object, object_to_js = false)] pub struct CliOptions { pub lint: Arc>>, + pub build: Arc>>, pub cwd: Option, } @@ -37,7 +38,7 @@ pub async fn run(options: CliOptions) -> Result<()> { let args = Args::parse_from(std::env::args_os().skip(1)); let cwd = if let Some(cwd) = options.cwd { PathBuf::from(cwd) } else { current_dir()? }; let lint = options.lint; - + let build = options.build; if let Err(e) = vite_task::main( cwd, args, @@ -52,6 +53,16 @@ pub async fn run(options: CliOptions) -> Result<()> { Ok(resolved.into()) }, + build: || async { + let resolved = build + .call_async(Ok(())) + .await + .map_err(js_error_to_lint_error)? + .await + .map_err(js_error_to_lint_error)?; + + Ok(resolved.into()) + }, }), ) .await diff --git a/packages/cli/src/bin.ts b/packages/cli/src/bin.ts index 245fba2efd..964accde08 100644 --- a/packages/cli/src/bin.ts +++ b/packages/cli/src/bin.ts @@ -1,7 +1,8 @@ -import { lint } from './lint.js' - -import { run } from '../binding/index.js' +import { run } from '../binding/index.js'; +import { build } from './build.js'; +import { lint } from './lint.js'; run({ lint, -}) + build, +}); diff --git a/packages/cli/src/build.ts b/packages/cli/src/build.ts new file mode 100644 index 0000000000..91700d9817 --- /dev/null +++ b/packages/cli/src/build.ts @@ -0,0 +1,20 @@ +import { createRequire } from 'node:module'; +import { join } from 'node:path'; + +const require = createRequire(import.meta.url); + +export async function build(): Promise<{ + binPath: string; + envs: Record; +}> { + const pkgJsonPath = require.resolve('vite/package.json'); + const binPath = join(pkgJsonPath, '..', 'bin', 'vite.js'); + return { + binPath, + envs: process.env.DEBUG_DISABLE_SOURCE_MAP + ? { + DEBUG_DISABLE_SOURCE_MAP: process.env.DEBUG_DISABLE_SOURCE_MAP, + } + : {}, + }; +} From 24bb5782c4ae0c050f73305874dffdcba58bba31 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 14 Aug 2025 12:27:15 +0800 Subject: [PATCH 3/9] Add build error --- crates/vite_error/src/lib.rs | 3 +++ packages/cli/binding/src/lib.rs | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/vite_error/src/lib.rs b/crates/vite_error/src/lib.rs index 610480537e..71ad9638aa 100644 --- a/crates/vite_error/src/lib.rs +++ b/crates/vite_error/src/lib.rs @@ -82,6 +82,9 @@ pub enum Error { #[error("Lint failed")] LintFailed { status: String, reason: String }, + #[error("Build failed")] + BuildFailed { status: String, reason: String }, + #[error(transparent)] AnyhowError(#[from] anyhow::Error), } diff --git a/packages/cli/binding/src/lib.rs b/packages/cli/binding/src/lib.rs index b3957207b9..268bb1f98c 100644 --- a/packages/cli/binding/src/lib.rs +++ b/packages/cli/binding/src/lib.rs @@ -57,9 +57,9 @@ pub async fn run(options: CliOptions) -> Result<()> { let resolved = build .call_async(Ok(())) .await - .map_err(js_error_to_lint_error)? + .map_err(js_error_to_build_error)? .await - .map_err(js_error_to_lint_error)?; + .map_err(js_error_to_build_error)?; Ok(resolved.into()) }, @@ -75,3 +75,7 @@ pub async fn run(options: CliOptions) -> Result<()> { fn js_error_to_lint_error(err: napi::Error) -> Error { Error::LintFailed { status: err.status.to_string(), reason: err.to_string() } } + +fn js_error_to_build_error(err: napi::Error) -> Error { + Error::BuildFailed { status: err.status.to_string(), reason: err.to_string() } +} From 774697930a2586a8acb678c9023f7a790d369bc0 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 14 Aug 2025 12:31:09 +0800 Subject: [PATCH 4/9] Apply cr reviews --- crates/vite_task/src/build.rs | 2 +- packages/cli/src/build.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/vite_task/src/build.rs b/crates/vite_task/src/build.rs index 2b4a0df03c..730a0598a5 100644 --- a/crates/vite_task/src/build.rs +++ b/crates/vite_task/src/build.rs @@ -19,7 +19,7 @@ pub async fn build< workspace, resolve_build_command, "build", - iter::once(&"build".to_string()).chain(args.iter()), + iter::once("build").chain(args.iter().map(|arg| arg.as_str())), ) .await?; let mut task_graph: StableGraph = Default::default(); diff --git a/packages/cli/src/build.ts b/packages/cli/src/build.ts index 91700d9817..b99839498e 100644 --- a/packages/cli/src/build.ts +++ b/packages/cli/src/build.ts @@ -1,5 +1,5 @@ import { createRequire } from 'node:module'; -import { join } from 'node:path'; +import { dirname, join } from 'node:path'; const require = createRequire(import.meta.url); @@ -8,7 +8,7 @@ export async function build(): Promise<{ envs: Record; }> { const pkgJsonPath = require.resolve('vite/package.json'); - const binPath = join(pkgJsonPath, '..', 'bin', 'vite.js'); + const binPath = join(dirname(pkgJsonPath), 'bin', 'vite.js'); return { binPath, envs: process.env.DEBUG_DISABLE_SOURCE_MAP From ea7c988ce17c4e1a7ed1f36451d16bf234022eba Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 14 Aug 2025 12:32:47 +0800 Subject: [PATCH 5/9] Apply cr reviews --- packages/cli/src/lint.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/lint.ts b/packages/cli/src/lint.ts index 26a76684e1..48942ffac4 100644 --- a/packages/cli/src/lint.ts +++ b/packages/cli/src/lint.ts @@ -1,22 +1,19 @@ -import { createRequire } from 'node:module' +import { createRequire } from 'node:module'; -const require = createRequire(import.meta.url) +const require = createRequire(import.meta.url); export async function lint(): Promise<{ - binPath: string - envs: Record + binPath: string; + envs: Record; }> { - const binPath = require.resolve('oxlint/bin/oxlint') - const bin = require.resolve(binPath, { - paths: [require.resolve('oxlint/package.json')], - }) + const binPath = require.resolve('oxlint/bin/oxlint'); return { - binPath: bin, + binPath, // TODO: provide envs inference API envs: { JS_RUNTIME_VERSION: process.versions.node, JS_RUNTIME_NAME: process.release.name, NODE_PACKAGE_MANAGER: 'vite-plus', }, - } + }; } From c855458d4e03027a429bfb30bc2ae4669efe3456 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 14 Aug 2025 15:08:28 +0800 Subject: [PATCH 6/9] add test command --- CLAUDE.md | 9 +- crates/vite_error/src/lib.rs | 7 +- crates/vite_task/src/lib.rs | 108 ++++++++++++++++----- crates/vite_task/src/{build.rs => test.rs} | 14 +-- crates/vite_task/src/vite.rs | 30 ++++++ packages/cli/binding/index.d.ts | 3 +- packages/cli/binding/src/lib.rs | 32 ++++-- packages/cli/src/bin.ts | 6 +- packages/cli/src/test.ts | 18 ++++ packages/cli/src/{build.ts => vite.ts} | 2 +- 10 files changed, 184 insertions(+), 45 deletions(-) rename crates/vite_task/src/{build.rs => test.rs} (68%) create mode 100644 crates/vite_task/src/vite.rs create mode 100644 packages/cli/src/test.ts rename packages/cli/src/{build.ts => vite.ts} (92%) diff --git a/CLAUDE.md b/CLAUDE.md index 9049ba3bce..4659192982 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,13 +7,18 @@ A monorepo task runner (like nx/turbo) with intelligent caching and dependency r **Task Execution**: Run tasks across monorepo packages with automatic dependency ordering. ```bash -# Run task in current package (implicit mode) -vite-plus build +# Built-in commands +vite-plus build # Run Vite build (dedicated command) +vite-plus test # Run Vite test (dedicated command) +vite-plus lint # Run oxlint (dedicated command) # Run tasks across packages (explicit mode) vite-plus run build -r # recursive with topological ordering vite-plus run app#build web#build # specific packages vite-plus run build -r --no-topological # recursive without implicit deps + +# Run task in current package (implicit mode - for non-built-in tasks) +vite-plus dev # runs dev script from package.json ``` ## Key Architecture diff --git a/crates/vite_error/src/lib.rs b/crates/vite_error/src/lib.rs index 71ad9638aa..1910b6e943 100644 --- a/crates/vite_error/src/lib.rs +++ b/crates/vite_error/src/lib.rs @@ -82,8 +82,11 @@ pub enum Error { #[error("Lint failed")] LintFailed { status: String, reason: String }, - #[error("Build failed")] - BuildFailed { status: String, reason: String }, + #[error("Vite failed")] + ViteError { status: String, reason: String }, + + #[error("Test failed")] + TestFailed { status: String, reason: String }, #[error(transparent)] AnyhowError(#[from] anyhow::Error), diff --git a/crates/vite_task/src/lib.rs b/crates/vite_task/src/lib.rs index 38ff728477..9d6d83483c 100644 --- a/crates/vite_task/src/lib.rs +++ b/crates/vite_task/src/lib.rs @@ -1,4 +1,3 @@ -mod build; mod cache; mod cmd; mod collections; @@ -10,6 +9,8 @@ mod lint; mod maybe_str; mod schedule; mod str; +mod test; +mod vite; #[cfg(test)] mod test_utils; @@ -84,6 +85,11 @@ pub enum Commands { /// Arguments to pass to vite build args: Vec, }, + Test { + #[clap(last = true)] + /// Arguments to pass to vite test + args: Vec, + }, } /// Resolve boolean flag value considering both positive and negative forms. @@ -98,13 +104,18 @@ pub struct CliOptions< Box>>, >, LintFn: Fn() -> Lint = Box Lint>, - Build: Future> = Pin< + Vite: Future> = Pin< Box>>, >, - BuildFn: Fn() -> Build = Box Build>, + ViteFn: Fn() -> Vite = Box Vite>, + Test: Future> = Pin< + Box>>, + >, + TestFn: Fn() -> Test = Box Test>, > { pub lint: LintFn, - pub build: BuildFn, + pub vite: ViteFn, + pub test: TestFn, } pub struct ResolveCommandResult { @@ -145,12 +156,14 @@ pub struct ResolveCommandResult { pub async fn main< Lint: Future>, LintFn: Fn() -> Lint, - Build: Future>, - BuildFn: Fn() -> Build, + Vite: Future>, + ViteFn: Fn() -> Vite, + Test: Future>, + TestFn: Fn() -> Test, >( cwd: PathBuf, args: Args, - options: Option>, + options: Option>, ) -> Result<(), Error> { let mut recursive_run = false; let mut parallel_run = false; @@ -190,8 +203,16 @@ pub async fn main< } Some(Commands::Build { args }) => { let mut workspace = Workspace::partial_load(cwd)?; - if let Some(build_fn) = options.map(|o| o.build) { - build::build(build_fn, &mut workspace, args).await?; + if let Some(vite_fn) = options.map(|o| o.vite) { + vite::create_vite("build", vite_fn, &mut workspace, args).await?; + workspace.unload().await?; + } + return Ok(()); + } + Some(Commands::Test { args }) => { + let mut workspace = Workspace::partial_load(cwd)?; + if let Some(test_fn) = options.map(|o| o.test) { + test::test(test_fn, &mut workspace, args).await?; workspace.unload().await?; } return Ok(()); @@ -294,33 +315,58 @@ mod tests { #[test] fn test_args_basic_task() { let args = Args::try_parse_from(&["vite-plus", "build"]).unwrap(); - assert_eq!(args.task, Some("build".into())); + assert_eq!(args.task, None); assert!(args.task_args.is_empty()); - assert!(args.commands.is_none()); + assert!(matches!(args.commands, Some(Commands::Build { .. }))); assert!(!args.debug); } #[test] fn test_args_with_task_args() { + // Now "test" is a dedicated command, so let's use a different task name for implicit mode let args = - Args::try_parse_from(&["vite-plus", "test", "--", "--watch", "--verbose"]).unwrap(); - assert_eq!(args.task, Some("test".into())); - assert_eq!(args.task_args, vec!["--watch", "--verbose"]); + Args::try_parse_from(&["vite-plus", "dev", "--", "--watch", "--verbose"]).unwrap(); + assert_eq!(args.task, Some("dev".into())); + assert_eq!(args.task_args, vec!["--watch".into(), "--verbose".into()]); assert!(args.commands.is_none()); assert!(!args.debug); } + #[test] + fn test_args_test_command() { + let args = Args::try_parse_from(&["vite-plus", "test"]).unwrap(); + assert_eq!(args.task, None); + assert!(args.task_args.is_empty()); + assert!(matches!(args.commands, Some(Commands::Test { .. }))); + assert!(!args.debug); + } + + #[test] + fn test_args_test_command_with_args() { + let args = + Args::try_parse_from(&["vite-plus", "test", "--", "--watch", "--coverage"]).unwrap(); + assert_eq!(args.task, None); + assert!(args.task_args.is_empty()); + if let Some(Commands::Test { args }) = &args.commands { + assert_eq!(args, &vec!["--watch".to_string(), "--coverage".to_string()]); + } else { + panic!("Expected Test command"); + } + } + #[test] fn test_args_debug_flag() { let args = Args::try_parse_from(&["vite-plus", "--debug", "build"]).unwrap(); - assert_eq!(args.task, Some("build".into())); + assert_eq!(args.task, None); + assert!(matches!(args.commands, Some(Commands::Build { .. }))); assert!(args.debug); } #[test] fn test_args_debug_flag_short() { let args = Args::try_parse_from(&["vite-plus", "-d", "build"]).unwrap(); - assert_eq!(args.task, Some("build".into())); + assert_eq!(args.task, None); + assert!(matches!(args.commands, Some(Commands::Build { .. }))); assert!(args.debug); } @@ -571,8 +617,22 @@ mod tests { ]) .unwrap(); - assert_eq!(args.task, Some("test".into())); - assert_eq!(args.task_args, vec!["--config", "jest.config.js", "--coverage", "--watch"]); + // "test" is now a dedicated command + assert_eq!(args.task, None); + assert!(args.task_args.is_empty()); + if let Some(Commands::Test { args }) = &args.commands { + assert_eq!( + args, + &vec![ + "--config".to_string(), + "jest.config.js".to_string(), + "--coverage".to_string(), + "--watch".to_string() + ] + ); + } else { + panic!("Expected Test command"); + } } #[test] @@ -628,9 +688,13 @@ mod tests { panic!("Expected Run command"); } - // Verify args2: implicit mode - assert_eq!(args2.task, Some("build".into())); - assert_eq!(args2.task_args, vec!["--watch", "--mode=development"]); - assert!(args2.commands.is_none()); + // Verify args2: now maps to Build command instead of implicit mode + assert_eq!(args2.task, None); + assert!(args2.task_args.is_empty()); // Build command captures args directly, not via task_args + if let Some(Commands::Build { args }) = &args2.commands { + assert_eq!(args, &vec!["--watch".to_string(), "--mode=development".to_string()]); + } else { + panic!("Expected Build command"); + } } } diff --git a/crates/vite_task/src/build.rs b/crates/vite_task/src/test.rs similarity index 68% rename from crates/vite_task/src/build.rs rename to crates/vite_task/src/test.rs index 730a0598a5..707b0280bb 100644 --- a/crates/vite_task/src/build.rs +++ b/crates/vite_task/src/test.rs @@ -7,19 +7,19 @@ use crate::config::ResolvedTask; use crate::schedule::ExecutionPlan; use crate::{Error, ResolveCommandResult, Workspace}; -pub async fn build< - Build: Future>, - BuildFn: Fn() -> Build, +pub async fn test< + Test: Future>, + TestFn: Fn() -> Test, >( - resolve_build_command: BuildFn, + resolve_test_command: TestFn, workspace: &mut Workspace, args: &Vec, ) -> Result<(), Error> { let resolved_task = ResolvedTask::resolve_from_built_in( workspace, - resolve_build_command, - "build", - iter::once("build").chain(args.iter().map(|arg| arg.as_str())), + resolve_test_command, + "test", + iter::once("test").chain(args.iter().map(|arg| arg.as_str())), ) .await?; let mut task_graph: StableGraph = Default::default(); diff --git a/crates/vite_task/src/vite.rs b/crates/vite_task/src/vite.rs new file mode 100644 index 0000000000..e7a865a781 --- /dev/null +++ b/crates/vite_task/src/vite.rs @@ -0,0 +1,30 @@ +use std::future::Future; +use std::iter; + +use petgraph::stable_graph::StableGraph; + +use crate::config::ResolvedTask; +use crate::schedule::ExecutionPlan; +use crate::{Error, ResolveCommandResult, Workspace}; + +pub async fn create_vite< + Vite: Future>, + ViteFn: Fn() -> Vite, +>( + arg_forward: &str, + resolve_vite_command: ViteFn, + workspace: &mut Workspace, + args: &Vec, +) -> Result<(), Error> { + let resolved_task = ResolvedTask::resolve_from_built_in( + workspace, + resolve_vite_command, + arg_forward, + iter::once(arg_forward).chain(args.iter().map(|arg| arg.as_str())), + ) + .await?; + let mut task_graph: StableGraph = Default::default(); + task_graph.add_node(resolved_task); + ExecutionPlan::plan(task_graph, false)?.execute(workspace).await?; + Ok(()) +} diff --git a/packages/cli/binding/index.d.ts b/packages/cli/binding/index.d.ts index 158e9f27d3..12befcee39 100644 --- a/packages/cli/binding/index.d.ts +++ b/packages/cli/binding/index.d.ts @@ -2,7 +2,8 @@ /* eslint-disable */ export interface CliOptions { lint: ((err: Error | null, ) => Promise) - build: ((err: Error | null, ) => Promise) + vite: ((err: Error | null, ) => Promise) + test: ((err: Error | null, ) => Promise) cwd?: string } diff --git a/packages/cli/binding/src/lib.rs b/packages/cli/binding/src/lib.rs index 268bb1f98c..62b80ab851 100644 --- a/packages/cli/binding/src/lib.rs +++ b/packages/cli/binding/src/lib.rs @@ -17,7 +17,8 @@ pub fn init() { #[napi(object, object_to_js = false)] pub struct CliOptions { pub lint: Arc>>, - pub build: Arc>>, + pub vite: Arc>>, + pub test: Arc>>, pub cwd: Option, } @@ -38,7 +39,8 @@ pub async fn run(options: CliOptions) -> Result<()> { let args = Args::parse_from(std::env::args_os().skip(1)); let cwd = if let Some(cwd) = options.cwd { PathBuf::from(cwd) } else { current_dir()? }; let lint = options.lint; - let build = options.build; + let vite = options.vite; + let test = options.test; if let Err(e) = vite_task::main( cwd, args, @@ -53,13 +55,23 @@ pub async fn run(options: CliOptions) -> Result<()> { Ok(resolved.into()) }, - build: || async { - let resolved = build + vite: || async { + let resolved = vite .call_async(Ok(())) .await - .map_err(js_error_to_build_error)? + .map_err(js_error_to_vite_error)? .await - .map_err(js_error_to_build_error)?; + .map_err(js_error_to_vite_error)?; + + Ok(resolved.into()) + }, + test: || async { + let resolved = test + .call_async(Ok(())) + .await + .map_err(js_error_to_test_error)? + .await + .map_err(js_error_to_test_error)?; Ok(resolved.into()) }, @@ -76,6 +88,10 @@ fn js_error_to_lint_error(err: napi::Error) -> Error { Error::LintFailed { status: err.status.to_string(), reason: err.to_string() } } -fn js_error_to_build_error(err: napi::Error) -> Error { - Error::BuildFailed { status: err.status.to_string(), reason: err.to_string() } +fn js_error_to_vite_error(err: napi::Error) -> Error { + Error::ViteError { status: err.status.to_string(), reason: err.to_string() } +} + +fn js_error_to_test_error(err: napi::Error) -> Error { + Error::TestFailed { status: err.status.to_string(), reason: err.to_string() } } diff --git a/packages/cli/src/bin.ts b/packages/cli/src/bin.ts index 964accde08..77e2feaf8f 100644 --- a/packages/cli/src/bin.ts +++ b/packages/cli/src/bin.ts @@ -1,8 +1,10 @@ import { run } from '../binding/index.js'; -import { build } from './build.js'; import { lint } from './lint.js'; +import { test } from './test.js'; +import { vite } from './vite.js'; run({ lint, - build, + vite, + test, }); diff --git a/packages/cli/src/test.ts b/packages/cli/src/test.ts new file mode 100644 index 0000000000..8aaea4d3c9 --- /dev/null +++ b/packages/cli/src/test.ts @@ -0,0 +1,18 @@ +import { createRequire } from 'node:module'; + +const require = createRequire(import.meta.url); + +export async function test(): Promise<{ + binPath: string; + envs: Record; +}> { + const binPath = require.resolve('vitest/vitest.mjs'); + return { + binPath, + envs: process.env.DEBUG_DISABLE_SOURCE_MAP + ? { + DEBUG_DISABLE_SOURCE_MAP: process.env.DEBUG_DISABLE_SOURCE_MAP, + } + : {}, + }; +} diff --git a/packages/cli/src/build.ts b/packages/cli/src/vite.ts similarity index 92% rename from packages/cli/src/build.ts rename to packages/cli/src/vite.ts index b99839498e..e31d3ab98b 100644 --- a/packages/cli/src/build.ts +++ b/packages/cli/src/vite.ts @@ -3,7 +3,7 @@ import { dirname, join } from 'node:path'; const require = createRequire(import.meta.url); -export async function build(): Promise<{ +export async function vite(): Promise<{ binPath: string; envs: Record; }> { From eaf5da046890541e13e76a6a2a36db13f49c07fe Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 14 Aug 2025 15:42:13 +0800 Subject: [PATCH 7/9] Add docs --- packages/cli/README.md | 140 +++++++++++++++++++++++++++++++- packages/cli/binding/index.d.ts | 43 ++++++++++ packages/cli/binding/src/lib.rs | 78 ++++++++++++++++-- packages/cli/src/bin.ts | 19 ++++- packages/cli/src/lint.ts | 27 ++++++ packages/cli/src/test.ts | 23 ++++++ packages/cli/src/vite.ts | 25 ++++++ 7 files changed, 345 insertions(+), 10 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 84c289ccaa..bd4ffee5bc 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1 +1,139 @@ -→ [../global/README.md](../global/README.md) +# Vite-Plus CLI Package + +## Overview + +This package provides the JavaScript-to-Rust bridge that enables vite-plus to execute JavaScript tooling (like Vite, Vitest, and oxlint) from the Rust core. It uses NAPI-RS to create native Node.js bindings. + +## Architecture + +### How It Works + +The architecture follows a callback-based pattern where JavaScript functions resolve tool paths and pass them to Rust for execution: + +``` +┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ JavaScript │────▶│ NAPI Bridge │────▶│ Rust Core │ +│ (bin.ts) │ │ (binding/) │ │ (vite_task) │ +└─────────────────┘ └──────────────────┘ └─────────────────┘ + │ │ │ + ▼ ▼ ▼ + Resolves tool Converts JS Executes tools + binary paths callbacks to Rust with resolved paths +``` + +### Key Components + +#### 1. JavaScript Layer (`src/`) + +The JavaScript layer is responsible for resolving tool binary paths: + +- **`bin.ts`**: Entry point that initializes the CLI with tool resolvers +- **`vite.ts`**: Resolves the Vite binary path for build commands +- **`test.ts`**: Resolves the Vitest binary path for test commands +- **`lint.ts`**: Resolves the oxlint binary path for linting +- **`index.ts`**: Exports the `defineConfig` helper for Vite configuration + +Each resolver function returns: + +```typescript +{ + binPath: string, // Absolute path to the tool's binary + envs: Record // Environment variables to set +} +``` + +#### 2. NAPI Binding Layer (`binding/`) + +The binding layer provides the JavaScript-to-Rust bridge using NAPI-RS: + +- **`src/lib.rs`**: Defines the NAPI bindings and type conversions +- **`index.d.ts`**: TypeScript type definitions (auto-generated) +- **`index.js`**: Native module loader (auto-generated) + +The binding converts JavaScript callbacks into Rust futures using `ThreadsafeFunction`. + +#### 3. Rust Core Integration + +The Rust core (`crates/vite_task`) receives the tool resolvers through `CliOptions`: + +```rust +pub struct CliOptions { + pub lint: LintFn, // Callback to resolve lint tool + pub vite: ViteFn, // Callback to resolve vite tool + pub test: TestFn, // Callback to resolve test tool +} +``` + +## Execution Flow + +1. **Initialization**: `bin.ts` calls `run()` with tool resolver functions +2. **Command Parsing**: Rust parses CLI arguments to determine which command to run +3. **Tool Resolution**: When a command needs a tool (e.g., `vite build`): + - Rust calls back to JavaScript through NAPI + - JavaScript resolver finds the tool's binary path + - Path is returned to Rust +4. **Execution**: Rust executes the tool binary with appropriate arguments + +## Example: Vite Build Command + +When a user runs `vite-plus build`: + +1. Rust identifies this as a Build command +2. Calls the `vite` callback function +3. JavaScript `vite.ts` resolves `vite/bin/vite.js` path +4. Returns path to Rust +5. Rust executes: `node /path/to/vite.js build [args]` + +## Development + +### Building + +```bash +# Build the native binding +pnpm build + +# Or watch for changes +pnpm build:debug +``` + +### Adding a New Tool + +1. Create a resolver in `src/`: + +```typescript +// src/mytool.ts +export async function mytool() { + const binPath = require.resolve('mytool/bin/cli.js'); + return { binPath, envs: {} }; +} +``` + +2. Add to `CliOptions` in `binding/src/lib.rs`: + +```rust +pub struct CliOptions { + // ... existing fields + pub mytool: Arc>>, +} +``` + +3. Wire it up in `bin.ts`: + +```typescript +import { mytool } from './mytool.js'; +run({ lint, vite, test, mytool }); +``` + +## Benefits of This Architecture + +1. **Tool Resolution in JavaScript**: Leverages Node.js module resolution to find tools +2. **Execution in Rust**: Benefits from Rust's performance and concurrency +3. **Type Safety**: Full type safety across the JS-Rust boundary +4. **Flexibility**: Easy to add new tools without changing core logic +5. **Environment Handling**: Can pass environment variables per tool + +## Dependencies + +- `napi`: Node-API bindings for Rust +- `napi-derive`: Procedural macros for NAPI +- `vite`, `vitest`, `oxlint`: The actual tools being wrapped diff --git a/packages/cli/binding/index.d.ts b/packages/cli/binding/index.d.ts index 12befcee39..d4ef089963 100644 --- a/packages/cli/binding/index.d.ts +++ b/packages/cli/binding/index.d.ts @@ -1,15 +1,58 @@ /* auto-generated by NAPI-RS */ /* eslint-disable */ +/** + * Configuration options passed from JavaScript to Rust. + * + * Each field (except `cwd`) is a JavaScript function wrapped in a `ThreadsafeFunction`. + * These functions are called by Rust to resolve tool binary paths when needed. + * + * The `ThreadsafeFunction` wrapper ensures the JavaScript functions can be + * safely called from Rust's async runtime without blocking or race conditions. + */ export interface CliOptions { + /** Resolver function for the lint tool (oxlint) */ lint: ((err: Error | null, ) => Promise) + /** Resolver function for the vite tool (used for build/dev) */ vite: ((err: Error | null, ) => Promise) + /** Resolver function for the test tool (vitest) */ test: ((err: Error | null, ) => Promise) + /** Optional working directory override */ cwd?: string } +/** + * Result returned by JavaScript resolver functions. + * + * This structure contains the information needed to execute a tool: + * - `bin_path`: The absolute path to the tool's binary/script + * - `envs`: Environment variables to set when executing the tool + */ export interface JsCommandResolvedResult { + /** Absolute path to the tool's executable or script */ binPath: string + /** Environment variables to set when running the tool */ envs: Record } +/** + * Main entry point for the CLI, called from JavaScript. + * + * This function: + * 1. Parses command-line arguments + * 2. Sets up the working directory + * 3. Creates Rust-callable wrappers for JavaScript resolver functions + * 4. Passes control to the Rust core (`vite_task::main`) + * + * ## JavaScript-to-Rust Bridge + * + * The resolver functions are wrapped to: + * - Call the JavaScript function asynchronously + * - Handle errors and convert them to Rust error types + * - Convert the JavaScript result to Rust's expected format + * + * ## Error Handling + * + * Errors from JavaScript resolvers are converted to specific error types + * (e.g., `LintFailed`, `ViteError`) to provide better error messages. + */ export declare function run(options: CliOptions): Promise diff --git a/packages/cli/binding/src/lib.rs b/packages/cli/binding/src/lib.rs index 62b80ab851..4dd1ed8985 100644 --- a/packages/cli/binding/src/lib.rs +++ b/packages/cli/binding/src/lib.rs @@ -1,3 +1,18 @@ +//! NAPI binding layer for vite-plus CLI +//! +//! This module provides the bridge between JavaScript tool resolvers and the Rust core. +//! It uses NAPI-RS to create native Node.js bindings that allow JavaScript functions +//! to be called from Rust code. +//! +//! ## Architecture +//! +//! The binding follows a callback pattern: +//! 1. JavaScript passes resolver functions to Rust through `CliOptions` +//! 2. These functions are wrapped in `ThreadsafeFunction` for safe cross-runtime calls +//! 3. When Rust needs a tool, it calls the corresponding JavaScript function +//! 4. JavaScript resolves the tool path and returns it to Rust +//! 5. Rust executes the tool with the resolved path + use std::collections::HashMap; use std::env::current_dir; use std::path::PathBuf; @@ -9,52 +24,98 @@ use napi_derive::napi; use vite_error::Error; use vite_task::{Args, CliOptions as ViteTaskCliOptions, ResolveCommandResult}; +/// Module initialization - sets up tracing for debugging #[napi_derive::module_init] pub fn init() { vite_task::init_tracing(); } +/// Configuration options passed from JavaScript to Rust. +/// +/// Each field (except `cwd`) is a JavaScript function wrapped in a `ThreadsafeFunction`. +/// These functions are called by Rust to resolve tool binary paths when needed. +/// +/// The `ThreadsafeFunction` wrapper ensures the JavaScript functions can be +/// safely called from Rust's async runtime without blocking or race conditions. #[napi(object, object_to_js = false)] pub struct CliOptions { + /// Resolver function for the lint tool (oxlint) pub lint: Arc>>, + /// Resolver function for the vite tool (used for build/dev) pub vite: Arc>>, + /// Resolver function for the test tool (vitest) pub test: Arc>>, + /// Optional working directory override pub cwd: Option, } +/// Result returned by JavaScript resolver functions. +/// +/// This structure contains the information needed to execute a tool: +/// - `bin_path`: The absolute path to the tool's binary/script +/// - `envs`: Environment variables to set when executing the tool #[napi(object, object_to_js = false)] pub struct JsCommandResolvedResult { + /// Absolute path to the tool's executable or script pub bin_path: String, + /// Environment variables to set when running the tool pub envs: HashMap, } +/// Convert JavaScript result to Rust's expected format impl From for ResolveCommandResult { fn from(value: JsCommandResolvedResult) -> Self { ResolveCommandResult { bin_path: value.bin_path, envs: value.envs } } } +/// Main entry point for the CLI, called from JavaScript. +/// +/// This function: +/// 1. Parses command-line arguments +/// 2. Sets up the working directory +/// 3. Creates Rust-callable wrappers for JavaScript resolver functions +/// 4. Passes control to the Rust core (`vite_task::main`) +/// +/// ## JavaScript-to-Rust Bridge +/// +/// The resolver functions are wrapped to: +/// - Call the JavaScript function asynchronously +/// - Handle errors and convert them to Rust error types +/// - Convert the JavaScript result to Rust's expected format +/// +/// ## Error Handling +/// +/// Errors from JavaScript resolvers are converted to specific error types +/// (e.g., `LintFailed`, `ViteError`) to provide better error messages. #[napi] pub async fn run(options: CliOptions) -> Result<()> { + // Parse CLI arguments (skip first arg which is the node binary) let args = Args::parse_from(std::env::args_os().skip(1)); + // Use provided cwd or current directory let cwd = if let Some(cwd) = options.cwd { PathBuf::from(cwd) } else { current_dir()? }; + // Extract resolver functions from options let lint = options.lint; let vite = options.vite; let test = options.test; + // Call the Rust core with wrapped resolver functions if let Err(e) = vite_task::main( cwd, args, Some(ViteTaskCliOptions { + // Wrap the lint resolver to be callable from Rust lint: || async { + // Call the JavaScript function and await both the promise and the result let resolved = lint - .call_async(Ok(())) - .await - .map_err(js_error_to_lint_error)? - .await - .map_err(js_error_to_lint_error)?; + .call_async(Ok(())) // Call with no arguments + .await // Wait for the call to complete + .map_err(js_error_to_lint_error)? // Convert call errors + .await // Wait for the promise to resolve + .map_err(js_error_to_lint_error)?; // Convert promise errors - Ok(resolved.into()) + Ok(resolved.into()) // Convert to Rust type }, + // Wrap the vite resolver to be callable from Rust vite: || async { let resolved = vite .call_async(Ok(())) @@ -65,6 +126,7 @@ pub async fn run(options: CliOptions) -> Result<()> { Ok(resolved.into()) }, + // Wrap the test resolver to be callable from Rust test: || async { let resolved = test .call_async(Ok(())) @@ -79,19 +141,23 @@ pub async fn run(options: CliOptions) -> Result<()> { ) .await { + // Convert Rust errors to NAPI errors for JavaScript return Err(napi::Error::new(Status::GenericFailure, e.to_string())); } Ok(()) } +/// Convert JavaScript errors to Rust lint errors fn js_error_to_lint_error(err: napi::Error) -> Error { Error::LintFailed { status: err.status.to_string(), reason: err.to_string() } } +/// Convert JavaScript errors to Rust vite errors fn js_error_to_vite_error(err: napi::Error) -> Error { Error::ViteError { status: err.status.to_string(), reason: err.to_string() } } +/// Convert JavaScript errors to Rust test errors fn js_error_to_test_error(err: napi::Error) -> Error { Error::TestFailed { status: err.status.to_string(), reason: err.to_string() } } diff --git a/packages/cli/src/bin.ts b/packages/cli/src/bin.ts index 77e2feaf8f..062894c398 100644 --- a/packages/cli/src/bin.ts +++ b/packages/cli/src/bin.ts @@ -1,10 +1,23 @@ +/** + * Entry point for the vite-plus CLI. + * + * This file initializes the CLI by passing JavaScript tool resolver functions + * to the Rust core through NAPI bindings. Each resolver function is responsible + * for locating the binary path of its respective tool using Node.js module resolution. + * + * The Rust core will call these functions when it needs to execute the corresponding + * tools (e.g., when running `vite-plus build`, it calls the vite resolver). + */ + import { run } from '../binding/index.js'; import { lint } from './lint.js'; import { test } from './test.js'; import { vite } from './vite.js'; +// Initialize the CLI with tool resolvers +// These functions will be called from Rust when needed run({ - lint, - vite, - test, + lint, // Resolves oxlint binary for linting + vite, // Resolves vite binary for build/dev commands + test, // Resolves vitest binary for test commands }); diff --git a/packages/cli/src/lint.ts b/packages/cli/src/lint.ts index 48942ffac4..80bd7572c6 100644 --- a/packages/cli/src/lint.ts +++ b/packages/cli/src/lint.ts @@ -1,18 +1,45 @@ +/** + * Oxlint tool resolver for the vite-plus CLI. + * + * This module exports a function that resolves the oxlint binary path + * using Node.js module resolution. The resolved path is passed back + * to the Rust core, which then executes oxlint for code linting. + * + * Used for: `vite-plus lint` command + * + * Oxlint is a fast JavaScript/TypeScript linter written in Rust that + * provides ESLint-compatible linting with significantly better performance. + */ + import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); +/** + * Resolves the oxlint binary path and environment variables. + * + * @returns Promise containing: + * - binPath: Absolute path to the oxlint binary + * - envs: Environment variables to set when executing oxlint + * + * The environment variables provide runtime context to oxlint, + * including Node.js version information and package manager details. + */ export async function lint(): Promise<{ binPath: string; envs: Record; }> { + // Resolve the oxlint binary directly (it's a native executable) const binPath = require.resolve('oxlint/bin/oxlint'); + return { binPath, // TODO: provide envs inference API envs: { + // Provide Node.js runtime information for oxlint's telemetry/compatibility JS_RUNTIME_VERSION: process.versions.node, JS_RUNTIME_NAME: process.release.name, + // Indicate that vite-plus is the package manager invoking oxlint NODE_PACKAGE_MANAGER: 'vite-plus', }, }; diff --git a/packages/cli/src/test.ts b/packages/cli/src/test.ts index 8aaea4d3c9..bfcd2fee0b 100644 --- a/packages/cli/src/test.ts +++ b/packages/cli/src/test.ts @@ -1,14 +1,37 @@ +/** + * Vitest tool resolver for the vite-plus CLI. + * + * This module exports a function that resolves the Vitest binary path + * using Node.js module resolution. The resolved path is passed back + * to the Rust core, which then executes Vitest for running tests. + * + * Used for: `vite-plus test` command + */ + import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); +/** + * Resolves the Vitest binary path and environment variables. + * + * @returns Promise containing: + * - binPath: Absolute path to the Vitest CLI entry point (vitest.mjs) + * - envs: Environment variables to set when executing Vitest + * + * Vitest is Vite's testing framework that provides a Jest-compatible + * testing experience with Vite's fast HMR and transformation pipeline. + */ export async function test(): Promise<{ binPath: string; envs: Record; }> { + // Resolve the Vitest CLI module directly const binPath = require.resolve('vitest/vitest.mjs'); + return { binPath, + // Pass through source map debugging environment variable if set envs: process.env.DEBUG_DISABLE_SOURCE_MAP ? { DEBUG_DISABLE_SOURCE_MAP: process.env.DEBUG_DISABLE_SOURCE_MAP, diff --git a/packages/cli/src/vite.ts b/packages/cli/src/vite.ts index e31d3ab98b..b755fe4f3f 100644 --- a/packages/cli/src/vite.ts +++ b/packages/cli/src/vite.ts @@ -1,16 +1,41 @@ +/** + * Vite tool resolver for the vite-plus CLI. + * + * This module exports a function that resolves the Vite binary path + * using Node.js module resolution. The resolved path is passed back + * to the Rust core, which then executes Vite with the appropriate + * command and arguments. + * + * Used for: `vite-plus build` and potentially `vite-plus dev` commands + */ + import { createRequire } from 'node:module'; import { dirname, join } from 'node:path'; const require = createRequire(import.meta.url); +/** + * Resolves the Vite binary path and environment variables. + * + * @returns Promise containing: + * - binPath: Absolute path to the Vite CLI entry point (vite.js) + * - envs: Environment variables to set when executing Vite + * + * The function uses require.resolve to find the vite package installation, + * then constructs the path to the CLI binary within the package. + */ export async function vite(): Promise<{ binPath: string; envs: Record; }> { + // Find the vite package.json to locate the installation directory const pkgJsonPath = require.resolve('vite/package.json'); + // Vite's CLI binary is located at bin/vite.js relative to the package root const binPath = join(dirname(pkgJsonPath), 'bin', 'vite.js'); + return { binPath, + // Pass through source map debugging environment variable if set envs: process.env.DEBUG_DISABLE_SOURCE_MAP ? { DEBUG_DISABLE_SOURCE_MAP: process.env.DEBUG_DISABLE_SOURCE_MAP, From 7fb4dad1541499704efbcb24d7a934077772d5ce Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 20 Aug 2025 22:40:06 +0800 Subject: [PATCH 8/9] Fix rebase tests --- crates/vite_task/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vite_task/src/lib.rs b/crates/vite_task/src/lib.rs index 9d6d83483c..5999b610fd 100644 --- a/crates/vite_task/src/lib.rs +++ b/crates/vite_task/src/lib.rs @@ -327,7 +327,7 @@ mod tests { let args = Args::try_parse_from(&["vite-plus", "dev", "--", "--watch", "--verbose"]).unwrap(); assert_eq!(args.task, Some("dev".into())); - assert_eq!(args.task_args, vec!["--watch".into(), "--verbose".into()]); + assert_eq!(args.task_args, vec!["--watch", "--verbose"]); assert!(args.commands.is_none()); assert!(!args.debug); } From 2d8230166c4cec1ee228d422fc1ab3388588d4e4 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 20 Aug 2025 22:40:46 +0800 Subject: [PATCH 9/9] clippy fix --- crates/vite_task/src/config/mod.rs | 4 ++-- crates/vite_task/src/execute.rs | 2 +- crates/vite_task/src/test.rs | 2 +- crates/vite_task/src/vite.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/vite_task/src/config/mod.rs b/crates/vite_task/src/config/mod.rs index ab34a1db21..c22433f818 100644 --- a/crates/vite_task/src/config/mod.rs +++ b/crates/vite_task/src/config/mod.rs @@ -121,12 +121,12 @@ impl ResolvedTask { let resolved_command = ResolvedTaskCommand { fingerprint: CommandFingerprint { cwd: workspace.dir.as_path().to_string_lossy().as_ref().into(), - command: link_task.clone().into(), + command: link_task, envs_without_pass_through: resolved_envs.envs_without_pass_through, }, all_envs: resolved_envs.all_envs, }; - Ok(ResolvedTask { + Ok(Self { name: TaskName { package_name: workspace.package_json.name.as_str().into(), task_group_name: task_name.into(), diff --git a/crates/vite_task/src/execute.rs b/crates/vite_task/src/execute.rs index 64b694e651..a3f4965534 100644 --- a/crates/vite_task/src/execute.rs +++ b/crates/vite_task/src/execute.rs @@ -95,7 +95,7 @@ async fn collect_std_outputs( /// - Explicitly declared as dependencies of the task /// - Included in `envs_without_pass_through` /// - Changes to these invalidate the cache -/// - Example: NODE_ENV, API_URL, BUILD_MODE +/// - Example: `NODE_ENV`, `API_URL`, `BUILD_MODE` /// /// 2. **Pass-through envs** (in task config's `pass_through_envs` or defaults like PATH): /// - Available to the task but don't affect caching diff --git a/crates/vite_task/src/test.rs b/crates/vite_task/src/test.rs index 707b0280bb..762a726f07 100644 --- a/crates/vite_task/src/test.rs +++ b/crates/vite_task/src/test.rs @@ -19,7 +19,7 @@ pub async fn test< workspace, resolve_test_command, "test", - iter::once("test").chain(args.iter().map(|arg| arg.as_str())), + iter::once("test").chain(args.iter().map(std::string::String::as_str)), ) .await?; let mut task_graph: StableGraph = Default::default(); diff --git a/crates/vite_task/src/vite.rs b/crates/vite_task/src/vite.rs index e7a865a781..aa5112f16c 100644 --- a/crates/vite_task/src/vite.rs +++ b/crates/vite_task/src/vite.rs @@ -20,7 +20,7 @@ pub async fn create_vite< workspace, resolve_vite_command, arg_forward, - iter::once(arg_forward).chain(args.iter().map(|arg| arg.as_str())), + iter::once(arg_forward).chain(args.iter().map(std::string::String::as_str)), ) .await?; let mut task_graph: StableGraph = Default::default();