From 34d3fac10a97b74cc4d272f8896f829e73f3b3e1 Mon Sep 17 00:00:00 2001 From: bbenligiray Date: Wed, 6 Aug 2025 14:14:09 +0300 Subject: [PATCH] Add an implicit "all" group --- .changeset/young-maps-sort.md | 6 ++++++ __tests__/group.test.js | 36 ++++++++++++++++++++++++++++++----- src/group.js | 14 +++++++++++--- src/report.js | 3 ++- 4 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 .changeset/young-maps-sort.md diff --git a/.changeset/young-maps-sort.md b/.changeset/young-maps-sort.md new file mode 100644 index 0000000..0760594 --- /dev/null +++ b/.changeset/young-maps-sort.md @@ -0,0 +1,6 @@ +--- +"hinter-cline": minor +--- + +Add an implicit group called "all" that includes all peers. +If you have already created a group with this name on v0.1, it will be overriden so you are recommended to remove it. diff --git a/__tests__/group.test.js b/__tests__/group.test.js index cbe46d9..ce78a3c 100644 --- a/__tests__/group.test.js +++ b/__tests__/group.test.js @@ -34,8 +34,9 @@ describe("group", () => { }); describe("getGroups", () => { - it("should return a map of groups and their members", async () => { - getPeerAliases.mockResolvedValue(["peer1", "peer2", "peer3"]); + it("should return a map of groups and their members, including the 'all' group", async () => { + const peerAliases = ["peer1", "peer2", "peer3"]; + getPeerAliases.mockResolvedValue(peerAliases); getPeerConfig.mockImplementation((_, alias) => { const configs = { peer1: { "hinter-cline": { groups: ["group1"] } }, @@ -47,12 +48,14 @@ describe("group", () => { const groups = await group.getGroups(DATA_PATH); expect(groups.get("group1")).toEqual(["peer1", "peer2"]); expect(groups.get("group2")).toEqual(["peer2", "peer3"]); + expect(groups.get("all")).toEqual(peerAliases); }); - it("should return an empty map if no peers exist", async () => { + it("should return a map with only the 'all' group if no peers exist", async () => { getPeerAliases.mockResolvedValue([]); const groups = await group.getGroups(DATA_PATH); - expect(groups.size).toBe(0); + expect(groups.size).toBe(1); + expect(groups.get("all")).toEqual([]); }); }); @@ -76,6 +79,15 @@ describe("group", () => { expect(updatePeerConfig).not.toHaveBeenCalled(); }); + it('should not add a group named "all"', async () => { + question.mockResolvedValue("all"); + const logSpy = jest.spyOn(console, "log").mockImplementation(() => {}); + await group.addGroup(DATA_PATH); + expect(logSpy).toHaveBeenCalledWith('The group name "all" is reserved.'); + expect(updatePeerConfig).not.toHaveBeenCalled(); + logSpy.mockRestore(); + }); + it("should not add a group if alias already exists", async () => { question.mockResolvedValue("existing-group"); getPeerAliases.mockResolvedValue(["peer1"]); @@ -127,7 +139,7 @@ describe("group", () => { }); describe("manageGroup", () => { - it("should do nothing if no groups exist", async () => { + it("should do nothing if no manageable groups exist", async () => { getPeerAliases.mockResolvedValue([]); const logSpy = jest.spyOn(console, "log").mockImplementation(() => {}); await group.manageGroup(DATA_PATH); @@ -135,6 +147,20 @@ describe("group", () => { logSpy.mockRestore(); }); + it("should not show the 'all' group in the list of manageable groups", async () => { + getPeerAliases.mockResolvedValue(["peer1"]); + getPeerConfig.mockResolvedValue({ + "hinter-cline": { groups: ["group1"] }, + }); + selectFromList.mockResolvedValue([]); + await group.manageGroup(DATA_PATH); + expect(selectFromList).toHaveBeenCalledWith( + ["group1"], + "Select a group to manage.", + { allowMultiple: false }, + ); + }); + it("should do nothing if no group is selected", async () => { getPeerAliases.mockResolvedValue(["peer1"]); getPeerConfig.mockResolvedValue({ diff --git a/src/group.js b/src/group.js index 43ee616..49ab2fe 100644 --- a/src/group.js +++ b/src/group.js @@ -19,6 +19,10 @@ async function getGroups(dataPath) { groups.get(group).push(peerAlias); } } + + // Add the implicit "all" group last to override user-defined versions + groups.set("all", peerAliases); + return groups; } @@ -27,6 +31,10 @@ async function addGroup(dataPath) { const newGroupAlias = await question( "Enter new group alias (e.g., ai-developers): ", ); + if (newGroupAlias === "all") { + console.log('The group name "all" is reserved.'); + return; + } if (!isValidSlug(newGroupAlias)) { console.log( "Invalid alias format. Use lowercase letters, numbers, and single hyphens.", @@ -77,12 +85,12 @@ async function addGroup(dataPath) { async function manageGroup(dataPath) { console.log("\n--- Manage a group ---"); const groups = await getGroups(dataPath); - if (groups.size === 0) { + + const groupAliases = Array.from(groups.keys()).filter((g) => g !== "all"); + if (groupAliases.length === 0) { console.log("No groups to manage."); return; } - - const groupAliases = Array.from(groups.keys()); let selectedGroupAliases; try { selectedGroupAliases = await selectFromList( diff --git a/src/report.js b/src/report.js index 9d6f014..4003510 100644 --- a/src/report.js +++ b/src/report.js @@ -26,9 +26,10 @@ async function createDraft(dataPath) { const peerAliases = await getPeerAliases(dataPath); const groups = await getGroups(dataPath); - const groupAliases = Array.from(groups.keys()); + const groupAliases = Array.from(groups.keys()).filter((g) => g !== "all"); const availableRecipients = [ + "group:all", ...groupAliases.map((groupAlias) => `group:${groupAlias}`), ...peerAliases, ];