refactor: use @heroku/sdk for addons commands#3718
Open
eablack wants to merge 10 commits into
Open
Conversation
- addons (list) now calls SDK addOn.list / addOn.listByApp and addOnAttachment.list / addOnAttachment.listByApp. The Accept-Expansion: addon_service,plan header that drives nested service/plan inlining is now passed once via createPlatformClient options. - addons:info now uses addOnAttachment.listByAddOn for the attachment fetch. resolveAddon keeps its existing /actions/addons/resolve flow and cache.
Replace direct Platform API calls in addons (list), addons:info, addons:create, addons:services, addons:plans, and addons:upgrade with @heroku/sdk equivalents: - addOn.list / addOn.listByApp + addOnAttachment.list / listByApp - describeAddon (resolves, fetches attachments, applies grandfathered pricing in one call) - addOn.create - addOnService.list - plan.listByAddOn - upgrade composition (resolves + updates in one call, with the onResolved callback firing between for the action display line) Drops legacy lib/addons/resolve.ts dependency from upgrade in favor of the SDK's typed AddonAmbiguousError. Tests updated for the SDK's body/header/error shapes (UUIDs in PATCH paths, no `app: null` on resolve bodies, etc.). Adds tmp/ to the eslint ignore list (build artifacts produced 138k unrelated lint errors).
The SDK was rearchitected to replace the compositions/ helpers with a HerokuSDK + extension factory pattern. Update each command to construct a HerokuSDK with the extensions it needs and call methods through the resulting platform proxy. Also bumps the SDK pin to the working branch so addOnExtensions.upgrade exposes onResolved (previously dropped from the extension's options type).
- rename: addOn.info + addOn.update (passes plan through unchanged to satisfy the schema's required field) - detach: addOnAttachment.infoByApp + addOnAttachment.delete + release.list (Range header via withHeaders) - plans: addOn.listPlans extension (replaces lodash sort with native contract+cents sort) Test fixtures updated to return JSON bodies for the PATCH/DELETE responses the SDK now parses.
- waitForAddonProvisioning: use createPlatformClient + addOn.infoByApp
with Accept-Expansion header via withHeaders. Drops APIClient param.
- createAddon helper: drops APIClient param (no longer needed once
waitForAddonProvisioning is migrated).
- create.ts, wait.ts, data:pg:{create,fork,migrate}: update call sites
to match the new signature (no this.heroku passed in).
- addons:wait test: replace lolex install + setTimeout override with
sinon.useFakeTimers({toFake: ['Date'], shouldAdvanceTime: true})
so the SDK's real setTimeout polling drives the test while Date.now
can still be fake-ticked for the >5s notifier threshold.
- pg create/fork test fixtures: nock body matchers changed from
{plan: {name: 'foo'}} to {plan: 'foo'} to match the canonical
AddOnCreateOpts shape the SDK sends.
- migrate test: shift createAddonStub argument indexes to reflect the
new signature (heroku param removed).
Replaces the local trapConfirmationRequired + waitForAddonProvisioning
flow with a single platform.addOn.createAndWait call. The SDK now owns:
- 423 confirmation_required → typed AddonConfirmationRequiredError,
caught here and passed through ConfirmCommand for the UX prompt.
- state=provisioning + wait=true → poll loop until terminal.
- state=deprovisioned → typed AddonProvisioningFailedError.
The two-phase status display (Creating <plan>... <price>, then
Creating <addonName>... done while polling) is preserved by hooking
the SDK's onProvisioning callback to close the create-phase action,
print the provision message + 'Waiting for...' line, and start the
wait-phase action.
Bumps SDK pin to eb/feat/addon-create-and-wait.
Per @heroku/sdk#26 (chore!: cleans-up exports), HerokuSDK is now exported from the package root and the './sdk' subpath is gone. Bump the lockfile to pick up the SDK 0.4.0 build that includes both this exports change and the createAndWait + onProvisioning callback work that lib/addons/create-addon.ts depends on.
The global /addons endpoint rejects Accept-Expansion: addon_service,plan
('must be within ``'), but the per-app /apps/:id/addons endpoint
accepts it. Move the expansion off createPlatformClient's defaults and
onto a withHeaders-scoped client used only for the app-scoped list.
The attachment endpoints (list / listByApp) use Accept-Inclusion, not
Accept-Expansion, so they don't need the header either.
Bumps the SDK pin to pick up describeAddon's matching expansion-scoping
fix.
The integration branch's #3717 imports from @heroku/sdk/compositions/pipeline, but the SDK's exports cleanup (heroku-sdk#26) replaced compositions/ with resources/. Update each call site to use the new shape: - listPipelineApps → pipelineCouplingExtensions.listApps via HerokuSDK - promotePipeline → standalone import (kept as a static reference on the Promote command class so existing sinon stubs continue to work) - AppWithPipelineCoupling, ReleaseStreamContext → type imports from resources/platform/pipeline-{coupling,promotion} The promote test's stub callback now sees a 3-arg signature (ctx, body, options) instead of 2-arg, and firstCall.args[0] becomes [1] for body assertions. Test scope cleanup for addons/index and addons/info: split the 'apiSdk' nock scope (Accept-Expansion required) from the 'api' scope (no expansion). Global /addons and /addons/<id>/addon-attachments don't accept the expansion header, matching the SDK's per-call header scoping in heroku-sdk#27.
bb65ea4 to
937d495
Compare
heroku-sdk PR #27 has merged; flip the pin from the feature branch back to main. Resolves to commit efce1d4. Also pulls in the eslint --fix import-sort cleanup across the seven files we touched (each was importing '@heroku/sdk/extensions/platform' or '@heroku/sdk/resources/platform/...' before '@heroku/sdk', which the perfectionist/sort-imports rule flagged as an error).
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Migrates
addons:,maintenance:, and (as a consequence of rebasing ontofeat/heroku-sdk-integration)pipelines:commands to use@heroku/sdk's newHerokuSDK + extensionsarchitecture from sdk PR #25 / #26 / #27.Migrated commands:
addons(list) —addOn.list/listByApp+addOnAttachment.list/listByApp. Accept-Expansion is scoped viawithHeadersto the app-scoped list only (the global/addonsendpoint rejects it).addons:info—addOn.describe. The SDK scopes the Accept-Expansion header to the resolve call only (the/addons/:id/addon-attachmentsendpoint uses Accept-Inclusion, not Expansion).addons:create—addOn.createAndWait. Confirmation-required handling, polling-on-wait, and provisioning-failed errors now live in the SDK; the CLI hooksonProvisioningto preserve the two-phase status display ("Creating ... " → "Waiting for " → "Creating ... done"). The SDK also bakes inAccept-Expansion: addon_service,planandX-Heroku-Legacy-Provider-Messages: trueso the response includes the price for the action stop line and the provider'sprovision_message.addons:upgrade—addOn.upgrade(onResolvedcallback drives the action line; bare plan name auto-qualifies via the SDK).addons:services—addOnService.list.addons:plans—addOn.listPlans. Native contract+cents sort replaces the lodash sort.addons:rename—addOn.info+addOn.update(forwards the addon's current plan unchanged to satisfy the schema'splanrequired field).addons:detach—addOnAttachment.infoByApp+addOnAttachment.delete+release.list(Range header viawithHeaders).maintenance(status / on / off) —app.info,app.enableMaintenance,app.disableMaintenance.pipelines:info,pipelines:diff,pipelines:transfer,pipelines:promote— migrated from@heroku/sdk/compositions/pipeline(which no longer exists) topipelineCouplingExtensions.listAppsand the standalonepromotePipelinefunction.Promote.promotePipelineis kept as a static reference so existing sinon stubs continue to work.Helper migrations:
lib/addons/create-addon.ts— orchestration moved into the SDK'saddOn.createAndWait. Helper is now a thin UX wrapper.lib/addons/addons-wait.ts—waitForAddonProvisioningmigrated to the SDK polling path;waitForAddonDeprovisioningstill uses legacyAPIClient(used by deferredaddons:destroy).data:pg:create,data:pg:fork,data:pg:migrate,addons:wait— call-site signature follow-through (no morethis.herokufirst arg tocreateAddon).Drops:
lib/addons/resolve.tsdependency fromaddons:upgradein favor of the SDK's typedAddonAmbiguousError.Deferred to follow-up PRs (each has a single sticky API call not modeled in the SDK route registry):
addons:destroy— needsbody: {force}on DELETE.addons:attach— needs the/addons/:name/config/:namespacecredential-config path variant.addons:wait,addons:docs,addons:open— fully migratable, just out of scope for this PR.Test fixture updates:
POST /apps/:app/addonsuseplan: 'foo'instead ofplan: {name: 'foo'}(matchesAddOnCreateOptscanonical shape).addons:renamePATCH body now includesplan(required by the schema; we forward the addon's current plan unchanged).addons:detachmocks return JSON bodies for the DELETE response (the SDK's dispatcher parses, where the legacy client tolerated empty).data:pg:migratecreateAddonStub.args[0][N]indexes shifted down by 1 (no moreherokufirst arg).addons:waittest replaces lolex with sinon'suseFakeTimers({toFake: ['Date'], shouldAdvanceTime: true})so the SDK's realsetTimeoutpolling drives the test whileDate.now()can still be fake-ticked for the >5s notifier threshold.pipelines:promotetest stubsCmd.promotePipeline(a static reference to the standalone SDK function), with the callback signature shifted to 3-arg(_ctx, _body, options)andfirstCall.args[1]for body assertions.addonsandaddons:infotests split the nock scope soapiSdk(Accept-Expansion required) wraps only the resolve POSTs, whileapi(no reqheader constraint) wraps the global/addonsand the/addons/<id>/addon-attachmentscalls — matching the SDK's per-call header scoping.Type of Change
Testing
Run the touched-area unit tests:
Expect 211 passing, 0 failing.
End-to-end smoke (against
heroku-dev-toolsteam or any team you have access to):For the confirmation-required path, repeat
addons:createagainst an app whose owner team requires confirmation; expect the CLI to prompt for the team name, then succeed.Related Issues
GUS work item: W-22265114