Skip to content
Draft
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
55 changes: 55 additions & 0 deletions src/hooks/command_error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2024 Adobe Inc. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

const fs = require('fs')
const path = require('path')
const chalk = require('chalk')

/**
* Hook that runs when a command error occurs.
* Provides helpful error messages when authentication/project errors occur.
*
* @param {object} options the hook options
* @param {string} options.message the error message
*/
async function hook (options) {
const { message } = options

// Check if the error is authentication-related
const authErrors = [
'An AUTH key must be specified',
'AUTH key must be specified',
'authentication',
'not authenticated'
]

const isAuthError = authErrors.some(error =>

Check failure on line 35 in src/hooks/command_error.js

View workflow job for this annotation

GitHub Actions / build (24.x, ubuntu-latest)

Trailing spaces not allowed

Check failure on line 35 in src/hooks/command_error.js

View workflow job for this annotation

GitHub Actions / build (20.x, ubuntu-latest)

Trailing spaces not allowed
message && message.toLowerCase().includes(error.toLowerCase())
)

if (isAuthError) {
// Check if we're in an App Builder project directory
const appConfigPath = path.join(process.cwd(), 'app.config.yaml')
const hasAppConfig = fs.existsSync(appConfigPath)

if (!hasAppConfig) {
// Provide a helpful error message
console.error(chalk.red('\n❌ You are not in an App Builder project directory.\n'))
console.error(chalk.yellow('To fix this:'))
console.error(chalk.yellow(' 1. Navigate to an existing App Builder project directory, or'))
console.error(chalk.yellow(' 2. Create a new App Builder project with: ') + chalk.cyan('aio app init'))
console.error(chalk.yellow('\nFor more information, visit: https://developer.adobe.com/app-builder/docs/\n'))
}
}
}

module.exports = hook
114 changes: 114 additions & 0 deletions test/hooks/command_error.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright 2024 Adobe Inc. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

const hook = require('../../src/hooks/command_error')
const fs = require('fs')
const path = require('path')

jest.mock('fs')

describe('command_error hook', () => {
let consoleErrorSpy

beforeEach(() => {
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
jest.clearAllMocks()
})

afterEach(() => {
consoleErrorSpy.mockRestore()
})

describe('authentication errors', () => {
test('shows helpful message when auth error occurs outside App Builder project', async () => {
fs.existsSync.mockReturnValue(false)

await hook({ message: 'An AUTH key must be specified' })

expect(fs.existsSync).toHaveBeenCalledWith(path.join(process.cwd(), 'app.config.yaml'))
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('You are not in an App Builder project directory'))
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('To fix this:'))
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('aio app init'))
})

test('shows helpful message for "AUTH key must be specified" error', async () => {
fs.existsSync.mockReturnValue(false)

await hook({ message: 'AUTH key must be specified' })

expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('You are not in an App Builder project directory'))
})

test('shows helpful message for "authentication" error', async () => {
fs.existsSync.mockReturnValue(false)

await hook({ message: 'authentication failed' })

expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('You are not in an App Builder project directory'))
})

test('shows helpful message for "not authenticated" error', async () => {
fs.existsSync.mockReturnValue(false)

await hook({ message: 'not authenticated' })

expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('You are not in an App Builder project directory'))
})

test('does not show message when auth error occurs inside App Builder project', async () => {
fs.existsSync.mockReturnValue(true)

await hook({ message: 'An AUTH key must be specified' })

expect(fs.existsSync).toHaveBeenCalledWith(path.join(process.cwd(), 'app.config.yaml'))
expect(consoleErrorSpy).not.toHaveBeenCalled()
})

test('handles case-insensitive auth error matching', async () => {
fs.existsSync.mockReturnValue(false)

await hook({ message: 'an auth KEY must be SPECIFIED' })

expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('You are not in an App Builder project directory'))
})
})

describe('non-authentication errors', () => {
test('does not show message for non-auth errors', async () => {
await hook({ message: 'Some other error occurred' })

expect(fs.existsSync).not.toHaveBeenCalled()
expect(consoleErrorSpy).not.toHaveBeenCalled()
})

test('handles undefined message', async () => {
await hook({ message: undefined })

expect(fs.existsSync).not.toHaveBeenCalled()
expect(consoleErrorSpy).not.toHaveBeenCalled()
})

test('handles null message', async () => {
await hook({ message: null })

expect(fs.existsSync).not.toHaveBeenCalled()
expect(consoleErrorSpy).not.toHaveBeenCalled()
})

test('handles empty message', async () => {
await hook({ message: '' })

expect(fs.existsSync).not.toHaveBeenCalled()
expect(consoleErrorSpy).not.toHaveBeenCalled()
})
})
})
Loading