-
Notifications
You must be signed in to change notification settings - Fork 2k
feat: add COMMAND DOCS command support #3270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
2ca6e89
feat: add COMMAND DOCS command support
ruguoba 5dff530
test: add COMMAND DOCS spec file
ruguoba 0dd36c0
fix: address cursor-bot review comments
ruguoba 6116a44
fix: register COMMAND_DOCS in commands index
ruguoba e1c7900
fix(COMMAND_DOCS): fix all 5 Cursor Bot issues
ruguoba 522ec5c
test(COMMAND_DOCS): improve tests for arguments transformation
ruguoba cd43534
fix: clean up transformArguments for COMMAND DOCS
ruguoba 926d731
fix: use && instead of || in COMMAND DOCS test
ruguoba File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { strict as assert } from 'node:assert'; | ||
| import testUtils, { GLOBAL } from '../test-utils'; | ||
| import COMMAND_DOCS from './COMMAND_DOCS'; | ||
|
|
||
| describe('COMMAND DOCS', () => { | ||
| testUtils.testWithClient('client.commandDocs()', async client => { | ||
| const result = await client.commandDocs(); | ||
| assert.equal(typeof result, 'object'); | ||
| // Verify structure of at least one entry | ||
| const firstKey = Object.keys(result)[0]; | ||
| if (firstKey) { | ||
| const entry = result[firstKey]; | ||
| assert.equal(typeof entry, 'object'); | ||
| // 'arguments' should be an array of objects if present | ||
| if (entry.arguments) { | ||
| assert.ok(Array.isArray(entry.arguments)); | ||
| for (const arg of entry.arguments) { | ||
| assert.equal(typeof arg.name, 'string'); | ||
| assert.equal(typeof arg.type, 'string'); | ||
| } | ||
| } | ||
| } | ||
| }, GLOBAL.SERVERS.OPEN); | ||
|
|
||
| testUtils.testWithClient('client.commandDocs("GET", "SET")', async client => { | ||
| const result = await client.commandDocs('GET', 'SET'); | ||
| assert.equal(typeof result, 'object'); | ||
| // Redis returns lowercase command names | ||
| assert.ok('get' in result && 'set' in result); | ||
|
|
||
| // Verify the returned entry has proper structure | ||
| const key = 'get' in result ? 'get' : 'set'; | ||
| const entry = result[key]; | ||
| assert.equal(typeof entry, 'object'); | ||
|
|
||
| // Check arguments transformation if present | ||
| if (entry.arguments) { | ||
| assert.ok(Array.isArray(entry.arguments)); | ||
| for (const arg of entry.arguments) { | ||
| assert.equal(typeof arg, 'object'); | ||
| assert.equal(typeof arg.name, 'string'); | ||
| assert.equal(typeof arg.type, 'string'); | ||
| } | ||
| } | ||
| }, GLOBAL.SERVERS.OPEN); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| import { CommandParser } from '../client/parser'; | ||
| import { ArrayReply, BlobStringReply, Command, MapReply, Resp2Reply, UnwrapReply } from '../RESP/types'; | ||
|
|
||
| type CommandDocsRawReply = ArrayReply<BlobStringReply | MapReply<BlobStringReply, BlobStringReply>>; | ||
|
|
||
| export type CommandDocsReply = Record<string, { | ||
| summary?: string; | ||
| since?: string; | ||
| group?: string; | ||
| complexity?: string; | ||
| arguments?: Array<{ | ||
| name: string; | ||
| type: string; | ||
| optional?: boolean; | ||
| multiple?: boolean; | ||
| }>; | ||
| }>; | ||
|
|
||
| interface CommandDocsArgument { | ||
| name: string; | ||
| type: string; | ||
| optional?: boolean; | ||
| multiple?: boolean; | ||
| } | ||
|
|
||
| function transformArguments(args: unknown[]): CommandDocsArgument[] { | ||
| if (!Array.isArray(args)) return []; | ||
|
|
||
| return args.map((arg: unknown) => { | ||
| if (!Array.isArray(arg)) return { name: '', type: '' }; | ||
|
|
||
| const result: CommandDocsArgument = { name: '', type: '' }; | ||
| const arr = arg as unknown[]; | ||
|
|
||
| for (let i = 0; i < arr.length; i += 2) { | ||
| const key = arr[i] as string; | ||
| const value = arr[i + 1]; | ||
|
|
||
| switch (key) { | ||
| case 'name': | ||
| result.name = value as string; | ||
| break; | ||
| case 'type': | ||
| result.type = value as string; | ||
| break; | ||
| case 'optional': | ||
| result.optional = true; | ||
| break; | ||
| case 'multiple': | ||
| result.multiple = true; | ||
| break; | ||
| case 'flags': | ||
| if (Array.isArray(value)) { | ||
| if ((value as string[]).includes('optional')) result.optional = true; | ||
| if ((value as string[]).includes('multiple')) result.multiple = true; | ||
| } | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| return result; | ||
| }); | ||
| } | ||
|
|
||
| function transformResp2Entry(details: unknown[]): Record<string, unknown> { | ||
| const entry: Record<string, unknown> = {}; | ||
|
|
||
| for (let j = 0; j < details.length; j += 2) { | ||
| const key = details[j] as string; | ||
| const value = details[j + 1]; | ||
|
|
||
| if (key === 'arguments') { | ||
| entry.arguments = transformArguments(value as unknown[]); | ||
| } else { | ||
| entry[key] = value; | ||
| } | ||
| } | ||
|
|
||
| return entry; | ||
| } | ||
|
|
||
| export default { | ||
| NOT_KEYED_COMMAND: true, | ||
| IS_READ_ONLY: true, | ||
| parseCommand(parser: CommandParser, ...commands: Array<string>) { | ||
| parser.push('COMMAND', 'DOCS'); | ||
| if (commands.length > 0) { | ||
| parser.push(...commands); | ||
| } | ||
| }, | ||
| transformReply: { | ||
| 2(reply: UnwrapReply<Resp2Reply<CommandDocsRawReply>>): CommandDocsReply { | ||
| const result: CommandDocsReply = {}; | ||
| const arr = reply as unknown[]; | ||
|
|
||
| for (let i = 0; i < arr.length; i += 2) { | ||
| const name = arr[i] as string; | ||
| const details = arr[i + 1] as unknown[]; | ||
|
|
||
| if (Array.isArray(details)) { | ||
| result[name] = transformResp2Entry(details) as CommandDocsReply[string]; | ||
| } | ||
| } | ||
|
|
||
| return result; | ||
| }, | ||
| 3: undefined as unknown as () => CommandDocsReply | ||
| } | ||
| } as const satisfies Command; | ||
|
cursor[bot] marked this conversation as resolved.
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.