Skip to content

Commit d41602b

Browse files
tempusfrangitclaude
andcommitted
fix: handle //:name depends without crashing task graph
`//:name` refers to a task at the monorepo root config root, so the `pathPattern` between `//` and `:` is empty. `dependsPatternMatchesTask` short-circuits when that empty string exactly matches a root target's config root, but for a target in a non-root config root it fell through to `micromatch.isMatch(targetConfigRoot, "", ...)`, which picomatch rejects with `Expected pattern to be a non-empty string`. That path is hit for every non-root task while building the task graph, so a single `//:name` depends entry took the whole graph webview down. Skip the micromatch call for an empty pathPattern (the exact-match branch already yields the correct answer for root-only targets), and guard `matchesTaskName` against empty patterns defensively so any other edge case that leaks an empty pattern returns `false` instead of throwing. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 2571ed5 commit d41602b

2 files changed

Lines changed: 45 additions & 5 deletions

File tree

src/utils/taskNames.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,21 @@ describe("dependsPatternMatchesTask", () => {
448448
dependsPatternMatchesTask("//projects/frontend:docs:*", "", docsBuild),
449449
).toBe(true);
450450
});
451+
452+
// regression: `//:name` (root-config task) crashed micromatch with an empty
453+
// path pattern when the target task lived in a non-root config root.
454+
it("matches //:name against root tasks only", () => {
455+
expect(
456+
dependsPatternMatchesTask("//:root-task", "projects/frontend", rootTask),
457+
).toBe(true);
458+
expect(
459+
dependsPatternMatchesTask(
460+
"//:root-task",
461+
"projects/frontend",
462+
frontendBuild,
463+
),
464+
).toBe(false);
465+
});
451466
});
452467

453468
describe("findTasksMatchingDependsPattern", () => {
@@ -774,6 +789,23 @@ describe("getTaskDependencyEdges", () => {
774789
});
775790
});
776791

792+
// regression: a `//:root-task` depends entry crashed graph resolution as
793+
// soon as any target task lived in a non-root config root.
794+
it("resolves //:name depends without crashing on non-root targets", () => {
795+
const tasks = [
796+
createTask("//:root-task", "/repo/mise.toml"),
797+
{
798+
...createTask("//pkg-a:child", "/repo/pkg-a/mise.toml"),
799+
depends: ["//:root-task"],
800+
},
801+
createTask("//pkg-b:other", "/repo/pkg-b/mise.toml"),
802+
];
803+
const edges = getTaskDependencyEdges(tasks);
804+
expect(edges).toEqual([
805+
{ from: "//pkg-a:child", to: "//:root-task", kind: "depends" },
806+
]);
807+
});
808+
777809
it("does not create self edges", () => {
778810
const selfReferencing = [
779811
{

src/utils/taskNames.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ const MICROMATCH_OPTIONS = {
2222
* treats `/` as a separator, so both sides are normalized to path-like names.
2323
*/
2424
function matchesTaskName(name: string, pattern: string) {
25+
// picomatch throws on empty patterns; an empty name pattern (e.g. `//path:`)
26+
// has no meaningful match against real task names.
27+
if (!pattern) {
28+
return false;
29+
}
2530
return micromatch.isMatch(
2631
name.replaceAll(":", "/"),
2732
pattern.replaceAll(":", "/"),
@@ -461,15 +466,18 @@ export function dependsPatternMatchesTask(
461466
}
462467
const pathPattern = taskPattern.slice(2, separatorIndex);
463468
const namePattern = taskPattern.slice(separatorIndex + 1);
469+
// `//:name` targets the root config root only; exact-match handles it, so
470+
// skip micromatch (which throws on empty patterns) for non-root targets.
464471
const pathMatches =
465472
pathPattern === "..."
466473
? true
467474
: pathPattern === targetConfigRoot ||
468-
micromatch.isMatch(
469-
targetConfigRoot,
470-
pathPattern.replaceAll("...", "**"),
471-
MICROMATCH_OPTIONS,
472-
);
475+
(pathPattern !== "" &&
476+
micromatch.isMatch(
477+
targetConfigRoot,
478+
pathPattern.replaceAll("...", "**"),
479+
MICROMATCH_OPTIONS,
480+
));
473481
return pathMatches && matchesLocalName(namePattern);
474482
}
475483

0 commit comments

Comments
 (0)