Skip to content

Commit aacac17

Browse files
ryancbahanclaude
andcommitted
Add structured {valid, issues} JSON contract for app config validate
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3249375 commit aacac17

2 files changed

Lines changed: 27 additions & 8 deletions

File tree

packages/app/src/cli/services/validate.test.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe('validateApp', () => {
3939
test('outputs json success when --json is enabled and there are no errors', async () => {
4040
const app = testAppLinked()
4141
await validateApp(app, {json: true})
42-
expect(outputResult).toHaveBeenCalledWith(JSON.stringify({valid: true, errors: []}, null, 2))
42+
expect(outputResult).toHaveBeenCalledWith(JSON.stringify({valid: true, issues: []}, null, 2))
4343
expect(renderSuccess).not.toHaveBeenCalled()
4444
expect(renderError).not.toHaveBeenCalled()
4545
})
@@ -60,7 +60,7 @@ describe('validateApp', () => {
6060
expect(outputResult).not.toHaveBeenCalled()
6161
})
6262

63-
test('outputs json errors and throws when --json is enabled and there are validation errors', async () => {
63+
test('outputs structured json issues when --json is enabled and there are validation errors', async () => {
6464
const errors = new AppErrors()
6565
errors.addError({file: '/path/to/shopify.app.toml', message: 'client_id is required'})
6666
errors.addError({file: '/path/to/extensions/my-ext/shopify.extension.toml', message: 'invalid type "unknown"'})
@@ -69,7 +69,17 @@ describe('validateApp', () => {
6969

7070
await expect(validateApp(app, {json: true})).rejects.toThrow(AbortSilentError)
7171
expect(outputResult).toHaveBeenCalledWith(
72-
JSON.stringify({valid: false, errors: ['client_id is required', 'invalid type "unknown"']}, null, 2),
72+
JSON.stringify(
73+
{
74+
valid: false,
75+
issues: [
76+
{file: '/path/to/shopify.app.toml', message: 'client_id is required'},
77+
{file: '/path/to/extensions/my-ext/shopify.extension.toml', message: 'invalid type "unknown"'},
78+
],
79+
},
80+
null,
81+
2,
82+
),
7383
)
7484
expect(renderError).not.toHaveBeenCalled()
7585
expect(renderSuccess).not.toHaveBeenCalled()
@@ -84,13 +94,22 @@ describe('validateApp', () => {
8494
expect(outputResult).not.toHaveBeenCalled()
8595
})
8696

87-
test('formats structured errors with paths for JSON output', async () => {
97+
test('includes path and code in structured json issues', async () => {
8898
const errors = new AppErrors()
8999
errors.addError({file: '/path/to/shopify.app.toml', path: ['name'], message: 'Required', code: 'invalid_type'})
90100
const app = testAppLinked()
91101
app.errors = errors
92102

93103
await expect(validateApp(app, {json: true})).rejects.toThrow(AbortSilentError)
94-
expect(outputResult).toHaveBeenCalledWith(JSON.stringify({valid: false, errors: ['[name]: Required']}, null, 2))
104+
expect(outputResult).toHaveBeenCalledWith(
105+
JSON.stringify(
106+
{
107+
valid: false,
108+
issues: [{file: '/path/to/shopify.app.toml', message: 'Required', path: ['name'], code: 'invalid_type'}],
109+
},
110+
null,
111+
2,
112+
),
113+
)
95114
})
96115
})

packages/app/src/cli/services/validate.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export async function validateApp(app: AppLinkedInterface, options: ValidateAppO
1313

1414
if (!appErrors || appErrors.isEmpty()) {
1515
if (options.json) {
16-
outputResult(JSON.stringify({valid: true, errors: []}, null, 2))
16+
outputResult(JSON.stringify({valid: true, issues: []}, null, 2))
1717
return
1818
}
1919

@@ -24,8 +24,8 @@ export async function validateApp(app: AppLinkedInterface, options: ValidateAppO
2424
const errors = appErrors.getErrors()
2525

2626
if (options.json) {
27-
const errorMessages = errors.map(formatConfigurationError)
28-
outputResult(JSON.stringify({valid: false, errors: errorMessages}, null, 2))
27+
const issues = errors.map(({file, message, path, code}) => ({file, message, path, code}))
28+
outputResult(JSON.stringify({valid: false, issues}, null, 2))
2929
throw new AbortSilentError()
3030
}
3131

0 commit comments

Comments
 (0)