Skip to content

Commit 2929234

Browse files
authored
Merge pull request #6934 from Shopify/rcb/non-interactive-validate
Add `app validate` command
2 parents cba504e + 4518b8f commit 2929234

8 files changed

Lines changed: 305 additions & 0 deletions

File tree

docs-shopify.dev/generated/generated_docs_data.json

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3084,6 +3084,98 @@
30843084
"category": "app",
30853085
"related": []
30863086
},
3087+
{
3088+
"name": "app validate",
3089+
"description": "Validates the selected app configuration file and all extension configurations against their schemas and reports any errors found.",
3090+
"overviewPreviewDescription": "Validate your app configuration and extensions.",
3091+
"type": "command",
3092+
"isVisualComponent": false,
3093+
"defaultExample": {
3094+
"codeblock": {
3095+
"tabs": [
3096+
{
3097+
"title": "app validate",
3098+
"code": "shopify app validate [flags]",
3099+
"language": "bash"
3100+
}
3101+
],
3102+
"title": "app validate"
3103+
}
3104+
},
3105+
"definitions": [
3106+
{
3107+
"title": "Flags",
3108+
"description": "The following flags are available for the `app validate` command:",
3109+
"type": "appvalidate",
3110+
"typeDefinitions": {
3111+
"appvalidate": {
3112+
"filePath": "docs-shopify.dev/commands/interfaces/app-validate.interface.ts",
3113+
"name": "appvalidate",
3114+
"description": "",
3115+
"members": [
3116+
{
3117+
"filePath": "docs-shopify.dev/commands/interfaces/app-validate.interface.ts",
3118+
"syntaxKind": "PropertySignature",
3119+
"name": "--client-id <value>",
3120+
"value": "string",
3121+
"description": "The Client ID of your app.",
3122+
"isOptional": true,
3123+
"environmentValue": "SHOPIFY_FLAG_CLIENT_ID"
3124+
},
3125+
{
3126+
"filePath": "docs-shopify.dev/commands/interfaces/app-validate.interface.ts",
3127+
"syntaxKind": "PropertySignature",
3128+
"name": "--no-color",
3129+
"value": "\"\"",
3130+
"description": "Disable color output.",
3131+
"isOptional": true,
3132+
"environmentValue": "SHOPIFY_FLAG_NO_COLOR"
3133+
},
3134+
{
3135+
"filePath": "docs-shopify.dev/commands/interfaces/app-validate.interface.ts",
3136+
"syntaxKind": "PropertySignature",
3137+
"name": "--path <value>",
3138+
"value": "string",
3139+
"description": "The path to your app directory.",
3140+
"isOptional": true,
3141+
"environmentValue": "SHOPIFY_FLAG_PATH"
3142+
},
3143+
{
3144+
"filePath": "docs-shopify.dev/commands/interfaces/app-validate.interface.ts",
3145+
"syntaxKind": "PropertySignature",
3146+
"name": "--reset",
3147+
"value": "\"\"",
3148+
"description": "Reset all your settings.",
3149+
"isOptional": true,
3150+
"environmentValue": "SHOPIFY_FLAG_RESET"
3151+
},
3152+
{
3153+
"filePath": "docs-shopify.dev/commands/interfaces/app-validate.interface.ts",
3154+
"syntaxKind": "PropertySignature",
3155+
"name": "--verbose",
3156+
"value": "\"\"",
3157+
"description": "Increase the verbosity of the output.",
3158+
"isOptional": true,
3159+
"environmentValue": "SHOPIFY_FLAG_VERBOSE"
3160+
},
3161+
{
3162+
"filePath": "docs-shopify.dev/commands/interfaces/app-validate.interface.ts",
3163+
"syntaxKind": "PropertySignature",
3164+
"name": "-c, --config <value>",
3165+
"value": "string",
3166+
"description": "The name of the app configuration.",
3167+
"isOptional": true,
3168+
"environmentValue": "SHOPIFY_FLAG_APP_CONFIG"
3169+
}
3170+
],
3171+
"value": "export interface appvalidate {\n /**\n * The Client ID of your app.\n * @environment SHOPIFY_FLAG_CLIENT_ID\n */\n '--client-id <value>'?: string\n\n /**\n * The name of the app configuration.\n * @environment SHOPIFY_FLAG_APP_CONFIG\n */\n '-c, --config <value>'?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * The path to your app directory.\n * @environment SHOPIFY_FLAG_PATH\n */\n '--path <value>'?: string\n\n /**\n * Reset all your settings.\n * @environment SHOPIFY_FLAG_RESET\n */\n '--reset'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
3172+
}
3173+
}
3174+
}
3175+
],
3176+
"category": "app",
3177+
"related": []
3178+
},
30873179
{
30883180
"name": "app versions list",
30893181
"description": "Lists the deployed app versions. An app version is a snapshot of your app extensions.",
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {appFlags} from '../../flags.js'
2+
import {validateApp} from '../../services/validate.js'
3+
import AppLinkedCommand, {AppLinkedCommandOutput} from '../../utilities/app-linked-command.js'
4+
import {linkedAppContext} from '../../services/app-context.js'
5+
import {globalFlags} from '@shopify/cli-kit/node/cli'
6+
7+
export default class Validate extends AppLinkedCommand {
8+
static summary = 'Validate your app configuration and extensions.'
9+
10+
static descriptionWithMarkdown = `Validates the selected app configuration file and all extension configurations against their schemas and reports any errors found.`
11+
12+
static description = this.descriptionWithoutMarkdown()
13+
14+
static flags = {
15+
...globalFlags,
16+
...appFlags,
17+
}
18+
19+
public async run(): Promise<AppLinkedCommandOutput> {
20+
const {flags} = await this.parse(Validate)
21+
22+
const {app} = await linkedAppContext({
23+
directory: flags.path,
24+
clientId: flags['client-id'],
25+
forceRelink: flags.reset,
26+
userProvidedConfigName: flags.config,
27+
unsafeReportMode: true,
28+
})
29+
30+
await validateApp(app)
31+
32+
return {app}
33+
}
34+
}

packages/app/src/cli/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import GenerateSchema from './commands/app/generate/schema.js'
2323
import ImportExtensions from './commands/app/import-extensions.js'
2424
import AppInfo from './commands/app/info.js'
2525
import Init from './commands/app/init.js'
26+
import Validate from './commands/app/validate.js'
2627
import Release from './commands/app/release.js'
2728
import VersionsList from './commands/app/versions/list.js'
2829
import WebhookTrigger from './commands/app/webhook/trigger.js'
@@ -55,6 +56,7 @@ export const commands: {[key: string]: typeof AppLinkedCommand | typeof AppUnlin
5556
'app:import-extensions': ImportExtensions,
5657
'app:info': AppInfo,
5758
'app:init': Init,
59+
'app:validate': Validate,
5860
'app:release': Release,
5961
'app:config:link': ConfigLink,
6062
'app:config:use': ConfigUse,
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {validateApp} from './validate.js'
2+
import {testAppLinked} from '../models/app/app.test-data.js'
3+
import {AppErrors} from '../models/app/loader.js'
4+
import {describe, expect, test, vi} from 'vitest'
5+
import {renderError, renderSuccess} from '@shopify/cli-kit/node/ui'
6+
import {AbortSilentError} from '@shopify/cli-kit/node/error'
7+
8+
vi.mock('@shopify/cli-kit/node/ui')
9+
10+
describe('validateApp', () => {
11+
test('renders success when there are no errors', async () => {
12+
// Given
13+
const app = testAppLinked()
14+
15+
// When
16+
await validateApp(app)
17+
18+
// Then
19+
expect(renderSuccess).toHaveBeenCalledWith({headline: 'App configuration is valid.'})
20+
expect(renderError).not.toHaveBeenCalled()
21+
})
22+
23+
test('renders errors and throws when there are validation errors', async () => {
24+
// Given
25+
const errors = new AppErrors()
26+
errors.addError('/path/to/shopify.app.toml', 'client_id is required')
27+
errors.addError('/path/to/extensions/my-ext/shopify.extension.toml', 'invalid type "unknown"')
28+
const app = testAppLinked()
29+
app.errors = errors
30+
31+
// When / Then
32+
await expect(validateApp(app)).rejects.toThrow(AbortSilentError)
33+
expect(renderError).toHaveBeenCalledWith({
34+
headline: 'Validation errors found.',
35+
body: expect.stringContaining('client_id is required'),
36+
})
37+
expect(renderSuccess).not.toHaveBeenCalled()
38+
})
39+
40+
test('renders success when errors object exists but is empty', async () => {
41+
// Given
42+
const errors = new AppErrors()
43+
const app = testAppLinked()
44+
app.errors = errors
45+
46+
// When
47+
await validateApp(app)
48+
49+
// Then
50+
expect(renderSuccess).toHaveBeenCalledWith({headline: 'App configuration is valid.'})
51+
})
52+
})
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {AppLinkedInterface} from '../models/app/app.js'
2+
import {stringifyMessage} from '@shopify/cli-kit/node/output'
3+
import {renderError, renderSuccess} from '@shopify/cli-kit/node/ui'
4+
import {AbortSilentError} from '@shopify/cli-kit/node/error'
5+
6+
export async function validateApp(app: AppLinkedInterface): Promise<void> {
7+
const errors = app.errors
8+
9+
if (!errors || errors.isEmpty()) {
10+
renderSuccess({headline: 'App configuration is valid.'})
11+
return
12+
}
13+
14+
const errorMessages = errors.toJSON().map((error) => stringifyMessage(error).trim())
15+
16+
renderError({
17+
headline: 'Validation errors found.',
18+
body: errorMessages.join('\n\n'),
19+
})
20+
21+
throw new AbortSilentError()
22+
}

packages/cli/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
* [`shopify app logs`](#shopify-app-logs)
2828
* [`shopify app logs sources`](#shopify-app-logs-sources)
2929
* [`shopify app release --version <version>`](#shopify-app-release---version-version)
30+
* [`shopify app validate`](#shopify-app-validate)
3031
* [`shopify app versions list`](#shopify-app-versions-list)
3132
* [`shopify app webhook trigger`](#shopify-app-webhook-trigger)
3233
* [`shopify auth login`](#shopify-auth-login)
@@ -919,6 +920,29 @@ DESCRIPTION
919920
Releases an existing app version. Pass the name of the version that you want to release using the `--version` flag.
920921
```
921922

923+
## `shopify app validate`
924+
925+
Validate your app configuration and extensions.
926+
927+
```
928+
USAGE
929+
$ shopify app validate [--client-id <value> | -c <value>] [--no-color] [--path <value>] [--reset | ] [--verbose]
930+
931+
FLAGS
932+
-c, --config=<value> [env: SHOPIFY_FLAG_APP_CONFIG] The name of the app configuration.
933+
--client-id=<value> [env: SHOPIFY_FLAG_CLIENT_ID] The Client ID of your app.
934+
--no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output.
935+
--path=<value> [env: SHOPIFY_FLAG_PATH] The path to your app directory.
936+
--reset [env: SHOPIFY_FLAG_RESET] Reset all your settings.
937+
--verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output.
938+
939+
DESCRIPTION
940+
Validate your app configuration and extensions.
941+
942+
Validates the selected app configuration file and all extension configurations against their schemas and reports any
943+
errors found.
944+
```
945+
922946
## `shopify app versions list`
923947

924948
List deployed versions of your app.

packages/cli/oclif.manifest.json

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2852,6 +2852,84 @@
28522852
"summary": "Release an app version.",
28532853
"usage": "app release --version <version>"
28542854
},
2855+
"app:validate": {
2856+
"aliases": [
2857+
],
2858+
"args": {
2859+
},
2860+
"customPluginName": "@shopify/app",
2861+
"description": "Validates the selected app configuration file and all extension configurations against their schemas and reports any errors found.",
2862+
"descriptionWithMarkdown": "Validates the selected app configuration file and all extension configurations against their schemas and reports any errors found.",
2863+
"flags": {
2864+
"client-id": {
2865+
"description": "The Client ID of your app.",
2866+
"env": "SHOPIFY_FLAG_CLIENT_ID",
2867+
"exclusive": [
2868+
"config"
2869+
],
2870+
"hasDynamicHelp": false,
2871+
"hidden": false,
2872+
"multiple": false,
2873+
"name": "client-id",
2874+
"type": "option"
2875+
},
2876+
"config": {
2877+
"char": "c",
2878+
"description": "The name of the app configuration.",
2879+
"env": "SHOPIFY_FLAG_APP_CONFIG",
2880+
"hasDynamicHelp": false,
2881+
"hidden": false,
2882+
"multiple": false,
2883+
"name": "config",
2884+
"type": "option"
2885+
},
2886+
"no-color": {
2887+
"allowNo": false,
2888+
"description": "Disable color output.",
2889+
"env": "SHOPIFY_FLAG_NO_COLOR",
2890+
"hidden": false,
2891+
"name": "no-color",
2892+
"type": "boolean"
2893+
},
2894+
"path": {
2895+
"description": "The path to your app directory.",
2896+
"env": "SHOPIFY_FLAG_PATH",
2897+
"hasDynamicHelp": false,
2898+
"multiple": false,
2899+
"name": "path",
2900+
"noCacheDefault": true,
2901+
"type": "option"
2902+
},
2903+
"reset": {
2904+
"allowNo": false,
2905+
"description": "Reset all your settings.",
2906+
"env": "SHOPIFY_FLAG_RESET",
2907+
"exclusive": [
2908+
"config"
2909+
],
2910+
"hidden": false,
2911+
"name": "reset",
2912+
"type": "boolean"
2913+
},
2914+
"verbose": {
2915+
"allowNo": false,
2916+
"description": "Increase the verbosity of the output.",
2917+
"env": "SHOPIFY_FLAG_VERBOSE",
2918+
"hidden": false,
2919+
"name": "verbose",
2920+
"type": "boolean"
2921+
}
2922+
},
2923+
"hasDynamicHelp": false,
2924+
"hiddenAliases": [
2925+
],
2926+
"id": "app:validate",
2927+
"pluginAlias": "@shopify/cli",
2928+
"pluginName": "@shopify/cli",
2929+
"pluginType": "core",
2930+
"strict": true,
2931+
"summary": "Validate your app configuration and extensions."
2932+
},
28552933
"app:versions:list": {
28562934
"aliases": [
28572935
],

packages/e2e/data/snapshots/commands.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
│ ├─ logs
3232
│ │ └─ sources
3333
│ ├─ release
34+
│ ├─ validate
3435
│ ├─ versions
3536
│ │ └─ list
3637
│ └─ webhook

0 commit comments

Comments
 (0)