feat: add api subcommand for raw GraphQL access#121
feat: add api subcommand for raw GraphQL access#121bendrucker wants to merge 10 commits intoschpet:mainfrom
api subcommand for raw GraphQL access#121Conversation
|
Fixing CI... (forgot to re-generate the skill) Also backing out some of those unrelated changes in the skill refs, just the |
|
@bendrucker nice one! i'll take this for a spin tomorrow |
Use `after` instead of `endCursor` as the injected pagination variable to match Linear's GraphQL schema conventions. Add tests for: no API key, null/false coercion, values containing equals signs, single-page pagination, non-connection pagination with --paginate, and file-not-found errors for -F @path.
|
❤️ I also need to kick the tires on this a bit, feel free to review but I'm gonna convert back to a draft pending a bit more manual testing (and CI fix). |
schpet
left a comment
There was a problem hiding this comment.
i think in general, at least to start, going with a more json oriented api might be more simple and easy? open to ideas but that's my hunch
Redesign the api subcommand variable interface for GraphQL-native semantics: - --variable key=value (repeated) with type coercion and @file support - --variables-json for complex nested objects - --variable takes precedence over --variables-json on key conflicts - Custom Cliffy Type for key=value parsing instead of hand-rolled split - Fix deno fmt issue on api-filter.json fixture
|
Ran Results
Sample OutputsSimple Variablelinear api 'query($id: String\!) { issue(id: $id) { id identifier title } }' --variable id=abc-123Complex Filterlinear api 'query($filter: IssueFilter\!) { issues(filter: $filter) { nodes { identifier title } } }' \
--variables-json '{"filter": {"state": {"name": {"eq": "In Progress"}}}}'Multiple Variableslinear api 'mutation($title: String\!, $teamId: String\!) { issueCreate(input: { title: $title, teamId: $teamId }) { success issue { id identifier title } } }' \
--variable title='Fix login bug' --variable teamId=team-xyzMethodology
|
The --silent flag was not suppressing output when the API returned HTTP 400+ status codes in both executeSingle and executePaginated paths.
|
Ready for review! Did another self-review/testing pass on this, and only came up with 9647a7d. |
schpet
left a comment
There was a problem hiding this comment.
couple changes - let me know if you agree! open to push back. also depending on your bandwidth, i'm happy to make the changes i suggested for you.
| variables: Record<string, unknown>, | ||
| headers: Record<string, string>, | ||
| silent: boolean, | ||
| ): Promise<void> { |
There was a problem hiding this comment.
i'm worried this pagination strategy only supports certain graphql queries. e.g. if a graphql document is supplied that has two different fields, it will not work right?
linear api 'query GetBoth($after: String) {
viewer {
assignedIssues(first: 2, after: $after) {
nodes { identifier }
pageInfo { hasNextPage endCursor }
}
createdIssues(first: 2, after: $after) {
nodes { identifier }
pageInfo { hasNextPage endCursor }
}
}
}' --paginatecan you either change this to work reliably, or remove the pagination feature? my recommendation would be to omit it from this PR so we can land it, and if you determine reliable pagination is viable to implement that in a follow up PR.
if dropping it, perhaps we can bake in someting into the help text that helps agents understand they need to paginate stuff, i.e. suggest using pageInfo and cursor based pagination, something like that.
There was a problem hiding this comment.
Good flag, I've used the paginate flag heavily with gh, but this is a legitimate sharp edge. I'll look into how gh attempts to deal with this, if at all (e.g., does it error when --paginate is used with an incompatible query?). If there's no clean and simple answer, taking it out sounds fine.
There was a problem hiding this comment.
Looked into it — gh api --paginate has the exact same limitation, it just silently paginates the first connection and ignores the rest. Added detection that errors if there are multiple connections, with guidance to paginate manually. Also added test coverage for the nested connection case (sub-issues inside issues) to make sure that doesn't false-positive.
| return variables | ||
| } | ||
|
|
||
| async function resolveTypedValue(value: string): Promise<unknown> { |
There was a problem hiding this comment.
this relates to prev discussion
without GraphQL schema type information, there's no way to know if "007" should be string "007" or number 7
repro/explanation here:
https://gist.github.com/schpetbot/60dc745b48cd1fe1f7cf31a9d60421e2
i DO think you could do this with graphql, but are you up for omitting this way of providing variables and doing json to start, and splitting the variable entry tuple parsing stuff on follow up PR?
There was a problem hiding this comment.
Sure, similar to above I will think on this a little and if there's is no simple answer I'll cut this into a series of 2+ stacked PRs so you can land the simple parts and we can keep iterating on the trickier bits.
There was a problem hiding this comment.
Added a roundtrip check that handles this plus a bunch of adjacent cases — hex, octal, leading +, trailing decimals like 1.0, etc. all stay as strings. Added test coverage for leading zeros and scientific notation specifically. --variables-json is still there as the escape hatch.
|
Added workarounds for both comments, but happy to back these features out for this PR as well. |
String(Number(value)) === value prevents "007" from becoming 7 and "1e5" from becoming 100000.
gh api --paginate silently paginates only the first connection. Instead of replicating that footgun, detect multiple connections in the response and error with guidance to paginate manually.
26551da to
b4b50eb
Compare
Adds a
linear apisubcommand for making raw GraphQL requests, mirroringgh apiconventions.Changes
-, or via auto-detected piped input--variable key=valuefor typed variable coercion (booleans, numbers, null,@filefor file reads,@-for stdin)--variables-json '{"key": "value"}'for passing all variables as a JSON object (merged with--variable, which takes precedence)--paginatewalkspageInfo.endCursorautomatically and outputs concatenatednodesarray--silentsuppresses response output while exit code still reflects errorsjqfetchso users see the exact server response including bothdataanderrorsfieldsTesting
MockLinearServercover query resolution, variable handling (type coercion,@file,--variables-json, precedence), output modes, pagination (multi-page, single-page, non-connection), auth errors, and--silentbehavior for both successful and HTTP error responses--paginate), non-existent issue lookup, variable type mismatch errors, stdin pipingRelated
apisubcommand for raw GraphQL access #123