Skip to content

Commit 8b4c8d3

Browse files
branchseerclaude
andauthored
feat: add object form for input globs with base directory (#295)
## Summary - Input entries now support an object form `{ "base": "workspace" | "package", "pattern": "..."}` to resolve glob patterns relative to the workspace root instead of the package directory - Bare string globs continue to work as before (resolved relative to the package directory) - Negation (`!`) is supported in both the bare string and object forms - `base` is required in the object form — if you want package-relative, just use a bare string. `{ "base": "package", "pattern": "src/**" }` is equivalent to `"src/**"`. ### Example ```json { "input": [ "src/**", { "base": "workspace", "pattern": "configs/tsconfig.json" }, { "base": "workspace", "pattern": "!dist/**"}, { "auto": true } ] } ``` ## Test plan - [x] Unit tests for deserialization of new object form (valid values, missing `base` error, invalid `base` error, mixed arrays) - [x] Unit tests for glob resolution with workspace and package bases - [x] Plan snapshot fixture (`input-workspace-base`) verifying workspace-relative resolution - [x] TypeScript type generation updated and verified https://claude.ai/code/session_01KiaZHtCW4hCsdJyPuBNPnw --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 65f6643 commit 8b4c8d3

File tree

12 files changed

+395
-58
lines changed

12 files changed

+395
-58
lines changed

CHANGELOG.md

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

3+
- **Added** object form for `input` entries: `{ "pattern": "...", "base": "workspace" | "package" }` to resolve glob patterns relative to the workspace root instead of the package directory ([#295](https://github.com/voidzero-dev/vite-task/pull/295))
34
- **Fixed** arguments after the task name being consumed by `vp` instead of passed through to the task ([#286](https://github.com/voidzero-dev/vite-task/pull/286), [#290](https://github.com/voidzero-dev/vite-task/pull/290))
45
- **Changed** default untracked env patterns to align with Turborepo, covering more CI and platform-specific variables ([#262](https://github.com/voidzero-dev/vite-task/pull/262))
56
- **Added** `--log=interleaved|labeled|grouped` flag to control task output display: `interleaved` (default) streams directly, `labeled` prefixes lines with `[pkg#task]`, `grouped` buffers output per task ([#266](https://github.com/voidzero-dev/vite-task/pull/266))

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ Tasks are defined in `vite-task.json`:
9999
"cache": true,
100100
"env": ["NODE_ENV"],
101101
"untrackedEnv": ["CI"],
102-
"input": ["src/**", "!dist/**", { "auto": true }]
102+
"input": ["src/**", "!dist/**", { "auto": true }, { "pattern": "tsconfig.json", "base": "workspace" }]
103103
}
104104
}
105105
}

crates/vite_task_graph/run-config.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
// This file is auto-generated by `cargo test`. Do not edit manually.
22

3+
export type AutoInput = {
4+
/**
5+
* Automatically track which files the task reads
6+
*/
7+
auto: boolean;
8+
};
9+
10+
export type GlobWithBase = {
11+
/**
12+
* The glob pattern (positive or negative starting with `!`)
13+
*/
14+
pattern: string;
15+
/**
16+
* The base directory for resolving the pattern
17+
*/
18+
base: InputBase;
19+
};
20+
21+
export type InputBase = 'package' | 'workspace';
22+
323
export type Task = {
424
/**
525
* The command to run for the task.
@@ -32,21 +52,12 @@ export type Task = {
3252
*
3353
* - Omitted: automatically tracks which files the task reads
3454
* - `[]` (empty): disables file tracking entirely
35-
* - Glob patterns (e.g. `"src/**"`) select specific files
55+
* - Glob patterns (e.g. `"src/**"`) select specific files, relative to the package directory
56+
* - `{pattern: "...", base: "workspace" | "package"}` specifies a glob with an explicit base directory
3657
* - `{auto: true}` enables automatic file tracking
3758
* - Negative patterns (e.g. `"!dist/**"`) exclude matched files
38-
*
39-
* Patterns are relative to the package directory.
4059
*/
41-
input?: Array<
42-
| string
43-
| {
44-
/**
45-
* Automatically track which files the task reads
46-
*/
47-
auto: boolean;
48-
}
49-
>;
60+
input?: Array<string | GlobWithBase | AutoInput>;
5061
}
5162
| {
5263
/**

crates/vite_task_graph/src/config/mod.rs

Lines changed: 126 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ use monostate::MustBe;
77
use rustc_hash::FxHashSet;
88
use serde::Serialize;
99
pub use user::{
10-
EnabledCacheConfig, ResolvedGlobalCacheConfig, UserCacheConfig, UserGlobalCacheConfig,
11-
UserInputEntry, UserInputsConfig, UserRunConfig, UserTaskConfig,
10+
AutoInput, EnabledCacheConfig, GlobWithBase, InputBase, ResolvedGlobalCacheConfig,
11+
UserCacheConfig, UserGlobalCacheConfig, UserInputEntry, UserInputsConfig, UserRunConfig,
12+
UserTaskConfig,
1213
};
1314
use vite_path::AbsolutePath;
1415
use vite_str::Str;
@@ -153,30 +154,54 @@ impl ResolvedInputConfig {
153154

154155
for entry in entries {
155156
match entry {
156-
UserInputEntry::Auto { auto: true } => includes_auto = true,
157-
UserInputEntry::Auto { auto: false } => {} // Ignore {auto: false}
157+
UserInputEntry::Auto(AutoInput { auto: true }) => includes_auto = true,
158+
UserInputEntry::Auto(AutoInput { auto: false }) => {} // Ignore {auto: false}
158159
UserInputEntry::Glob(pattern) => {
159-
if let Some(negated) = pattern.strip_prefix('!') {
160-
let resolved = resolve_glob_to_workspace_relative(
161-
negated,
162-
package_dir,
163-
workspace_root,
164-
)?;
165-
negative_globs.insert(resolved);
166-
} else {
167-
let resolved = resolve_glob_to_workspace_relative(
168-
pattern.as_str(),
169-
package_dir,
170-
workspace_root,
171-
)?;
172-
positive_globs.insert(resolved);
173-
}
160+
Self::insert_glob(
161+
pattern.as_str(),
162+
package_dir,
163+
workspace_root,
164+
&mut positive_globs,
165+
&mut negative_globs,
166+
)?;
167+
}
168+
UserInputEntry::GlobWithBase(GlobWithBase { pattern, base }) => {
169+
let base_dir = match base {
170+
InputBase::Package => package_dir,
171+
InputBase::Workspace => workspace_root,
172+
};
173+
Self::insert_glob(
174+
pattern.as_str(),
175+
base_dir,
176+
workspace_root,
177+
&mut positive_globs,
178+
&mut negative_globs,
179+
)?;
174180
}
175181
}
176182
}
177183

178184
Ok(Self { includes_auto, positive_globs, negative_globs })
179185
}
186+
187+
/// Insert a glob pattern into the appropriate set (positive or negative),
188+
/// resolving it relative to the given base directory.
189+
fn insert_glob(
190+
pattern: &str,
191+
base_dir: &AbsolutePath,
192+
workspace_root: &AbsolutePath,
193+
positive_globs: &mut BTreeSet<Str>,
194+
negative_globs: &mut BTreeSet<Str>,
195+
) -> Result<(), ResolveTaskConfigError> {
196+
if let Some(negated) = pattern.strip_prefix('!') {
197+
let resolved = resolve_glob_to_workspace_relative(negated, base_dir, workspace_root)?;
198+
negative_globs.insert(resolved);
199+
} else {
200+
let resolved = resolve_glob_to_workspace_relative(pattern, base_dir, workspace_root)?;
201+
positive_globs.insert(resolved);
202+
}
203+
Ok(())
204+
}
180205
}
181206

182207
/// Resolve a single glob pattern to be workspace-root-relative.
@@ -428,7 +453,7 @@ mod tests {
428453
#[test]
429454
fn test_resolved_input_config_auto_only() {
430455
let (pkg, ws) = test_paths();
431-
let user_inputs = vec![UserInputEntry::Auto { auto: true }];
456+
let user_inputs = vec![UserInputEntry::Auto(AutoInput { auto: true })];
432457
let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap();
433458
assert!(config.includes_auto);
434459
assert!(config.positive_globs.is_empty());
@@ -438,7 +463,7 @@ mod tests {
438463
#[test]
439464
fn test_resolved_input_config_auto_false_ignored() {
440465
let (pkg, ws) = test_paths();
441-
let user_inputs = vec![UserInputEntry::Auto { auto: false }];
466+
let user_inputs = vec![UserInputEntry::Auto(AutoInput { auto: false })];
442467
let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap();
443468
assert!(!config.includes_auto);
444469
assert!(config.positive_globs.is_empty());
@@ -482,7 +507,7 @@ mod tests {
482507
let (pkg, ws) = test_paths();
483508
let user_inputs = vec![
484509
UserInputEntry::Glob("package.json".into()),
485-
UserInputEntry::Auto { auto: true },
510+
UserInputEntry::Auto(AutoInput { auto: true }),
486511
UserInputEntry::Glob("!node_modules/**".into()),
487512
];
488513
let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap();
@@ -497,8 +522,10 @@ mod tests {
497522
fn test_resolved_input_config_globs_with_auto() {
498523
let (pkg, ws) = test_paths();
499524
// Globs with auto keeps inference enabled
500-
let user_inputs =
501-
vec![UserInputEntry::Glob("src/**/*.ts".into()), UserInputEntry::Auto { auto: true }];
525+
let user_inputs = vec![
526+
UserInputEntry::Glob("src/**/*.ts".into()),
527+
UserInputEntry::Auto(AutoInput { auto: true }),
528+
];
502529
let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap();
503530
assert!(config.includes_auto);
504531
}
@@ -524,4 +551,79 @@ mod tests {
524551
assert!(result.is_err());
525552
assert!(matches!(result.unwrap_err(), ResolveTaskConfigError::GlobOutsideWorkspace { .. }));
526553
}
554+
555+
#[test]
556+
fn test_resolved_input_config_glob_with_workspace_base() {
557+
let (pkg, ws) = test_paths();
558+
let user_inputs = vec![UserInputEntry::GlobWithBase(GlobWithBase {
559+
pattern: "configs/tsconfig.json".into(),
560+
base: InputBase::Workspace,
561+
})];
562+
let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap();
563+
assert!(!config.includes_auto);
564+
assert_eq!(config.positive_globs.len(), 1);
565+
// Workspace-base: should NOT have the package prefix
566+
assert!(
567+
config.positive_globs.contains("configs/tsconfig.json"),
568+
"expected 'configs/tsconfig.json', got {:?}",
569+
config.positive_globs
570+
);
571+
}
572+
573+
#[test]
574+
fn test_resolved_input_config_negative_glob_with_workspace_base() {
575+
let (pkg, ws) = test_paths();
576+
let user_inputs = vec![UserInputEntry::GlobWithBase(GlobWithBase {
577+
pattern: "!dist/**".into(),
578+
base: InputBase::Workspace,
579+
})];
580+
let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap();
581+
assert_eq!(config.negative_globs.len(), 1);
582+
assert!(
583+
config.negative_globs.contains("dist/**"),
584+
"expected 'dist/**', got {:?}",
585+
config.negative_globs
586+
);
587+
}
588+
589+
#[test]
590+
fn test_resolved_input_config_glob_with_package_base_explicit() {
591+
let (pkg, ws) = test_paths();
592+
// Explicit "package" base should behave same as bare string
593+
let user_inputs = vec![UserInputEntry::GlobWithBase(GlobWithBase {
594+
pattern: "src/**/*.ts".into(),
595+
base: InputBase::Package,
596+
})];
597+
let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap();
598+
assert_eq!(config.positive_globs.len(), 1);
599+
assert!(
600+
config.positive_globs.contains("packages/my-pkg/src/**/*.ts"),
601+
"expected 'packages/my-pkg/src/**/*.ts', got {:?}",
602+
config.positive_globs
603+
);
604+
}
605+
606+
#[test]
607+
fn test_resolved_input_config_mixed_bases() {
608+
let (pkg, ws) = test_paths();
609+
let user_inputs = vec![
610+
UserInputEntry::Glob("src/**".into()),
611+
UserInputEntry::GlobWithBase(GlobWithBase {
612+
pattern: "configs/**".into(),
613+
base: InputBase::Workspace,
614+
}),
615+
UserInputEntry::Auto(AutoInput { auto: true }),
616+
UserInputEntry::GlobWithBase(GlobWithBase {
617+
pattern: "!dist/**".into(),
618+
base: InputBase::Workspace,
619+
}),
620+
];
621+
let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap();
622+
assert!(config.includes_auto);
623+
assert_eq!(config.positive_globs.len(), 2);
624+
assert!(config.positive_globs.contains("packages/my-pkg/src/**"));
625+
assert!(config.positive_globs.contains("configs/**"));
626+
assert_eq!(config.negative_globs.len(), 1);
627+
assert!(config.negative_globs.contains("dist/**"));
628+
}
527629
}

0 commit comments

Comments
 (0)