diff --git a/cli/src/channel/set.ts b/cli/src/channel/set.ts index 2991122b59..3103c30881 100644 --- a/cli/src/channel/set.ts +++ b/cli/src/channel/set.ts @@ -227,9 +227,11 @@ export async function setChannelInternal(channel: string, appId: string, options throw new Error(`Cannot find channel ${channel}`) } + // Disable unlinks only when a rollout bundle is linked; match API promote gating. + const disableUnlinksRollout = rolloutDisable === true && existingChannel.rollout_version != null if (hasSettingsUpdate) await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, 'channel.update_settings', silent, true, existingChannel.id) - if (hasBundlePromotion) + if (hasBundlePromotion || disableUnlinksRollout) await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, 'channel.promote_bundle', silent, true, existingChannel.id) const orgId = existingChannel.owner_org @@ -424,8 +426,13 @@ export async function setChannelInternal(channel: string, appId: string, options if (rolloutEnable != null) channelPayload.rollout_enabled = !!rolloutEnable - if (rolloutDisable) + if (rolloutDisable) { + bundleLinkChanged = bundleLinkChanged || existingChannel.rollout_version != null channelPayload.rollout_enabled = false + channelPayload.rollout_version = null + channelPayload.rollout_paused_at = null + channelPayload.rollout_pause_reason = null + } if (rolloutPause) { channelPayload.rollout_paused_at = new Date().toISOString() diff --git a/cli/src/index.ts b/cli/src/index.ts index 2733585309..eb2b5f2ee2 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -583,7 +583,7 @@ Example: npx @capgo/cli@latest channel set production com.example.app --bundle 1 .option('--rollout-percentage ', `Rollout percentage from 0 to 100`, value => Number.parseFloat(value)) .option('--rollout-percentage-bps ', `Rollout percentage in basis points from 0 to 10000`, value => Number.parseInt(value, 10)) .option('--rollout-enable', `Enable the configured rollout`) - .option('--rollout-disable', `Disable the configured rollout`) + .option('--rollout-disable', `Disable the configured rollout and unlink the rollout bundle`) .option('--rollout-pause', `Pause rollout exposure without rolling back selected devices`) .option('--rollout-resume', `Resume a paused rollout`) .option('--rollout-rollback', `Clear rollout state and return devices to stable`) diff --git a/src/pages/app/[app].channel.[channel].vue b/src/pages/app/[app].channel.[channel].vue index 14cb44ba01..de4b069c6c 100644 --- a/src/pages/app/[app].channel.[channel].vue +++ b/src/pages/app/[app].channel.[channel].vue @@ -234,6 +234,7 @@ async function getChannel(force = false) { async function saveChannelChanges(update: ChannelUpdate) { const changesStableVersion = Object.prototype.hasOwnProperty.call(update, 'version') const changesRolloutVersion = Object.prototype.hasOwnProperty.call(update, 'rollout_version') + // Unlinking rollout_version (including disable) requires promote_bundle — matches refresh_channel_rollout_id. const canUpdate = changesStableVersion || changesRolloutVersion ? canPromoteBundle.value : canUpdateChannelSettings.value @@ -589,6 +590,17 @@ async function enableRollout() { await saveChannelChange('rollout_enabled', true as any) } +async function disableRollout() { + if (await saveChannelChanges({ + rollout_enabled: false, + rollout_version: null, + rollout_paused_at: null, + rollout_pause_reason: null, + })) { + await askUpdateNotificationAfterBundleChange() + } +} + async function saveRolloutPercentage(value: string) { const percentage = Number.parseFloat(value) if (Number.isNaN(percentage) || percentage < 0 || percentage > 100) { @@ -1051,7 +1063,7 @@ async function copyCurlCommand() { -