Skip to content

fix: poll for publish completion in interactive sdk publish - #315

Merged
mrafnadeem-apimatic merged 12 commits into
devfrom
fix/interactive-sdk-publish-polling
Aug 11, 2026
Merged

fix: poll for publish completion in interactive sdk publish#315
mrafnadeem-apimatic merged 12 commits into
devfrom
fix/interactive-sdk-publish-polling

Conversation

@mrafnadeem-apimatic

@mrafnadeem-apimatic mrafnadeem-apimatic commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Problem

The two modes of apimatic sdk publish disagreed about when the command was done.

  • Interactive returned as soon as the publishing API accepted the POST. It printed "To view the status of publishing, please visit…" and returned success(), so the command exited 0 whether publishing later succeeded or failed.
  • Non-interactive polled getSdkPublishingLog every 10 s to a terminal state, printed the closing note, and returned failed() when publishing did not succeed.

On cancellation with Ctrl+C, the CLI should say, Publishing is still running on APIMatic and will continue without the CLI..

Change

Polling moved into the shared SdkPublishAction, so both modes run one identical post-POST sequence: running notice → poll → closing note with the log URL. The poll runs after the withDirPath block closes, releasing the temp directory holding the zipped SDK before the CLI sits waiting for minutes.

  • Interactive's sdkPublishingInProgress message is deleted — publishing is no longer in flight when the command exits, so its wording had become wrong. Non-interactive's log URL wording is reused verbatim for both outcomes; the log URL is useful on success and essential on failure.
  • pollPublishingStatus returns 'succeeded' | 'failed' | 'cancelled' (exported from the prompts module, following QuickstartFlow). A boolean cannot express cancel-vs-fail, and the distinction matters for planned work. ActionResult stays out of the prompts layer.
  • SdkPublishAction.execute now returns Promise<ActionResult> rather than ActionResult<PublishingInfo>. Nothing reads the payload now that the action prints its own notes, and the old signature carried a hazard: the dry-run branch returns success() with no value, so getValue() on it throws.
  • Non-interactive behaviour is unchanged.

Deliberately not in scope

  • The completion predicate is unchanged. events.every(...) is vacuously true on an empty array, so an empty or partially-populated publish log reports instant false success. Tracked in sdk publish: missing array length check makes an empty publish-log report a false success #312, along with the lack of retry tolerance on transient status-fetch errors and the unbounded wait.
  • No timeout. Any ceiling is a guess about the slowest legitimate publish, and guessing low turns a slow success into a reported failure. Ctrl+C is the interactive escape hatch; job timeouts are the CI one.
  • The poll loop stays in the prompts layer, moved verbatim. Relocating it to a service (as portal-service.ts's pollUntilCompleted would suggest) means inventing a status-callback seam across a layer boundary — a refactor riding along on a parity fix.
  • No telemetry for publishes that fail after being accepted. SdkPublishValidationFailedEvent models a rejected publish request; folding remote failures into it would corrupt that metric. Tracked in sdk publish: no telemetry for publishes that fail after being accepted #313.

Testing

test/ has no publish coverage and no prompts tests at all. Meaningful coverage here needs either fake timers plus stdout capture against a live clack spinner, or dependency injection into SdkPublishAction (which constructs its own SdkPublishPrompts, PublishingApiService, and GenerateAction) — either is larger and riskier than this parity fix. Verified manually instead.

Automated: pnpm build and pnpm lint clean.

Manual (against a real publishing profile):

  1. Interactive, Package + Source Code → polls to [Published] | [Published], exit 0.
  2. Interactive, package-only → one target in the status line.
  3. Interactive, a publish that fails → exit 1, log URL printed.
  4. Interactive, Ctrl+C mid-poll → immediate exit, "still running" notice, log URL.
  5. Non-interactive regression → output unchanged.

🤖 Generated with Claude Code

mrafnadeem-apimatic and others added 12 commits August 10, 2026 12:59
Interactive `sdk publish` returned as soon as the publishing API accepted the
request, printing a link and exiting 0 regardless of the outcome, while
non-interactive polled to a terminal state and exited non-zero on failure.

Move the polling, the running notice, and the closing log-URL note into the
shared `SdkPublishAction`, so both modes report the same outcome and exit code.
`pollPublishingStatus`, `publishingRunningNotice`, and `postPublishingMessage`
move to the shared `SdkPublishPrompts`; the interactive-only
`sdkPublishingInProgress` note is dropped since publishing is no longer in
flight when the command exits.

The poller now reports `succeeded | failed | cancelled` instead of a boolean.
The spinner prints its own cancel line and removes its signal listeners on
Ctrl+C, so the previous loop kept polling invisibly until a second Ctrl+C
killed the process; it now aborts the wait, tells the user publishing continues
on APIMatic, prints the log URL, and exits 130.

Refs #312, #313

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cancelling the publishing wait printed no "still running" notice and no log
URL, and exited 0 for an abandoned publish.

`spin.start()` calls `block()` from `@clack/core`, which puts stdin in raw
mode and registers a keypress listener that calls `process.exit(0)` on Ctrl+C.
Raw mode also stops the terminal raising SIGINT, so the spinner's `onCancel`
never fired: the process died mid-await and the spinner's `exit` listener
printed its cancel line with the green submit symbol on the way out.

clack exposes no way to opt out of `block()` — `spinner()` takes `output` and
`signal` but never `input`, and `updateSettings` can only add key aliases —
so `startCancellableSpinner` takes the key back. It removes the listener
`block()` added, found by diffing stdin's keypress listeners against a
snapshot taken before `start()`, and installs one that flags cancellation and
aborts the pending poll timer.

`pollPublishingStatus` now stops its own spinner on cancel, guarded by
`isCancelled` so the SIGTERM path clack does handle does not print twice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@saeedjamshaid saeedjamshaid left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review of the polling change. 15 findings: 6 correctness issues in the new pollPublishingStatus loop and the action wiring, 1 architecture note, and 8 quality/hygiene items. Ordered roughly most-severe first in the inline comments below.

Comment thread src/prompts/sdk/publish.ts
Comment thread src/prompts/sdk/publish.ts
Comment thread src/prompts/sdk/publish.ts
Comment thread src/prompts/sdk/publish.ts
Comment thread src/actions/sdk/publish.ts
Comment thread src/actions/sdk/publish.ts
Comment thread src/actions/sdk/publish.ts
Comment thread src/prompts/sdk/publish.ts
Comment thread src/prompts/sdk/publish.ts
Comment thread src/prompts/sdk/publish.ts
@mrafnadeem-apimatic
mrafnadeem-apimatic merged commit 68b5660 into dev Aug 11, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants