feat: add CompositionClient to cli and bump SDK to v4.38.0#215
Conversation
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 95 |
| Duplication | 153 |
TIP This summary will be updated as you push new changes.
5e25461 to
9c9f982
Compare
34d611e to
44670b1
Compare
44670b1 to
1b105f3
Compare
NatanTechofNY
left a comment
There was a problem hiding this comment.
🤖 AI-Generated Code Review by Claude Code
Overview
Large new feature: 29 files, ~1870 additions adding a full compositions command group (list, get, upsert, delete, search + rules sub-group) with SDK bump to v4.38.0. Well-structured and follows repo conventions closely, with strong unit test coverage. A few consistency gaps versus existing delete commands and one blocking issue (E2E tests won't actually run).
✅ Strengths
- Follows the exact repo pattern:
New...Cmd,RunE,cmdutil.PrintFlags,validators.ExactArgsWithMsg, heredoc examples - Thorough unit tests for every command including timeout, API error, and confirmation cases
- Wait-always design is cleaner UX for compositions than opt-in
--wait - ACL annotations correct: read =
search, write =editSettings
📋 Summary of Findings
- 🚨 HIGH: E2E test script never runs —
TestCompositionsmissing ine2e/e2e_test.go ⚠️ MEDIUM (×4):CanPrompt()guard missing;ErrCancelon decline; prompt error wrapping inconsistency; upsert prints taskID after wait- 💡 LOW: First poll waits full interval before first
GetTaskcall
Generated by Claude Code — automated review focusing on security, performance, and best practices
| @@ -0,0 +1,94 @@ | |||
| env COMP_ID=test-comp-e2e | |||
There was a problem hiding this comment.
🚨 HIGH: E2E test script is never executed
This txtar file exists, but e2e/e2e_test.go has no TestCompositions function. The runner only picks up directories explicitly registered via runTestsInDir. The file currently registers 8 tests (TestVersion, TestIndices, TestSettings, TestObjects, TestSynonyms, TestRules, TestSearch, TestAgentReady) — compositions is absent.
// Add to e2e/e2e_test.go alongside the other Test* functions:
func TestCompositions(t *testing.T) {
runTestsInDir(t, "testscripts/compositions")
}Without this, go test ./e2e -tags=e2e silently skips all compositions txtar scripts.
There was a problem hiding this comment.
Fixed in 1b105f3. Added TestCompositions to e2e/e2e_test.go so the txtar script runs.
| opts.CompositionID = args[0] | ||
|
|
||
| if !opts.DoConfirm { | ||
| var confirmed bool |
There was a problem hiding this comment.
CanPrompt() guard before interactive prompt
Every other delete command in the repo (indices/delete, rules/delete, synonyms/delete, etc.) checks opts.IO.CanPrompt() before attempting an interactive prompt, and returns a clear error in non-TTY contexts:
// from pkg/cmd/rules/delete/delete.go (existing pattern)
if !confirm {
if !opts.IO.CanPrompt() {
return cmdutil.FlagErrorf("--confirm required when non-interactive shell is detected")
}
opts.DoConfirm = true
}Without this check, running in CI or a script without --confirm will call prompt.Confirm on a non-TTY and fail with a cryptic survey error. Please apply the same guard here (and in compositions/rules/delete).
There was a problem hiding this comment.
Added CanPrompt() guard to both compositions/delete and compositions/rules/delete, returning cmdutil.FlagErrorf in non-interactive contexts to match the repo pattern.
| } | ||
| if !confirmed { | ||
| return cmdutil.ErrCancel | ||
| } |
There was a problem hiding this comment.
ErrCancel on decline causes exit code 2 — inconsistent with repo convention
Returning cmdutil.ErrCancel here triggers exit code 2 via root.go's IsUserCancellation mapping. Every other delete command in the repo (indices, rules, synonyms, objects, dictionary, apikeys) returns nil (exit 0) when a user declines — the operation just doesn't happen.
// Change to:
if !confirmed {
return nil
}Scripts checking $? after a declined algolia compositions delete will see an unexpected failure otherwise.
There was a problem hiding this comment.
Changed both delete commands to return nil on decline instead of cmdutil.ErrCancel, matching the convention in indices/delete, rules/delete, etc.
| ) | ||
| if err != nil { | ||
| return err | ||
| } |
There was a problem hiding this comment.
In compositions/delete/delete.go, the equivalent block wraps the error:
return fmt.Errorf("failed to prompt: %w", err)
Here it's returned bare: return err. These are adjacent files in the same PR — pick one style and apply it consistently.
// Change to match compositions/delete:
return fmt.Errorf("failed to prompt: %w", err)There was a problem hiding this comment.
Wrapped the prompt error with fmt.Errorf("failed to prompt: %w", err) to match the sibling compositions/delete style.
| } | ||
|
|
||
| return p.Print(opts.IO, res) | ||
| } |
There was a problem hiding this comment.
After WaitForTask succeeds, p.Print(opts.IO, res) prints the original mutation API response (e.g. {"taskID": 42}). Across the rest of the repo, mutate+wait commands either print a structured summary map (objects/update, rules/import) or a TTY success message (synonyms/save, settings/set) — raw task responses are not the post-wait output pattern.
Users may be confused seeing a taskID when they expected confirmation of the composition state. Consider printing a brief success message (TTY) or a summary struct (non-TTY), or at minimum document in Short/Long that the output is the async task result. Same applies to rule upsert and both delete commands.
There was a problem hiding this comment.
Added a TTY-only success message after WaitForTask for all four mutate commands (upsert + delete, composition + rule). The structured JSON (taskID) is still emitted via PrintFlags for scripting, but TTY users now get explicit confirmation that the operation completed.
| return err | ||
| } | ||
| if resp.Status == algoliaComposition.TASK_STATUS_PUBLISHED { | ||
| io.StopProgressIndicator() |
There was a problem hiding this comment.
💡 LOW: First GetTask poll waits a full PollInterval (2s) before firing
time.NewTicker does not fire immediately — the first tick arrives after one pollInterval. For tasks that complete quickly, this adds up to 2s of unnecessary latency.
Consider issuing one immediate GetTask call before entering the ticker loop:
// Immediate first check before polling loop
resp, err := client.GetTask(client.NewApiGetTaskRequest(compositionID, taskID))
if err != nil { ... }
if resp.Status == algoliaComposition.TASK_STATUS_PUBLISHED {
io.StopProgressIndicator()
return nil
}
// then start the ticker loopThere was a problem hiding this comment.
Added an immediate GetTask call before entering the ticker loop in WaitForTask. Quick-completing tasks now return without waiting a full pollInterval. Existing tests still pass (mock response order is preserved).
- Register TestCompositions in e2e_test.go so the txtar script runs - Guard interactive prompts with CanPrompt() in both delete commands - Return nil (not ErrCancel) when user declines confirmation, matching repo convention - Wrap prompt error in rules/delete to match sibling style - Print TTY success message after WaitForTask in all four mutate commands - Issue an immediate GetTask in WaitForTask so quick tasks skip the first poll wait Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NatanTechofNY
left a comment
There was a problem hiding this comment.
🤖 AI Follow-up Review by Claude Code (commit 2ba948b)
All 6 findings from the previous round have been addressed — thank you for the fast turnaround.
✅ Previous issues resolved
| Issue | Status |
|---|---|
🚨 E2E test never ran — missing TestCompositions in e2e/e2e_test.go |
✅ Fixed |
CanPrompt() guard before interactive prompt in both delete commands |
✅ Fixed |
ErrCancel on decline → exit code 2 (inconsistent with rest of repo) |
✅ Fixed — now returns nil |
rules/delete |
✅ Fixed — now wrapped with fmt.Errorf("failed to prompt: %w") |
| ✅ Fixed — TTY success message added | |
💡 First GetTask poll wasted a full PollInterval |
✅ Fixed — immediate first check added before ticker loop |
💡 Minor nit (non-blocking)
On TTY, mutate commands (upsert, rules upsert, delete, rules delete) currently print both a success line and the JSON task response:
✓ Upserted composition my-comp
{"taskID":42}
The success message is a nice touch, but the JSON that follows (from p.Print(opts.IO, res)) makes the TTY output a little noisy. Other async commands in the repo (e.g. synonyms/save) print only the success message on TTY and return nil. This is a style preference — either keeping both (useful for piping) or suppressing the JSON on TTY would be reasonable. Up to you.
Approved. Clean implementation, good test coverage, and all review feedback addressed. 🎉
Generated by Claude Code — automated review
Summary
Adds a new compositions command group to the Algolia CLI covering the full management lifecycle for Algolia Compositions (https://www.algolia.com/doc/rest-api/composition/).
This also bumps the Algolia Go SDK from v4.35.0 to v4.38.0 to pick up the composition package.
Commands added
Composition management:
Composition rule management:
Design notes
indicescommand group.Changes
go.mod / go.sum: Bump algoliasearch-client-go/v4 to v4.38.0pkg/cmdutil/factory.go: Add CompositionClient fieldpkg/cmd/factory/default.go: Implement compositionClient() factory functionpkg/cmd/root/root.go: Register NewCompositionsCmdpkg/cmd/compositions/: New command groupTests
Each command has a unit test file covering:
End to end test script:
e2e/testscripts/compositions/compositions.txtar