diff --git a/.changeset/app-get-review-requirements.md b/.changeset/app-get-review-requirements.md new file mode 100644 index 00000000000..254eedb92d0 --- /dev/null +++ b/.changeset/app-get-review-requirements.md @@ -0,0 +1,6 @@ +--- +'@shopify/app': minor +'@shopify/cli-kit': patch +--- + +Add `shopify app get-review-requirements`, a new command that prints the [App Store review requirements](https://shopify.dev/docs/apps/launch/app-store-review/app-store-ai-self-review-requirements), with agent verification guidance, as markdown. Designed to be invoked by the `/shopify-app-review` agent skill in the Shopify AI Toolkit to power a local pre-submission compliance check against an app's codebase. diff --git a/packages/app/src/cli/commands/app/get-review-requirements.ts b/packages/app/src/cli/commands/app/get-review-requirements.ts new file mode 100644 index 00000000000..fc5aebe55e8 --- /dev/null +++ b/packages/app/src/cli/commands/app/get-review-requirements.ts @@ -0,0 +1,32 @@ +import {getReviewRequirements} from '../../services/app/get-review-requirements.js' +import {appFlags} from '../../flags.js' +import BaseCommand from '@shopify/cli-kit/node/base-command' +import {globalFlags} from '@shopify/cli-kit/node/cli' +import {outputResult} from '@shopify/cli-kit/node/output' + +export default class AppGetReviewRequirements extends BaseCommand { + static summary = 'Print the App Store self-review requirements as markdown.' + + static descriptionWithMarkdown = `Prints the [App Store review requirements](https://shopify.dev/docs/apps/launch/app-store-review/app-store-ai-self-review-requirements) to stdout as markdown, including the agent verification guidance an AI agent needs to evaluate each requirement against a local codebase. + + Designed to be invoked by the \`shopify-app-review\` agent skill from the Shopify AI Toolkit as part of a local pre-submission compliance check, but you can also run it yourself to read the latest locally checkable requirements.` + + static description = this.descriptionWithoutMarkdown() + + static flags = { + ...globalFlags, + path: appFlags.path, + config: appFlags.config, + } + + public async run(): Promise { + const {flags} = await this.parse(AppGetReviewRequirements) + + const markdown = await getReviewRequirements({ + directory: flags.path, + configName: flags.config, + }) + + outputResult(markdown) + } +} diff --git a/packages/app/src/cli/index.ts b/packages/app/src/cli/index.ts index f9d51c1261c..f67507a5279 100644 --- a/packages/app/src/cli/index.ts +++ b/packages/app/src/cli/index.ts @@ -20,6 +20,7 @@ import FetchSchema from './commands/app/function/schema.js' import FunctionTypegen from './commands/app/function/typegen.js' import AppGenerateExtension from './commands/app/generate/extension.js' import ImportExtensions from './commands/app/import-extensions.js' +import AppGetReviewRequirements from './commands/app/get-review-requirements.js' import AppInfo from './commands/app/info.js' import Init from './commands/app/init.js' import ConfigValidate from './commands/app/config/validate.js' @@ -70,6 +71,7 @@ export const commands: {[key: string]: typeof AppLinkedCommand | typeof AppUnlin 'app:function:schema': FetchSchema, 'app:function:typegen': FunctionTypegen, 'app:generate:extension': AppGenerateExtension, + 'app:get-review-requirements': AppGetReviewRequirements, 'app:versions:list': VersionsList, 'app:webhook:trigger': WebhookTrigger, 'demo:watcher': DemoWatcher, diff --git a/packages/app/src/cli/services/app/get-review-requirements.ts b/packages/app/src/cli/services/app/get-review-requirements.ts new file mode 100644 index 00000000000..8383a5e4d26 --- /dev/null +++ b/packages/app/src/cli/services/app/get-review-requirements.ts @@ -0,0 +1,88 @@ +import {loadApp} from '../../models/app/loader.js' +import {loadLocalExtensionsSpecifications} from '../../models/extensions/load-specifications.js' +import metadata from '../../metadata.js' +import {fetch} from '@shopify/cli-kit/node/http' +import {outputDebug} from '@shopify/cli-kit/node/output' +import {AbortError} from '@shopify/cli-kit/node/error' +import {CLI_KIT_VERSION} from '@shopify/cli-kit/common/version' + +const REQUIREMENTS_URL = 'https://shopify.dev/docs/apps/launch/app-store-review/app-store-ai-self-review-requirements' + +export interface GetReviewRequirementsOptions { + directory: string + configName?: string +} + +/** + * Fetches the canonical App Store self-review requirements markdown from + * shopify.dev and returns it as a string. Best-effort loads the local app so + * the auto-emitted Monorail event carries app-level attribution (api_key, + * app_path_hash, app_scopes, etc.). + * + * Designed to be invoked by the `shopify-app-review` agent skill in the + * Shopify AI Toolkit: the skill shells out to + * `shopify app get-review-requirements`, captures stdout, and uses it as the + * live list of requirements to evaluate the codebase against. + */ +export async function getReviewRequirements(options: GetReviewRequirementsOptions): Promise { + await loadAppForAttribution(options) + + const markdown = await fetchRequirementsMarkdown() + return markdown +} + +async function loadAppForAttribution(options: GetReviewRequirementsOptions): Promise { + try { + const app = await loadApp({ + directory: options.directory, + userProvidedConfigName: options.configName, + specifications: await loadLocalExtensionsSpecifications(), + remoteFlags: undefined, + skipPrompts: true, + }) + + if (app.configuration.client_id) { + await metadata.addPublicMetadata(() => ({ + api_key: String(app.configuration.client_id), + })) + } + // eslint-disable-next-line no-catch-all/no-catch-all + } catch (error) { + outputDebug( + `Skipping app attribution for review-requirements fetch: ${ + error instanceof Error ? error.message : String(error) + }`, + ) + } +} + +async function fetchRequirementsMarkdown(): Promise { + const response = await fetch( + REQUIREMENTS_URL, + { + headers: { + Accept: 'text/markdown, text/plain;q=0.9, */*;q=0.1', + 'User-Agent': `Shopify CLI/${CLI_KIT_VERSION} (app get-review-requirements)`, + }, + }, + 'non-blocking', + ) + + if (!response.ok) { + await metadata.addPublicMetadata(() => ({ + cmd_app_review_requirements_fetch_status: response.status, + })) + throw new AbortError( + `Failed to fetch App Store self-review requirements from shopify.dev (HTTP ${response.status} ${response.statusText}).`, + `Check your network connection and try again, or visit ${REQUIREMENTS_URL} directly.`, + ) + } + + const body = await response.text() + + await metadata.addPublicMetadata(() => ({ + cmd_app_review_requirements_fetch_status: response.status, + })) + + return body +} diff --git a/packages/cli-kit/src/public/node/monorail.ts b/packages/cli-kit/src/public/node/monorail.ts index bb8d1e9dcd2..ee88ec4329f 100644 --- a/packages/cli-kit/src/public/node/monorail.ts +++ b/packages/cli-kit/src/public/node/monorail.ts @@ -100,6 +100,7 @@ export interface Schemas { cmd_app_validate_valid?: Optional cmd_app_validate_issue_count?: Optional cmd_app_validate_file_count?: Optional + cmd_app_review_requirements_fetch_status?: Optional // Dev related commands cmd_dev_tunnel_type?: Optional diff --git a/packages/cli/README.md b/packages/cli/README.md index f7d6f3620d6..95d66550986 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -21,6 +21,7 @@ * [`shopify app function schema`](#shopify-app-function-schema) * [`shopify app function typegen`](#shopify-app-function-typegen) * [`shopify app generate extension`](#shopify-app-generate-extension) +* [`shopify app get-review-requirements`](#shopify-app-get-review-requirements) * [`shopify app import-custom-data-definitions`](#shopify-app-import-custom-data-definitions) * [`shopify app import-extensions`](#shopify-app-import-extensions) * [`shopify app info`](#shopify-app-info) @@ -749,6 +750,31 @@ DESCRIPTION your extension. ``` +## `shopify app get-review-requirements` + +Print the App Store self-review requirements as markdown. + +``` +USAGE + $ shopify app get-review-requirements [-c ] [--no-color] [--path ] [--verbose] + +FLAGS + -c, --config= [env: SHOPIFY_FLAG_APP_CONFIG] The name of the app configuration. + --no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output. + --path= [env: SHOPIFY_FLAG_PATH] The path to your app directory. + --verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output. + +DESCRIPTION + Print the App Store self-review requirements as markdown. + + Prints the "App Store review requirements" + (https://shopify.dev/docs/apps/launch/app-store-review/app-store-ai-self-review-requirements) to stdout as markdown, + including the agent verification guidance an AI agent needs to evaluate each requirement against a local codebase. + + Designed to be invoked by the `shopify-app-review` agent skill from the Shopify AI Toolkit as part of a local + pre-submission compliance check, but you can also run it yourself to read the latest locally checkable requirements. +``` + ## `shopify app import-custom-data-definitions` Import metafield and metaobject definitions. diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index eeacfe2b751..661dfaba633 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -2143,6 +2143,62 @@ "strict": true, "summary": "Generate a new app Extension." }, + "app:get-review-requirements": { + "aliases": [ + ], + "args": { + }, + "customPluginName": "@shopify/app", + "description": "Prints the \"App Store review requirements\" (https://shopify.dev/docs/apps/launch/app-store-review/app-store-ai-self-review-requirements) to stdout as markdown, including the agent verification guidance an AI agent needs to evaluate each requirement against a local codebase.\n\n Designed to be invoked by the `shopify-app-review` agent skill from the Shopify AI Toolkit as part of a local pre-submission compliance check, but you can also run it yourself to read the latest locally checkable requirements.", + "descriptionWithMarkdown": "Prints the [App Store review requirements](https://shopify.dev/docs/apps/launch/app-store-review/app-store-ai-self-review-requirements) to stdout as markdown, including the agent verification guidance an AI agent needs to evaluate each requirement against a local codebase.\n\n Designed to be invoked by the `shopify-app-review` agent skill from the Shopify AI Toolkit as part of a local pre-submission compliance check, but you can also run it yourself to read the latest locally checkable requirements.", + "enableJsonFlag": false, + "flags": { + "config": { + "char": "c", + "description": "The name of the app configuration.", + "env": "SHOPIFY_FLAG_APP_CONFIG", + "hasDynamicHelp": false, + "hidden": false, + "multiple": false, + "name": "config", + "type": "option" + }, + "no-color": { + "allowNo": false, + "description": "Disable color output.", + "env": "SHOPIFY_FLAG_NO_COLOR", + "hidden": false, + "name": "no-color", + "type": "boolean" + }, + "path": { + "description": "The path to your app directory.", + "env": "SHOPIFY_FLAG_PATH", + "hasDynamicHelp": false, + "multiple": false, + "name": "path", + "noCacheDefault": true, + "type": "option" + }, + "verbose": { + "allowNo": false, + "description": "Increase the verbosity of the output.", + "env": "SHOPIFY_FLAG_VERBOSE", + "hidden": false, + "name": "verbose", + "type": "boolean" + } + }, + "hasDynamicHelp": false, + "hiddenAliases": [ + ], + "id": "app:get-review-requirements", + "pluginAlias": "@shopify/cli", + "pluginName": "@shopify/cli", + "pluginType": "core", + "strict": true, + "summary": "Print the App Store self-review requirements as markdown." + }, "app:import-custom-data-definitions": { "aliases": [ ], @@ -7987,4 +8043,4 @@ } }, "version": "4.1.0" -} \ No newline at end of file +}