diff --git a/.changeset/chilled-paths-keep-x.md b/.changeset/chilled-paths-keep-x.md new file mode 100644 index 000000000..0ffc632a4 --- /dev/null +++ b/.changeset/chilled-paths-keep-x.md @@ -0,0 +1,10 @@ +--- +"changed-modules-go": patch +"changed-roots": patch +--- + +fix: only strip a literal "./" prefix when normalizing paths + +The prefix-stripping regex had an unescaped dot, so it matched any single character followed +by a slash. A module under a single-character top-level directory, such as `x/config`, was +reported as `config`, and consumers received a module directory no directory holds. diff --git a/actions/changed-modules-go/dist/index.js b/actions/changed-modules-go/dist/index.js index 3093794e5..f02702357 100644 --- a/actions/changed-modules-go/dist/index.js +++ b/actions/changed-modules-go/dist/index.js @@ -50649,7 +50649,7 @@ var import_micromatch = __toESM(require_micromatch()); function normalizePath(p) { let s = p.replace(/\\/g, "/"); if (s === "." || s === "./") return "."; - s = s.replace(/^.\//, "").replace(/\/+$/, ""); + s = s.replace(/^\.\//, "").replace(/\/+$/, ""); return s; } function sortDeepestFirst(mods) { diff --git a/actions/changed-modules-go/src/__tests__/path-ops.test.ts b/actions/changed-modules-go/src/__tests__/path-ops.test.ts index 7d8b51e53..f9b92c397 100644 --- a/actions/changed-modules-go/src/__tests__/path-ops.test.ts +++ b/actions/changed-modules-go/src/__tests__/path-ops.test.ts @@ -110,6 +110,26 @@ describe("matchModule", () => { const result = matchModule("scripts/build.js", moduleDirectories); expect(result).toBe("scripts"); }); + + // A single-character top-level directory used to be stripped along with the "./" prefix, + // so "x/config/go.mod" matched a module named "config" that no directory holds. + test("should keep a single-character top-level directory", () => { + const moduleDirectories = ["x/config"]; + const result = matchModule("x/config/go.mod", moduleDirectories); + expect(result).toBe("x/config"); + }); + + test('should still strip a leading "./" prefix', () => { + const moduleDirectories = ["x/config"]; + const result = matchModule("./x/config/go.mod", moduleDirectories); + expect(result).toBe("x/config"); + }); + + test('should normalize a module directory given with a "./" prefix', () => { + const moduleDirectories = ["./x/config"]; + const result = matchModules(["x/config/go.mod"], moduleDirectories); + expect(result).toEqual([["x/config/go.mod", "x/config"]]); + }); }); }); diff --git a/actions/changed-modules-go/src/path-ops.ts b/actions/changed-modules-go/src/path-ops.ts index 95baf17e9..b28a78718 100644 --- a/actions/changed-modules-go/src/path-ops.ts +++ b/actions/changed-modules-go/src/path-ops.ts @@ -14,7 +14,7 @@ function normalizePath(p: string): string { if (s === "." || s === "./") return "."; // strip leading ./ and extra slashes - s = s.replace(/^.\//, "").replace(/\/+$/, ""); + s = s.replace(/^\.\//, "").replace(/\/+$/, ""); return s; } diff --git a/actions/changed-roots/dist/index.js b/actions/changed-roots/dist/index.js index e416e1d25..0f2a207c5 100644 --- a/actions/changed-roots/dist/index.js +++ b/actions/changed-roots/dist/index.js @@ -50654,7 +50654,7 @@ var import_micromatch = __toESM(require_micromatch()); function normalizePath(p) { let s = p.replace(/\\/g, "/"); if (s === "." || s === "./") return "."; - s = s.replace(/^.\//, "").replace(/\/+$/, ""); + s = s.replace(/^\.\//, "").replace(/\/+$/, ""); return s; } function sortDeepestFirst(mods) { diff --git a/actions/changed-roots/src/__tests__/path-ops.test.ts b/actions/changed-roots/src/__tests__/path-ops.test.ts index 8b1814fec..e9a81bb9f 100644 --- a/actions/changed-roots/src/__tests__/path-ops.test.ts +++ b/actions/changed-roots/src/__tests__/path-ops.test.ts @@ -110,6 +110,26 @@ describe("matchModule", () => { const result = matchModule("scripts/build.js", moduleDirectories); expect(result).toBe("scripts"); }); + + // A single-character top-level directory used to be stripped along with the "./" prefix, + // so "x/config/go.mod" matched a module named "config" that no directory holds. + test("should keep a single-character top-level directory", () => { + const moduleDirectories = ["x/config"]; + const result = matchModule("x/config/go.mod", moduleDirectories); + expect(result).toBe("x/config"); + }); + + test('should still strip a leading "./" prefix', () => { + const moduleDirectories = ["x/config"]; + const result = matchModule("./x/config/go.mod", moduleDirectories); + expect(result).toBe("x/config"); + }); + + test('should normalize a module directory given with a "./" prefix', () => { + const moduleDirectories = ["./x/config"]; + const result = matchModules(["x/config/go.mod"], moduleDirectories); + expect(result).toEqual([["x/config/go.mod", "x/config"]]); + }); }); }); diff --git a/actions/changed-roots/src/path-ops.ts b/actions/changed-roots/src/path-ops.ts index dca542336..28abd132a 100644 --- a/actions/changed-roots/src/path-ops.ts +++ b/actions/changed-roots/src/path-ops.ts @@ -14,7 +14,7 @@ function normalizePath(p: string): string { if (s === "." || s === "./") return "."; // strip leading ./ and extra slashes - s = s.replace(/^.\//, "").replace(/\/+$/, ""); + s = s.replace(/^\.\//, "").replace(/\/+$/, ""); return s; }