Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/schedule-subscription-migrations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/app': minor
---

Add commands for scheduling and managing app subscription migrations with polling progress.
429 changes: 429 additions & 0 deletions docs-shopify.dev/generated/generated_docs_data_v2.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@shopify/theme-check-node": "3.27.0",
"@shopify/toml-patch": "0.3.0",
"chokidar": "3.6.0",
"csv-parse": "7.0.2",
"diff": "5.2.2",
"esbuild": "0.28.1",
"graphql-request": "6.1.0",
Expand Down
71 changes: 71 additions & 0 deletions packages/app/src/cli/api/graphql/subscription_migrations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {gql} from 'graphql-request'

// 250 matches the maximum migration submission batch size; operation results are currently bounded by that contract.
// eslint-disable-next-line @shopify/cli/no-inline-graphql
export const AppSubscriptionMigrationOperationCreateMutation = gql`
mutation AppSubscriptionMigrationOperationCreate($input: AppSubscriptionMigrationOperationCreateInput!) {
appSubscriptionMigrationOperationCreate(input: $input) {
operation {
id
status
total
results(first: 250) {
edges {
node {
shopId
code
}
}
}
}
userErrors {
message
field
}
}
}
`

// eslint-disable-next-line @shopify/cli/no-inline-graphql
export const AppSubscriptionMigrationOperationQuery = gql`
query AppSubscriptionMigrationOperation($apiKey: String!, $id: ID!) {
appSubscriptionMigrationOperation(apiKey: $apiKey, id: $id) {
id
status
total
results(first: 250) {
edges {
node {
shopId
code
}
}
}
}
}
`

// eslint-disable-next-line @shopify/cli/no-inline-graphql
export const AppSubscriptionMigrationOperationCancelMutation = gql`
mutation AppSubscriptionMigrationOperationCancel($input: AppSubscriptionMigrationOperationCancelInput!) {
appSubscriptionMigrationOperationCancel(input: $input) {
operation {
id
status
total
results(first: 250) {
edges {
node {
shopId
code
}
}
}
}
userErrors {
message
field
}
}
}
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {operationFlags} from './flags.js'
import {presentMigrationCancellationResult} from './result-presenter.js'
import {linkedAppContext} from '../../../services/app-context.js'
import {cancelMigrationOperations} from '../../../services/subscription-migrations/cancel-operations.js'
import AppLinkedCommand, {AppLinkedCommandOutput} from '../../../utilities/app-linked-command.js'

export default class Cancel extends AppLinkedCommand {
static summary = 'Cancels app subscription migration operations.'

static descriptionWithMarkdown = `Cancels app subscription migration operations.

Canceling stops additional unprocessed shops, but does not undo shops that have already been scheduled or migrated. Use \`unschedule\` for reversible schedules.

Repeat \`--id\` to cancel every operation GID returned by a multi-batch submission. Use \`--json\` to output the resulting operation states and per-shop results as structured JSON.

Run the command from an app project. By default, it uses the Client ID from the active app configuration. Use \`--path\` to select an app directory or \`--config\` to select a configuration. Pass \`--client-id\` to select a different app within the project. Use \`--reset\` to relink the app.`

static description = this.descriptionWithoutMarkdown()

static examples = [
'<%= config.bin %> <%= command.id %> --id <operation-id>',
'<%= config.bin %> <%= command.id %> --path ../my-app --config staging --id <operation-id-1> --id <operation-id-2>',
'<%= config.bin %> <%= command.id %> --client-id <client-id> --id <operation-id> --json',
]

static flags = {...operationFlags}

async run(): Promise<AppLinkedCommandOutput> {
const {flags} = await this.parse(Cancel)
const {app, remoteApp} = await linkedAppContext({
directory: flags.path,
clientId: flags['client-id'],
forceRelink: flags.reset,
userProvidedConfigName: flags.config,
})
const result = await cancelMigrationOperations({
clientId: remoteApp.apiKey,
operationIds: flags.id,
})
const exitCode = presentMigrationCancellationResult(result, {json: flags.json})
if (exitCode !== 0) process.exitCode = exitCode
return {app}
}
}
Loading
Loading