@@ -2,6 +2,7 @@ import Validate from './validate.js'
22import { linkedAppContext } from '../../../services/app-context.js'
33import { validateApp } from '../../../services/validate.js'
44import { testAppLinked } from '../../../models/app/app.test-data.js'
5+ import { AppConfigurationAbortError } from '../../../models/app/error-parsing.js'
56import { describe , expect , test , vi } from 'vitest'
67import { AbortError } from '@shopify/cli-kit/node/error'
78import { outputResult } from '@shopify/cli-kit/node/output'
@@ -56,52 +57,48 @@ describe('app config validate command', () => {
5657 expect ( validateApp ) . toHaveBeenCalledWith ( app , { json : true } )
5758 } )
5859
59- test ( 'outputs json issues when app loading aborts before validateApp runs ' , async ( ) => {
60+ test ( 'rethrows AppConfigurationAbortError in non-json mode without emitting json ' , async ( ) => {
6061 // Given
6162 vi . mocked ( linkedAppContext ) . mockRejectedValue (
62- new AbortError ( 'Validation errors in /tmp/shopify.app.toml:\n\n• [name]: String is required ' ) ,
63+ new AppConfigurationAbortError ( 'Validation errors in /tmp/shopify.app.toml' , '/tmp/shopify.app.toml ') ,
6364 )
6465
6566 // When / Then
66- await Validate . run ( [ '--json' , '--path=/tmp/app' ] , import . meta. url ) . catch ( ( ) => { } )
67- expect ( outputResult ) . toHaveBeenCalledWith (
68- JSON . stringify (
69- {
70- valid : false ,
71- issues : [
72- {
73- filePath : '/tmp/shopify.app.toml' ,
74- path : [ ] ,
75- pathString : 'name' ,
76- message : 'String is required' ,
77- } ,
78- ] ,
79- } ,
80- null,
81- 2 ,
82- ) ,
83- )
67+ await expect ( Validate . run ( [ '--path=/tmp/app' ] , import . meta. url ) ) . rejects . toThrow ( )
68+ expect ( outputResult ) . not . toHaveBeenCalled ( )
8469 expect ( validateApp ) . not . toHaveBeenCalled( )
8570 } )
8671
87- test ( 'outputs json issues when app loading aborts with ansi-colored structured text ' , async ( ) => {
72+ test ( 'outputs structured configuration issues from app loading before validateApp runs ' , async ( ) => {
8873 // Given
8974 vi . mocked ( linkedAppContext ) . mockRejectedValue (
90- new AbortError (
91- '\u001b[1m\u001b[91mValidation errors\u001b[39m\u001b[22m in /tmp/shopify.app.toml:\n\n• [name]: String is required' ,
75+ new AppConfigurationAbortError (
76+ 'Validation errors in /tmp/shopify.app.toml:\n\n• [name]: String is required' ,
77+ '/tmp/shopify.app.toml' ,
78+ [
79+ {
80+ filePath : '/tmp/shopify.app.toml' ,
81+ path : [ 'name' ] ,
82+ pathString : 'name' ,
83+ message : 'String is required' ,
84+ } ,
85+ ] ,
9286 ) ,
9387 )
9488
9589 // When / Then
96- await Validate . run ( [ '--json' , '--path=/tmp/app' ] , import . meta. url ) . catch ( ( ) => { } )
90+ await expect ( Validate . run ( [ '--json' , '--path=/tmp/app' ] , import . meta. url ) ) . rejects . toThrow (
91+ 'process.exit unexpectedly called with "1"' ,
92+ )
93+ expect ( outputResult ) . toHaveBeenCalledTimes ( 1 )
9794 expect ( outputResult ) . toHaveBeenCalledWith (
9895 JSON . stringify (
9996 {
10097 valid : false ,
10198 issues : [
10299 {
103100 filePath : '/tmp/shopify.app.toml' ,
104- path : [ ] ,
101+ path : [ 'name' ] ,
105102 pathString : 'name' ,
106103 message : 'String is required' ,
107104 } ,
@@ -114,33 +111,27 @@ describe('app config validate command', () => {
114111 expect ( validateApp ) . not . toHaveBeenCalled( )
115112 } )
116113
117- test ( 'preserves a root json issue when contextual text precedes structured validation errors ' , async ( ) => {
114+ test ( 'outputs a root json issue when app loading fails without structured issues ', async ( ) => {
118115 // Given
119116 vi . mocked ( linkedAppContext ) . mockRejectedValue (
120- new AbortError (
121- 'Could not infer extension handle\n\nValidation errors in /tmp/shopify.app.toml:\n\n• [name]: String is required' ,
122- ) ,
117+ new AppConfigurationAbortError ( "Couldn 't find an app toml file at / tmp / app ", '/ tmp / app ') ,
123118 )
124119
125120 // When / Then
126- await Validate . run ( [ '--json' , '--path=/tmp/app' ] , import . meta. url ) . catch ( ( ) => { } )
121+ await expect ( Validate . run ( [ '-- json ', '-- path = / t m p / app'], import.meta.url)).rejects.toThrow(
122+ ' process . exit unexpectedly called with "1" ',
123+ )
124+ expect ( outputResult ) . toHaveBeenCalledTimes ( 1 )
127125 expect ( outputResult ) . toHaveBeenCalledWith (
128126 JSON . stringify (
129127 {
130128 valid : false ,
131129 issues : [
132130 {
133- filePath : '/tmp/shopify.app.toml' ,
134- path : [ ] ,
135- pathString : 'name' ,
136- message : 'String is required' ,
137- } ,
138- {
139- filePath : '/tmp/shopify.app.toml' ,
131+ filePath : '/tmp/app' ,
140132 path : [ ] ,
141133 pathString : 'root' ,
142- message :
143- 'Could not infer extension handle\n\nValidation errors in /tmp/shopify.app.toml:\n\n• [name]: String is required' ,
134+ message : "Couldn't find an app toml file at /tmp/app" ,
144135 } ,
145136 ] ,
146137 } ,
@@ -151,22 +142,36 @@ describe('app config validate command', () => {
151142 expect ( validateApp ) . not . toHaveBeenCalled ( )
152143 } )
153144
154- test ( 'parses structured validation errors for windows-style paths ' , async ( ) => {
145+ test ( 'outputs json when validateApp throws a structured configuration abort ' , async ( ) => {
155146 // Given
156- vi . mocked ( linkedAppContext ) . mockRejectedValue (
157- new AbortError ( 'Validation errors in C:\\tmp\\shopify.app.toml:\n\n• [name]: String is required' ) ,
147+ const app = testAppLinked ( )
148+ vi . mocked ( linkedAppContext ) . mockResolvedValue ( { app} as Awaited < ReturnType < typeof linkedAppContext > > )
149+ vi . mocked ( validateApp ) . mockRejectedValue (
150+ new AppConfigurationAbortError (
151+ 'Validation errors in /tmp/shopify.app.toml:\n\n• [name]: String is required' ,
152+ '/tmp/shopify.app.toml' ,
153+ [
154+ {
155+ filePath : '/tmp/shopify.app.toml' ,
156+ path : [ 'name' ] ,
157+ pathString : 'name' ,
158+ message : 'String is required' ,
159+ } ,
160+ ] ,
161+ ) ,
158162 )
159163
160164 // When / Then
161- await Validate . run ( [ '--json' , '--path=/tmp/app' ] , import . meta. url ) . catch ( ( ) => { } )
165+ await expect ( Validate . run ( [ '--json' ] , import . meta. url ) ) . rejects . toThrow ( 'process.exit unexpectedly called with "1"' )
166+ expect ( outputResult ) . toHaveBeenCalledTimes ( 1 )
162167 expect ( outputResult ) . toHaveBeenCalledWith (
163168 JSON . stringify (
164169 {
165170 valid : false ,
166171 issues : [
167172 {
168- filePath : 'C:\\ tmp\\ shopify.app.toml' ,
169- path : [ ] ,
173+ filePath : '/ tmp/ shopify.app.toml' ,
174+ path : [ 'name' ] ,
170175 pathString : 'name' ,
171176 message : 'String is required' ,
172177 } ,
@@ -176,33 +181,17 @@ describe('app config validate command', () => {
176181 2 ,
177182 ) ,
178183 )
179- expect ( validateApp ) . not . toHaveBeenCalled( )
180184 } )
181185
182- test ( 'outputs a root json issue when app loading aborts with a non-structured message ' , async ( ) => {
186+ test ( 'rethrows non-configuration errors from validateApp in json mode without converting them to validation json ' , async ( ) => {
183187 // Given
184- vi . mocked ( linkedAppContext ) . mockRejectedValue ( new AbortError ( "Couldn't find an app toml file at /tmp/app" ) )
188+ const app = testAppLinked ( )
189+ vi . mocked ( linkedAppContext ) . mockResolvedValue ( { app} as Awaited < ReturnType < typeof linkedAppContext > > )
190+ vi . mocked ( validateApp ) . mockRejectedValue ( new AbortError ( 'network problem' ) )
185191
186192 // When / Then
187- await Validate . run ( [ '--json' , '--path=/tmp/app' ] , import . meta. url ) . catch ( ( ) => { } )
188- expect ( outputResult ) . toHaveBeenCalledWith (
189- JSON . stringify (
190- {
191- valid : false ,
192- issues : [
193- {
194- filePath : '/tmp/app' ,
195- path : [ ] ,
196- pathString : 'root' ,
197- message : "Couldn't find an app toml file at /tmp/app" ,
198- } ,
199- ] ,
200- } ,
201- null,
202- 2 ,
203- ) ,
204- )
205- expect ( validateApp ) . not . toHaveBeenCalled( )
193+ await expect ( Validate . run ( [ '--json' ] , import . meta. url ) ) . rejects . toThrow ( )
194+ expect ( outputResult ) . not . toHaveBeenCalled ( )
206195 } )
207196
208197 test ( 'rethrows unrelated abort errors in json mode without converting them to validation json' , async ( ) => {
0 commit comments