shopify-theme-deployer
Version:
A Node.js CLI tool that deploys Shopify theme code to multiple stores simultaneously using the Shopify CLI
120 lines (98 loc) • 3.1 kB
JavaScript
const chalk = require('chalk')
class RetryManager {
constructor(logger, spinner, errorParser) {
this.logger = logger
this.spinner = spinner
this.errorParser = errorParser
}
// Sleep utility function
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
// Retry operation with exponential backoff
async retryWithBackoff(operation, store, options = {}) {
const { retryCount = 3, retryDelay = 1000, verbose = false } = options
let lastError = null
// Attempt operation with retries
for (let attempt = 0; attempt <= retryCount; attempt++) {
try {
const success = await operation(store, {
...options,
singleStore: false, // We're handling retries here
})
if (success) {
if (attempt > 0) {
this.logger.success(
`Operation succeeded for ${store.url} after ${attempt} retry(ies)`
)
await this.logger.log(
`RETRY_SUCCESS - ${store.url} - Attempts: ${attempt + 1}`
)
}
return { success: true, attempts: attempt + 1 }
}
} catch (error) {
lastError = error
}
// If this was the last attempt, don't retry
if (attempt === retryCount) {
break
}
// Parse error to determine if it's retryable
const parsedError = this.errorParser.parse(lastError)
if (!this.errorParser.isRetryable(parsedError)) {
this.logger.warning(
`Non-retryable error for ${store.url}: ${parsedError.type}`
)
await this.logger.log(
`NON_RETRYABLE - ${store.url} - Error: ${parsedError.type}`
)
break
}
// Calculate delay with exponential backoff
const delay = retryDelay * Math.pow(2, attempt)
this.logger.warning(
`Operation failed for ${store.url}, retrying in ${delay}ms (attempt ${
attempt + 1
}/${retryCount + 1})`
)
await this.logger.log(
`RETRY_ATTEMPT - ${store.url} - Attempt: ${
attempt + 1
} - Delay: ${delay}ms`
)
if (verbose) {
console.log(chalk.gray(`Retry reason: ${parsedError.message}`))
}
await this.sleep(delay)
}
// All retries failed
const parsedError = this.errorParser.parse(lastError)
await this.logger.log(
`RETRY_EXHAUSTED - ${store.url} - Final error: ${parsedError.type}`
)
return {
success: false,
attempts: retryCount + 1,
error: parsedError,
}
}
// Limit concurrent operations
async limitConcurrency(tasks, maxConcurrent) {
const results = []
const executing = []
for (const task of tasks) {
const promise = task().then(result => {
executing.splice(executing.indexOf(promise), 1)
return result
})
results.push(promise)
executing.push(promise)
if (executing.length >= maxConcurrent) {
await Promise.race(executing)
}
}
return Promise.allSettled(results)
}
}
module.exports = RetryManager