Update changes - #773
Conversation
PR Reviewer Guide 🔍(Review updated until commit 752e569)
|
| for (const [path, pathItem] of Object.entries(document.paths ?? {})) { | ||
| if (!ALLOWED_PATH.test(path)) continue; | ||
| for (const [method, value] of Object.entries(pathItem)) { | ||
| if (!HTTP_METHODS.has(method) || !value || typeof value !== 'object') continue; | ||
| const operation = value as OpenApiOperation; | ||
| if (isStreamOperation(operation)) continue; | ||
| candidates.push({ | ||
| id: operation.operationId ?? fallbackId(method, path, operation.tags), | ||
| method, | ||
| path, | ||
| operation, | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
Suggestion: Include path-item-level OpenAPI parameters when constructing an operation. OpenAPI permits shared parameters such as path IDs to be declared on the path item, and ignoring them lets calls omit required values or rejects valid --param inputs. [possible bug, importance: 6]
| for (const [path, pathItem] of Object.entries(document.paths ?? {})) { | |
| if (!ALLOWED_PATH.test(path)) continue; | |
| for (const [method, value] of Object.entries(pathItem)) { | |
| if (!HTTP_METHODS.has(method) || !value || typeof value !== 'object') continue; | |
| const operation = value as OpenApiOperation; | |
| if (isStreamOperation(operation)) continue; | |
| candidates.push({ | |
| id: operation.operationId ?? fallbackId(method, path, operation.tags), | |
| method, | |
| path, | |
| operation, | |
| }); | |
| } | |
| } | |
| for (const [path, pathItem] of Object.entries(document.paths ?? {})) { | |
| if (!ALLOWED_PATH.test(path)) continue; | |
| const pathParameters = Array.isArray(pathItem.parameters) | |
| ? (pathItem.parameters as OpenApiParameter[]) | |
| : []; | |
| for (const [method, value] of Object.entries(pathItem)) { | |
| if (!HTTP_METHODS.has(method) || !value || typeof value !== 'object') continue; | |
| const operation = value as OpenApiOperation; | |
| if (isStreamOperation(operation)) continue; | |
| const parameters = new Map( | |
| pathParameters.map((parameter) => [`${parameter.in}:${parameter.name}`, parameter]) | |
| ); | |
| for (const parameter of operation.parameters ?? []) { | |
| parameters.set(`${parameter.in}:${parameter.name}`, parameter); | |
| } | |
| candidates.push({ | |
| id: operation.operationId ?? fallbackId(method, path, operation.tags), | |
| method, | |
| path, | |
| operation: { ...operation, parameters: [...parameters.values()] }, | |
| }); | |
| } | |
| } |
| if (args.all) { | ||
| if (body) throw new CliError('--all cannot be used with a request body'); | ||
| runtime.stdout( | ||
| formatOutput( | ||
| await fetchAllPages(baseUrl, operation, args.params, request.headers, runtime.fetch), | ||
| output | ||
| ) | ||
| ); | ||
| return 0; | ||
| } |
There was a problem hiding this comment.
Suggestion: Restrict --all to GET operations before issuing paginated requests. As written, a documented DELETE or other mutation with limit and offset can be repeatedly executed, including after just one risky-operation confirmation. [security, importance: 8]
| if (args.all) { | |
| if (body) throw new CliError('--all cannot be used with a request body'); | |
| runtime.stdout( | |
| formatOutput( | |
| await fetchAllPages(baseUrl, operation, args.params, request.headers, runtime.fetch), | |
| output | |
| ) | |
| ); | |
| return 0; | |
| } | |
| if (args.all) { | |
| if (operation.method !== 'get') throw new CliError('--all can only be used with GET operations'); | |
| if (body) throw new CliError('--all cannot be used with a request body'); | |
| runtime.stdout( | |
| formatOutput( | |
| await fetchAllPages(baseUrl, operation, args.params, request.headers, runtime.fetch), | |
| output | |
| ) | |
| ); | |
| return 0; | |
| } |
| limit ??= page.data.length; | ||
| if (!limit || page.data.length === 0) break; | ||
| allData.push(...page.data); | ||
| offset += limit; |
There was a problem hiding this comment.
Suggestion: Advance the offset by the number of records actually returned, rather than the requested limit. Servers may clamp a requested limit or return short pages, and advancing by the requested value can skip records. [possible bug, importance: 6]
| limit ??= page.data.length; | |
| if (!limit || page.data.length === 0) break; | |
| allData.push(...page.data); | |
| offset += limit; | |
| const received = page.data.length; | |
| limit ??= received; | |
| if (!limit || received === 0) break; | |
| allData.push(...page.data); | |
| offset += received; |
| "bin": { | ||
| "plexuscli": "src/index.ts" | ||
| }, |
There was a problem hiding this comment.
Suggestion: npm-generated command shims execute bin targets with Node, which cannot run a TypeScript entrypoint. Point this at a published JavaScript launcher that invokes Bun (or at a bundled JavaScript entrypoint), so npm install -g @mcowger/plexus-cli produces a working plexuscli command. [possible bug, importance: 7]
| "bin": { | |
| "plexuscli": "src/index.ts" | |
| }, | |
| "bin": { | |
| "plexuscli": "bin/plexuscli.js" | |
| } |
| limit ??= page.data.length; | ||
| if (!limit || page.data.length === 0) break; | ||
| allData.push(...page.data); | ||
| offset += limit; |
There was a problem hiding this comment.
Suggestion: Advance offset by the number of records actually returned, not the requested limit. Servers may clamp limits or return partial pages; using the requested value can skip records during --all retrieval. [possible bug, importance: 7]
| limit ??= page.data.length; | |
| if (!limit || page.data.length === 0) break; | |
| allData.push(...page.data); | |
| offset += limit; | |
| limit ??= page.data.length; | |
| if (!limit || page.data.length === 0) break; | |
| allData.push(...page.data); | |
| offset += page.data.length; |
| const operation = value as OpenApiOperation; | ||
| if (isStreamOperation(operation)) continue; | ||
| candidates.push({ | ||
| id: operation.operationId ?? fallbackId(method, path, operation.tags), | ||
| method, | ||
| path, | ||
| operation, | ||
| }); |
There was a problem hiding this comment.
Suggestion: Merge path-item parameters into each discovered operation before building requests. OpenAPI permits shared path parameters at the path-item level, and ignoring them makes valid operations fail with unknown or missing parameters. [possible issue, importance: 6]
| const operation = value as OpenApiOperation; | |
| if (isStreamOperation(operation)) continue; | |
| candidates.push({ | |
| id: operation.operationId ?? fallbackId(method, path, operation.tags), | |
| method, | |
| path, | |
| operation, | |
| }); | |
| const operationValue = value as OpenApiOperation; | |
| const operation: OpenApiOperation = { | |
| ...operationValue, | |
| parameters: [ | |
| ...(((pathItem.parameters as OpenApiParameter[] | undefined) ?? [])), | |
| ...(operationValue.parameters ?? []), | |
| ], | |
| }; | |
| if (isStreamOperation(operation)) continue; | |
| candidates.push({ | |
| id: operation.operationId ?? fallbackId(method, path, operation.tags), | |
| method, | |
| path, | |
| operation, | |
| }); |
| let body = args.body; | ||
| if (args.bodyFile) body = await Bun.file(args.bodyFile).text(); | ||
| if (body === '-') body = await runtime.stdin(); | ||
| if (!body && !runtime.isTTY) body = (await runtime.stdin()).trim() || undefined; |
There was a problem hiding this comment.
Suggestion: Only consume implicit stdin when the discovered operation accepts a request body. As written, every non-TTY read-only call consumes stdin and can block when the parent process keeps its pipe open. [possible bug, importance: 6]
| if (!body && !runtime.isTTY) body = (await runtime.stdin()).trim() || undefined; | |
| if (!body && !runtime.isTTY && operation.operation.requestBody) | |
| body = (await runtime.stdin()).trim() || undefined; |
Summary
Adds a dynamically discovered Plexus management CLI and makes the OpenAPI contract available to it at runtime.
@mcowger/plexus-cli/plexuscli, including OpenAPI-driven operation discovery, structured output, destructive-operation prompts, pagination, compiled binary artifacts, and npm trusted publishing from the release workflow.plexus-cliandplexus-rest-api, prefer the CLI when available, and expose both skill documents on the MCP page.