Skip to content

Commit

Permalink
Improve handling of error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
amcaplan committed Jan 8, 2025
1 parent 288b90c commit 2ae86fe
Showing 1 changed file with 24 additions and 46 deletions.
70 changes: 24 additions & 46 deletions packages/app/src/cli/services/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
OutputMessage,
formatPackageManagerCommand,
outputContent,
outputToken,
stringifyMessage,
} from '@shopify/cli-kit/node/output'

Check failure on line 15 in packages/app/src/cli/services/info.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/info.ts#L10-L15

[prettier/prettier] Replace `⏎··OutputMessage,⏎··formatPackageManagerCommand,⏎··outputContent,⏎··stringifyMessage,⏎` with `OutputMessage,·formatPackageManagerCommand,·outputContent,·stringifyMessage`
import {InlineToken, renderInfo} from '@shopify/cli-kit/node/ui'
Expand Down Expand Up @@ -107,8 +106,9 @@ function withPurgedSchemas(extensions: object[]): object[] {
})
}

const UNKNOWN_TEXT = outputContent`${outputToken.italic('unknown')}`.value
const NOT_CONFIGURED_TEXT = outputContent`${outputToken.italic('Not yet configured')}`.value
const UNKNOWN_TEXT: string = 'unknown'

Check failure on line 109 in packages/app/src/cli/services/info.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/info.ts#L109

[@typescript-eslint/no-inferrable-types] Type string trivially inferred from a string literal, remove type annotation.
const NOT_CONFIGURED_TOKEN: InlineToken = {subdued: 'Not yet configured'}
const NOT_LOADED_TEXT: InlineToken = 'NOT LOADED'

class AppInfo {
private readonly app: AppLinkedInterface
Expand All @@ -131,7 +131,7 @@ class AppInfo {
}

async devConfigsSection(): Promise<CustomSection[]> {
let updateUrls = NOT_CONFIGURED_TEXT
let updateUrls = NOT_CONFIGURED_TOKEN
if (this.app.configuration.build?.automatically_update_urls_on_dev !== undefined) {
updateUrls = this.app.configuration.build.automatically_update_urls_on_dev ? 'Yes' : 'No'
}
Expand All @@ -149,10 +149,10 @@ class AppInfo {
'Current app configuration',
[
['Configuration file', basename(this.app.configuration.path) || configurationFileNames.app],
['App name', this.remoteApp.title || NOT_CONFIGURED_TEXT],
['Client ID', this.remoteApp.apiKey || NOT_CONFIGURED_TEXT],
['App name', this.remoteApp.title || NOT_CONFIGURED_TOKEN],
['Client ID', this.remoteApp.apiKey || NOT_CONFIGURED_TOKEN],
['Access scopes', getAppScopes(this.app.configuration)],
['Dev store', this.app.configuration.build?.dev_store_url || NOT_CONFIGURED_TEXT],
['Dev store', this.app.configuration.build?.dev_store_url ?? NOT_CONFIGURED_TOKEN],
['Update URLs', updateUrls],
partnersAccountInfo,
],
Expand All @@ -173,36 +173,19 @@ class AppInfo {

async appComponentsSection(): Promise<CustomSection[]> {
const webComponentsSection = this.webComponentsSection()

const supportedExtensions = this.app.allExtensions.filter((ext) => ext.isReturnedAsInfo())
const extensionsSections = this.extensionsSections(supportedExtensions)

let errorsSection: CustomSection | undefined
if (this.app.errors?.isEmpty() === false) {
errorsSection = this.tableSection(
'Extensions with errors',
(
supportedExtensions
.map((extension) => this.invalidExtensionSubSection(extension))
.filter((data) => typeof data !== 'undefined') as [string, InlineToken][][]
).flat(),
)
}

return [
{
title: '\nDirectory components'.toUpperCase(),
body: '',
},
...(webComponentsSection ? [webComponentsSection] : []),
...extensionsSections,
...(errorsSection ? [errorsSection] : []),
...this.extensionsSections(),
]
}

webComponentsSection(): CustomSection | undefined {
const errors: OutputMessage[] = []
const sublevels: [string, InlineToken][] = []
const sublevels: InlineToken[][] = []
if (!this.app.webs[0]) return
this.app.webs.forEach((web) => {
if (web.configuration) {
Expand All @@ -218,7 +201,7 @@ class AppInfo {
})
}
} else {
sublevels.push([` 📂 ${UNKNOWN_TEXT}`, {filePath: relativePath(this.app.directory, web.directory)}])
sublevels.push([{subdued: ` 📂 ${UNKNOWN_TEXT}`}, {filePath: relativePath(this.app.directory, web.directory)}])
}
if (this.app.errors) {
const error = this.app.errors.getError(`${web.directory}/${configurationFileNames.web}`)
Expand All @@ -233,7 +216,8 @@ class AppInfo {
])
}

extensionsSections(extensions: ExtensionInstance[]): CustomSection[] {
extensionsSections(): CustomSection[] {
const extensions = this.app.allExtensions.filter((ext) => ext.isReturnedAsInfo())
const types = Array.from(new Set(extensions.map((ext) => ext.type)))
return types
.map((extensionType: string): CustomSection | undefined => {
Expand All @@ -248,33 +232,27 @@ class AppInfo {
.filter((section: CustomSection | undefined) => section !== undefined) as CustomSection[]
}

extensionSubSection(extension: ExtensionInstance): [string, InlineToken][] {
extensionSubSection(extension: ExtensionInstance): InlineToken[][] {
const config = extension.configuration
const details: [string, InlineToken][] = [
[`📂 ${extension.handle}`, {filePath: relativePath(this.app.directory, extension.directory)}],
[`📂 ${extension.handle || NOT_LOADED_TEXT}`, {filePath: relativePath(this.app.directory, extension.directory)}],

Check failure on line 238 in packages/app/src/cli/services/info.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/info.ts#L238

[@typescript-eslint/no-base-to-string] 'extension.handle || NOT_LOADED_TEXT' may use Object's default stringification format ('[object Object]') when stringified.
[' config file', {filePath: relativePath(extension.directory, extension.configurationPath)}],
]
if (config && config.metafields?.length) {
details.push([' metafields', `${config.metafields.length}`])
}
const error = this.app.errors?.getError(extension.configurationPath)
if (error) {
details.push([' error', {error: this.formattedError(error)}])
}

return details
}

invalidExtensionSubSection(extension: ExtensionInstance): [string, InlineToken][] | undefined {
const error = this.app.errors?.getError(extension.configurationPath)
if (!error) return
return [
[`📂 ${extension.handle}`, {filePath: relativePath(this.app.directory, extension.directory)}],
[' config file', {filePath: relativePath(extension.directory, extension.configurationPath)}],
[' message', {error: this.formattedError(error)}],
]
}

formattedError(str: OutputMessage): string {
const [errorFirstLine, ...errorRemainingLines] = stringifyMessage(str).split('\n')
const errorLines = [`! ${errorFirstLine}`, ...errorRemainingLines.map((line) => ` ${line}`)]
return outputContent`${outputToken.errorText(errorLines.join('\n'))}`.value
// Some errors have newlines at the beginning for no apparent reason
const [errorFirstLine, ...errorRemainingLines] = stringifyMessage(str).trim().split('\n')
return [`! ${errorFirstLine}`, ...errorRemainingLines.map((line) => ` ${line}`)].join('\n')
}

async systemInfoSection(): Promise<CustomSection> {
Expand All @@ -283,19 +261,19 @@ class AppInfo {
['Shopify CLI', CLI_KIT_VERSION],
['Package manager', this.app.packageManager],
['OS', `${platform}-${arch}`],
['Shell', process.env.SHELL || 'unknown'],
['Shell', process.env.SHELL ?? 'unknown'],
['Node version', process.version],
])
}

tableSection(title: string, rows: [string, InlineToken][], {isFirstItem = false} = {}): CustomSection {
tableSection(title: string, rows: InlineToken[][], {isFirstItem = false} = {}): CustomSection {
return {
title: `${isFirstItem ? '' : '\n'}${title.toUpperCase()}\n`,
body: {tabularData: rows, firstColumnSubdued: true},
}
}

subtableSection(title: string, rows: [string, InlineToken][]): CustomSection {
subtableSection(title: string, rows: InlineToken[][]): CustomSection {
return {
title,
body: {tabularData: rows, firstColumnSubdued: true},
Expand Down

0 comments on commit 2ae86fe

Please sign in to comment.