From 9ce8929c464f61edd7b26a9e81ef1de2df13c744 Mon Sep 17 00:00:00 2001 From: Vagabond Date: Sun, 11 Oct 2026 02:08:41 +0700 Subject: [PATCH] refactor(cli): fold selectors into inspect --- .changeset/compose-selectors.md | 5 -- .changeset/inspect-unknown-signatures.md | 5 ++ cli/src/comander.ts | 8 +-- cli/src/modules/inspect/output.ts | 6 +- cli/src/modules/pipelineBuilder/module.ts | 8 --- cli/src/modules/selectors/module.ts | 15 ----- cli/src/modules/selectors/output.ts | 23 ------- cli/src/pipelines/selectorsPipeline.ts | 22 ------- cli/test/comander.test.ts | 25 ++----- .../{selectors => inspect}/output.test.ts | 13 ++-- .../pipelineBuilder/pipelineBuilder.test.ts | 16 ----- .../selectorsPipeline.test.ts | 66 ------------------- 12 files changed, 25 insertions(+), 187 deletions(-) delete mode 100644 .changeset/compose-selectors.md create mode 100644 .changeset/inspect-unknown-signatures.md delete mode 100644 cli/src/modules/selectors/module.ts delete mode 100644 cli/src/modules/selectors/output.ts delete mode 100644 cli/src/pipelines/selectorsPipeline.ts rename cli/test/modules/{selectors => inspect}/output.test.ts (74%) delete mode 100644 cli/test/pipelines/selectorsPipeline/selectorsPipeline.test.ts diff --git a/.changeset/compose-selectors.md b/.changeset/compose-selectors.md deleted file mode 100644 index 83444612..00000000 --- a/.changeset/compose-selectors.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@perfect-abstractions/compose-cli": patch ---- - -Add `compose selectors` to list and decode a deployed diamond's selectors by facet. diff --git a/.changeset/inspect-unknown-signatures.md b/.changeset/inspect-unknown-signatures.md new file mode 100644 index 00000000..7a7b641d --- /dev/null +++ b/.changeset/inspect-unknown-signatures.md @@ -0,0 +1,5 @@ +--- +"@perfect-abstractions/compose-cli": patch +--- + +Mark selectors with unknown signatures in `compose inspect` output. diff --git a/cli/src/comander.ts b/cli/src/comander.ts index cdfcd681..899fecd4 100644 --- a/cli/src/comander.ts +++ b/cli/src/comander.ts @@ -61,12 +61,6 @@ export function buildProgram(): Command { .argument("
", "Diamond contract address") .option("--chain ", "Chain key from compose.json", "local") - program - .command("selectors") - .description("List a deployed diamond's selectors by facet") - .argument("
", "Diamond contract address") - .option("--chain ", "Chain key from compose.json", "local") - return program; } @@ -100,7 +94,7 @@ export function parseArgs(argv: string[]): { command: string; flags: Record typeof arg === "string" && arg !== command, ); if (positionalArgs.length > 0) { - if ((command === "inspect" || command === "selectors") && !flags.address) { + if (command === "inspect" && !flags.address) { flags.address = positionalArgs[0]; } else if (!flags.projectName) { flags.projectName = positionalArgs[0]; diff --git a/cli/src/modules/inspect/output.ts b/cli/src/modules/inspect/output.ts index 95b0ed52..a921c44b 100644 --- a/cli/src/modules/inspect/output.ts +++ b/cli/src/modules/inspect/output.ts @@ -1,4 +1,4 @@ -import { cyan, dim, green } from "../../utils/terminal"; +import { cyan, dim, green, yellow } from "../../utils/terminal"; import type { InspectResult } from "./types"; const TREE_BRANCH = "├── "; @@ -35,7 +35,9 @@ export function showInspect(result: InspectResult): void { const isLastSelector = j === facet.selectors.length - 1; const selectorBranch = isLastSelector ? TREE_LAST : TREE_BRANCH; - console.log(` ${dim(childPrefix + selectorBranch)}${dim(sel.selector)} ${green(sel.signature)}`); + const unknown = sel.signature.toLowerCase() === sel.selector.toLowerCase(); + const signature = unknown ? yellow("Unknown signature") : green(sel.signature); + console.log(` ${dim(childPrefix + selectorBranch)}${dim(sel.selector)} ${signature}`); } if (!isLast) { diff --git a/cli/src/modules/pipelineBuilder/module.ts b/cli/src/modules/pipelineBuilder/module.ts index f065bf81..df1788e9 100644 --- a/cli/src/modules/pipelineBuilder/module.ts +++ b/cli/src/modules/pipelineBuilder/module.ts @@ -5,7 +5,6 @@ import { CatalogPipeline } from "../../pipelines/catalogPipeline"; import { BuildPipeline } from "../../pipelines/buildPipeline"; import { RPCPipeline } from "../../pipelines/rpcPipeline"; import { InspectPipeline } from "../../pipelines/inspectPipeline"; -import { SelectorsPipeline } from "../../pipelines/selectorsPipeline"; import { ValidatePipeline } from "../../pipelines/validatePipeline"; /** @@ -77,13 +76,6 @@ export const PipelineBuilderModule = { error: null, }; return InspectPipeline.execute(ctx); - case "selectors": - ctx.state.commandSelected = { - success: true, - result: { command: ctx.param.command, address: ctx.param.address, chain: ctx.param.chain }, - error: null, - }; - return SelectorsPipeline.execute(ctx); default: ctx.state.commandRouting = { success: false, diff --git a/cli/src/modules/selectors/module.ts b/cli/src/modules/selectors/module.ts deleted file mode 100644 index 211be623..00000000 --- a/cli/src/modules/selectors/module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { IDiamondAdapter } from "../../adapters/IDiamondAdapter/interface"; -import type { ComposeContext } from "../../context/types"; -import { inspectAddress } from "../inspect/module"; -import { loadDiamondSelectors } from "../inspect/selectorLoader"; -import type { ResolvedChainConfig } from "../../utils/chainConfig"; -import { showSelectors } from "./output"; - -export const SelectorsModule = { - async list(ctx: ComposeContext, diamond: IDiamondAdapter, chain: ResolvedChainConfig): Promise { - const result = await loadDiamondSelectors(inspectAddress(ctx.param.address), diamond, chain); - ctx.state.selectors = { success: true, result, error: null }; - showSelectors(result); - return ctx; - }, -}; diff --git a/cli/src/modules/selectors/output.ts b/cli/src/modules/selectors/output.ts deleted file mode 100644 index 0c0703b0..00000000 --- a/cli/src/modules/selectors/output.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { cyan, dim, green, yellow } from "../../utils/terminal"; -import type { InspectResult } from "../inspect/types"; - -/** Prints a selector-first view grouped by facet address. */ -export function showSelectors(result: InspectResult): void { - console.log(`\n${cyan("Diamond Selectors")}\n`); - console.log(` Diamond: ${result.diamond}`); - console.log(` Chain: ${result.chainKey} (${result.chainId})`); - console.log(); - - for (const facet of result.facets) { - console.log(` ${facet.address}`); - if (facet.selectors.length === 0) { - console.log(` ${dim("No selectors")}`); - } - for (const { selector, signature } of facet.selectors) { - const unknown = signature.toLowerCase() === selector.toLowerCase(); - console.log(` ${dim(selector)} ${unknown ? yellow("Unknown signature") : green(signature)}`); - } - console.log(); - } - if (result.facets.length === 0) console.log(` ${dim("No facets found")}\n`); -} diff --git a/cli/src/pipelines/selectorsPipeline.ts b/cli/src/pipelines/selectorsPipeline.ts deleted file mode 100644 index c81b162b..00000000 --- a/cli/src/pipelines/selectorsPipeline.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ComposeContext } from "../context/types"; -import { inspectAddress } from "../modules/inspect/module"; -import { SelectorsModule } from "../modules/selectors/module"; -import { DependencyKey } from "../resolver/dependencyKey"; -import { DependencyResolver } from "../resolver/dependencyResolver"; -import { resolveChainConfig } from "../utils/chainConfig"; - -/** Focused on-chain selector listing pipeline. */ -export const SelectorsPipeline = { - async execute(ctx: ComposeContext): Promise { - inspectAddress(ctx.param.address); - const chainKey = typeof ctx.param.chain === "string" ? ctx.param.chain : "local"; - const chain = await resolveChainConfig({ chainKey }); - const dependencies = await DependencyResolver.resolve([{ - key: DependencyKey.Diamond, - params: { chainKey: chain.chainKey }, - }]); - const diamond = dependencies[DependencyKey.Diamond]; - if (!diamond) throw new Error("Diamond dependency was not resolved"); - return SelectorsModule.list(ctx, diamond, chain); - }, -}; diff --git a/cli/test/comander.test.ts b/cli/test/comander.test.ts index facd8af8..669259db 100644 --- a/cli/test/comander.test.ts +++ b/cli/test/comander.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { parseArgs } from "../src/comander"; +import { buildProgram, parseArgs } from "../src/comander"; describe("validate command arguments", () => { it("accepts a Compose project root", () => { @@ -68,6 +68,12 @@ describe("rpc command", () => { }); describe("inspect command", () => { + it("is the only facet and selector listing command", () => { + const commands = buildProgram().commands.map((command) => command.name()); + expect(commands).toContain("inspect"); + expect(commands).not.toContain("selectors"); + }); + it("parses address positional and chain flag", () => { const result = parseArgs([ "node", @@ -103,21 +109,4 @@ describe("inspect command", () => { }, }); }); - -}); - -describe("selectors command", () => { - it("parses address and chain", () => { - expect(parseArgs(["node", "compose", "selectors", "0x0000000000000000000000000000000000000001", "--chain", "sepolia"])).toEqual({ - command: "selectors", - flags: { address: "0x0000000000000000000000000000000000000001", chain: "sepolia" }, - }); - }); - - it("defaults to local", () => { - expect(parseArgs(["node", "compose", "selectors", "0x0000000000000000000000000000000000000001"])).toEqual({ - command: "selectors", - flags: { address: "0x0000000000000000000000000000000000000001", chain: "local" }, - }); - }); }); diff --git a/cli/test/modules/selectors/output.test.ts b/cli/test/modules/inspect/output.test.ts similarity index 74% rename from cli/test/modules/selectors/output.test.ts rename to cli/test/modules/inspect/output.test.ts index 6b797141..ca1aedbf 100644 --- a/cli/test/modules/selectors/output.test.ts +++ b/cli/test/modules/inspect/output.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it, vi } from "vitest"; -import { showSelectors } from "../../../src/modules/selectors/output"; +import { showInspect } from "../../../src/modules/inspect/output"; -describe("showSelectors", () => { - it("groups signatures by facet and marks unknown selectors", () => { +describe("showInspect", () => { + it("shows known signatures and marks unknown selectors", () => { const log = vi.spyOn(console, "log").mockImplementation(() => {}); try { - showSelectors({ + showInspect({ diamond: "0x0000000000000000000000000000000000000001", chainKey: "local", chainId: 31337, @@ -18,10 +18,13 @@ describe("showSelectors", () => { ], }], }); + const output = log.mock.calls.map(([line]) => line).join("\n"); + expect(output).toContain("1 facets"); + expect(output).toContain("2 selectors"); expect(output).toContain("facets()"); - expect(output).toContain("Unknown signature"); expect(output).toContain("0xdeadbeef"); + expect(output).toContain("Unknown signature"); } finally { log.mockRestore(); } diff --git a/cli/test/modules/pipelineBuilder/pipelineBuilder.test.ts b/cli/test/modules/pipelineBuilder/pipelineBuilder.test.ts index 72ed2dd4..5a957103 100644 --- a/cli/test/modules/pipelineBuilder/pipelineBuilder.test.ts +++ b/cli/test/modules/pipelineBuilder/pipelineBuilder.test.ts @@ -2,7 +2,6 @@ import { describe, expect, it, vi } from "vitest"; import { Context } from "../../../src/context/context"; import { PipelineBuilderModule } from "../../../src/modules/pipelineBuilder/module"; import { ValidatePipeline } from "../../../src/pipelines/validatePipeline"; -import { SelectorsPipeline } from "../../../src/pipelines/selectorsPipeline"; describe("PipelineBuilderModule", () => { it("routes the validate command to ValidatePipeline", async () => { @@ -20,19 +19,4 @@ describe("PipelineBuilderModule", () => { execute.mockRestore(); } }); - - it("routes the selectors command to SelectorsPipeline", async () => { - const ctx = Context.create(); - ctx.param = { command: "selectors", address: "0x0000000000000000000000000000000000000001", chain: "local" }; - const execute = vi.spyOn(SelectorsPipeline, "execute").mockResolvedValue(ctx); - - try { - const result = await PipelineBuilderModule.route(ctx); - expect(result).toBe(ctx); - expect(execute).toHaveBeenCalledWith(ctx); - expect(ctx.state.commandSelected).toMatchObject({ success: true, result: ctx.param }); - } finally { - execute.mockRestore(); - } - }); }); diff --git a/cli/test/pipelines/selectorsPipeline/selectorsPipeline.test.ts b/cli/test/pipelines/selectorsPipeline/selectorsPipeline.test.ts deleted file mode 100644 index dd202064..00000000 --- a/cli/test/pipelines/selectorsPipeline/selectorsPipeline.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { describe, expect, it, vi } from "vitest"; -import { Context } from "../../../src/context/context"; -import { DependencyKey } from "../../../src/resolver/dependencyKey"; - -const mocks = vi.hoisted(() => ({ - resolveChainConfig: vi.fn(), - resolve: vi.fn(), - showSelectors: vi.fn(), -})); - -vi.mock("../../../src/utils/chainConfig", () => ({ resolveChainConfig: mocks.resolveChainConfig })); -vi.mock("../../../src/resolver/dependencyResolver", () => ({ DependencyResolver: { resolve: mocks.resolve } })); -vi.mock("../../../src/modules/selectors/output", () => ({ showSelectors: mocks.showSelectors })); - -import { SelectorsPipeline } from "../../../src/pipelines/selectorsPipeline"; - -const address = "0x0000000000000000000000000000000000000001"; - -describe("SelectorsPipeline", () => { - it("rejects an invalid address before resolving the chain", async () => { - const ctx = Context.create(); - ctx.param = { address: "invalid" }; - await expect(SelectorsPipeline.execute(ctx)).rejects.toMatchObject({ code: "RPC_INVALID_ADDRESS" }); - expect(mocks.resolveChainConfig).not.toHaveBeenCalled(); - }); - - it("propagates an unknown chain error before resolving the adapter", async () => { - mocks.resolveChainConfig.mockRejectedValueOnce(new Error("Unknown chain: missing")); - const ctx = Context.create(); - ctx.param = { address, chain: "missing" }; - - await expect(SelectorsPipeline.execute(ctx)).rejects.toThrow("Unknown chain: missing"); - expect(mocks.resolve).not.toHaveBeenCalled(); - }); - - it("queries facets and decodes selectors", async () => { - const facets = vi.fn().mockResolvedValue([ - { facet: "0x0000000000000000000000000000000000000002", functionSelectors: ["0x7a0ed627", "0xdeadbeef"] }, - ]); - mocks.resolveChainConfig.mockResolvedValue({ chainKey: "sepolia", rpcUrl: "https://rpc.example", chainId: 11155111 }); - mocks.resolve.mockResolvedValue({ [DependencyKey.Diamond]: { facets } }); - - const ctx = Context.create(); - ctx.param = { address, chain: "sepolia" }; - const result = await SelectorsPipeline.execute(ctx); - - expect(mocks.resolve).toHaveBeenCalledWith([{ key: DependencyKey.Diamond, params: { chainKey: "sepolia" } }]); - expect(facets).toHaveBeenCalledWith(address); - expect(result.state.selectors).toMatchObject({ - success: true, - result: { facets: [{ selectors: [ - { selector: "0x7a0ed627", signature: "facets()" }, - { selector: "0xdeadbeef", signature: "0xdeadbeef" }, - ] }] }, - }); - expect(mocks.showSelectors).toHaveBeenCalledOnce(); - }); - - it("propagates RPC failures", async () => { - mocks.resolveChainConfig.mockResolvedValue({ chainKey: "local", rpcUrl: "http://localhost", chainId: 31337 }); - mocks.resolve.mockResolvedValue({ [DependencyKey.Diamond]: { facets: vi.fn().mockRejectedValue(new Error("RPC unavailable")) } }); - const ctx = Context.create(); - ctx.param = { address }; - await expect(SelectorsPipeline.execute(ctx)).rejects.toThrow("RPC unavailable"); - }); -});