Skip to content

Gracefully handle system errors while updating a draft extension #5902

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 4, 2025
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ describe('draftMessages', async () => {
const result = extensionInstance.draftMessages.errorMessage

// Then
expect(result).toEqual('Error while deploying updated extension draft')
expect(result).toEqual('Error updating extension draft for test-ui-extension')
})

test('returns no error message when the extension is draftable but configuration', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi
get draftMessages() {
if (this.isAppConfigExtension) return {successMessage: undefined, errorMessage: undefined}
const successMessage = `Draft updated successfully for extension: ${this.localIdentifier}`
const errorMessage = `Error while deploying updated extension draft`
const errorMessage = `Error updating extension draft for ${this.localIdentifier}`
return {successMessage, errorMessage}
}

Expand Down
174 changes: 173 additions & 1 deletion packages/app/src/cli/services/dev/update-extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,179 @@
bundlePath: tmpDir,
})

expect(stderr.write).toHaveBeenCalledWith('Error while updating drafts: Error1, Error2')
expect(stderr.write).toHaveBeenCalledWith('Error updating extension draft for test-ui-extension: Error1, Error2')
})
})

test('handles system error with errors array', async () => {
const systemError = {
errors: [{message: 'Network error'}, {message: 'Timeout error'}],
}
const developerPlatformClient: DeveloperPlatformClient = testDeveloperPlatformClient({
updateExtension: (_extensionInput: ExtensionUpdateDraftMutationVariables) => Promise.reject(systemError),

Check warning on line 290 in packages/app/src/cli/services/dev/update-extension.test.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/dev/update-extension.test.ts#L290

[@typescript-eslint/prefer-promise-reject-errors] Expected the Promise rejection reason to be an Error.
})

await inTemporaryDirectory(async (tmpDir) => {
const mockExtension = await testUIExtension({
devUUID: '1',
directory: tmpDir,
type: 'web_pixel_extension',
uid: 'uid1',
})

await mkdir(joinPath(tmpDir, 'uid1', 'dist'))
const outputPath = mockExtension.getOutputPathForDirectory(tmpDir)
await writeFile(outputPath, 'test content')

await updateExtensionDraft({
extension: mockExtension,
developerPlatformClient,
apiKey,
registrationId,
stdout,
stderr,
appConfiguration: placeholderAppConfiguration,
bundlePath: tmpDir,
})

expect(stderr.write).toHaveBeenCalledWith(
'Error updating extension draft for test-ui-extension: Network error, Timeout error',
)
})
})

test('handles system error with message string', async () => {
const systemError = {message: 'API connection failed'}
const developerPlatformClient: DeveloperPlatformClient = testDeveloperPlatformClient({
updateExtension: (_extensionInput: ExtensionUpdateDraftMutationVariables) => Promise.reject(systemError),

Check warning on line 325 in packages/app/src/cli/services/dev/update-extension.test.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/dev/update-extension.test.ts#L325

[@typescript-eslint/prefer-promise-reject-errors] Expected the Promise rejection reason to be an Error.
})

await inTemporaryDirectory(async (tmpDir) => {
const mockExtension = await testUIExtension({
devUUID: '1',
directory: tmpDir,
type: 'web_pixel_extension',
uid: 'uid1',
})

await mkdir(joinPath(tmpDir, 'uid1', 'dist'))
const outputPath = mockExtension.getOutputPathForDirectory(tmpDir)
await writeFile(outputPath, 'test content')

await updateExtensionDraft({
extension: mockExtension,
developerPlatformClient,
apiKey,
registrationId,
stdout,
stderr,
appConfiguration: placeholderAppConfiguration,
bundlePath: tmpDir,
})

expect(stderr.write).toHaveBeenCalledWith(
'Error updating extension draft for test-ui-extension: API connection failed',
)
})
})

test('handles string error', async () => {
const systemError = 'Connection timeout'
const developerPlatformClient: DeveloperPlatformClient = testDeveloperPlatformClient({
updateExtension: (_extensionInput: ExtensionUpdateDraftMutationVariables) => Promise.reject(systemError),

Check warning on line 360 in packages/app/src/cli/services/dev/update-extension.test.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/dev/update-extension.test.ts#L360

[@typescript-eslint/prefer-promise-reject-errors] Expected the Promise rejection reason to be an Error.
})

await inTemporaryDirectory(async (tmpDir) => {
const mockExtension = await testUIExtension({
devUUID: '1',
directory: tmpDir,
type: 'web_pixel_extension',
uid: 'uid1',
})

await mkdir(joinPath(tmpDir, 'uid1', 'dist'))
const outputPath = mockExtension.getOutputPathForDirectory(tmpDir)
await writeFile(outputPath, 'test content')

await updateExtensionDraft({
extension: mockExtension,
developerPlatformClient,
apiKey,
registrationId,
stdout,
stderr,
appConfiguration: placeholderAppConfiguration,
bundlePath: tmpDir,
})

expect(stderr.write).toHaveBeenCalledWith(
'Error updating extension draft for test-ui-extension: Connection timeout',
)
})
})

test('handles null/undefined error with fallback message', async () => {
const developerPlatformClient: DeveloperPlatformClient = testDeveloperPlatformClient({
updateExtension: (_extensionInput: ExtensionUpdateDraftMutationVariables) => Promise.reject(null),

Check warning on line 394 in packages/app/src/cli/services/dev/update-extension.test.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/dev/update-extension.test.ts#L394

[@typescript-eslint/prefer-promise-reject-errors] Expected the Promise rejection reason to be an Error.
})

await inTemporaryDirectory(async (tmpDir) => {
const mockExtension = await testUIExtension({
devUUID: '1',
directory: tmpDir,
type: 'web_pixel_extension',
uid: 'uid1',
})

await mkdir(joinPath(tmpDir, 'uid1', 'dist'))
const outputPath = mockExtension.getOutputPathForDirectory(tmpDir)
await writeFile(outputPath, 'test content')

await updateExtensionDraft({
extension: mockExtension,
developerPlatformClient,
apiKey,
registrationId,
stdout,
stderr,
appConfiguration: placeholderAppConfiguration,
bundlePath: tmpDir,
})

expect(stderr.write).toHaveBeenCalledWith('Error updating extension draft for test-ui-extension: Unknown error')
})
})

test('handles object error without errors or message properties', async () => {
const systemError = {status: 500, code: 'INTERNAL_ERROR'}
const developerPlatformClient: DeveloperPlatformClient = testDeveloperPlatformClient({
updateExtension: (_extensionInput: ExtensionUpdateDraftMutationVariables) => Promise.reject(systemError),

Check warning on line 427 in packages/app/src/cli/services/dev/update-extension.test.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/dev/update-extension.test.ts#L427

[@typescript-eslint/prefer-promise-reject-errors] Expected the Promise rejection reason to be an Error.
})

await inTemporaryDirectory(async (tmpDir) => {
const mockExtension = await testUIExtension({
devUUID: '1',
directory: tmpDir,
type: 'web_pixel_extension',
uid: 'uid1',
})

await mkdir(joinPath(tmpDir, 'uid1', 'dist'))
const outputPath = mockExtension.getOutputPathForDirectory(tmpDir)
await writeFile(outputPath, 'test content')

await updateExtensionDraft({
extension: mockExtension,
developerPlatformClient,
apiKey,
registrationId,
stdout,
stderr,
appConfiguration: placeholderAppConfiguration,
bundlePath: tmpDir,
})

expect(stderr.write).toHaveBeenCalledWith('Error updating extension draft for test-ui-extension: Unknown error')
})
})
})
Expand Down
35 changes: 30 additions & 5 deletions packages/app/src/cli/services/dev/update-extension.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import {ExtensionUpdateDraftMutationVariables} from '../../api/graphql/partners/generated/update-draft.js'
import {
ExtensionUpdateDraftMutation,
ExtensionUpdateDraftMutationVariables,
} from '../../api/graphql/partners/generated/update-draft.js'
import {AppConfigurationWithoutPath} from '../../models/app/app.js'
import {
loadConfigurationFileContent,
Expand Down Expand Up @@ -71,10 +74,32 @@ export async function updateExtensionDraft({
registrationId,
}

const mutationResult = await developerPlatformClient.updateExtension(extensionInput)
if (mutationResult.extensionUpdateDraft?.userErrors && mutationResult.extensionUpdateDraft?.userErrors?.length > 0) {
const errors = mutationResult.extensionUpdateDraft.userErrors.map((error) => error.message).join(', ')
stderr.write(`Error while updating drafts: ${errors}`)
let mutationResult: ExtensionUpdateDraftMutation | undefined
let errors: {message: string}[] = []
try {
mutationResult = await developerPlatformClient.updateExtension(extensionInput)
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error: unknown) {
errors = [{message: 'Unknown error'}]

if (error && typeof error === 'object') {
const errorObj = error as {errors?: {message: string}[]; message?: string}
if (errorObj.errors?.length) {
errors = errorObj.errors
} else if (errorObj.message) {
errors = [{message: errorObj.message}]
}
} else if (typeof error === 'string') {
errors = [{message: error}]
}
}
const userErrors = mutationResult?.extensionUpdateDraft?.userErrors
if (userErrors?.length) {
errors.push(...userErrors)
}
if (errors.length > 0) {
const errorMessages = errors.map((error: {message: string}) => error.message).join(', ')
stderr.write(`${extension.draftMessages.errorMessage}: ${errorMessages}`)
} else {
const draftUpdateSuccesMessage = extension.draftMessages.successMessage
if (draftUpdateSuccesMessage) outputInfo(draftUpdateSuccesMessage, stdout)
Expand Down
Loading