Skip to main content

validate-app

Run comprehensive pre-submission validation on commerce app packages. Use IMMEDIATELY when users mention "validate", "check", "verify", or "ready to submit". REQUIRED before calling submit-app - trigger proactively to catch errors early. Validates directory structure, manifest format, SHA256 hashes, impex XML, security issues, and runs complete CONTRIBUTING.md checklist. Also use when debugging validation failures or import errors to identify root cause quickly.

Source facts

Repository
SalesforceCommerceCloud/commerce-apps
Last source activity
September 9, 2026 at 22:39
Detected SKILL.md language
English
Stars
12
Forks
9

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
5 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
validate-app
description
Run comprehensive pre-submission validation on commerce app packages. Use IMMEDIATELY when users mention "validate", "check", "verify", or "ready to submit". REQUIRED before calling submit-app - trigger proactively to catch errors early. Validates directory structure, manifest format, SHA256 hashes, impex XML, security issues, and runs complete CONTRIBUTING.md checklist. Also use when debugging validation failures or import errors to identify root cause quickly.
# Validate Commerce App Package Run comprehensive validation checks on a commerce app before submitting a PR. > **Cross-tool note:** References to `/skill-name` (e.g., `/submit-app`) are Claude Code invocation syntax. If using another assistant, read and follow the corresponding `.claude/skills/<skill-name>/SKILL.md` file instead. ## Step 1: Identify app and resolve input Gather: - Domain (e.g., `tax`, `payment`, `shipping`) - App name (e.g., `avalara-tax`) - Version (or use latest ZIP) **Structure:** Apps must be at `{domain}/{appName}/` where `{appName}` matches the "id" field. See `references/folder-structure.md`. The skill accepts **either** a packaged `.zip` **or** an already-extracted CAP root directory (e.g., `commerce-<appName>-app-v<version>/`). Set both variables once; later steps branch on `INPUT_KIND`: ```bash # Resolve the input: a .zip path OR an extracted CAP root directory. INPUT="<path-to-zip-or-extracted-cap-root>" if [[ -f "$INPUT" && "$INPUT" == *.zip ]]; then INPUT_KIND=zip ZIP="$INPUT" EXTRACT_DIR="$(mktemp -d)" unzip -q "$ZIP" -d "$EXTRACT_DIR" CAP_ROOT="$EXTRACT_DIR/commerce-<appName>-app-v<version>" elif [[ -d "$INPUT" ]]; then INPUT_KIND=dir CAP_ROOT="$INPUT" ZIP="" # no zip available — Step 3 will be skipped EXTRACT_DIR="" # nothing to clean up in Step 14 else echo "Input must be a .zip file or extracted CAP root directory" >&2; exit 2 fi ``` ## Step 2: Verify input exists ```bash if [[ "$INPUT_KIND" == "zip" ]]; then ls -lh "$ZIP"; else ls -ld "$CAP_ROOT"; fi ``` ## Step 3: Validate SHA256 **Skip if `INPUT_KIND=dir`** — the manifest pins a hash of the `.zip` artifact, which can't be reproduced from extracted files. Note this gap in the report and re-run Step 3 once the ZIP is built. ```bash if [[ "$INPUT_KIND" == "zip" ]]; then shasum -a 256 "$ZIP" jq '[.[] | select(type=="array")] | flatten | .[] | select(.id == "<appName>") | .sha256' \ commerce-apps-manifest/manifest.json else echo "(skipped — no ZIP available; SHA256 must be re-checked against the built ZIP)" fi ``` Hashes must match exactly. ## Step 4: Validate manifest entry Check `commerce-apps-manifest/manifest.json` has all required fields: - `id` - matches app name - `name` - display name - `description` - `iconName` - matches icon in ZIP `icons/` directory - `domain` - valid domain (hyphen-case) - `type` - must be `"app"` - `provider` - must be `"thirdParty"` - `version` - semantic versioning - `zip` - matches actual filename - `sha256` - matches computed hash Optional fields (validate if present): - `storefrontSupport.sfnext.minVersion` - valid semver (`X.Y.Z` or `X.Y.Z-prerelease`) - `storefrontSupport.sfnext.maxVersion` - valid semver, optional (`X.Y.Z` or `X.Y.Z-prerelease`) - `storefrontSupport.sfra.minVersion` - valid semver (`X.Y.Z` or `X.Y.Z-prerelease`) - `storefrontSupport.sfra.maxVersion` - valid semver, optional (`X.Y.Z` or `X.Y.Z-prerelease`) - Only `sfnext` and `sfra` keys allowed inside `storefrontSupport`; only `minVersion` and `maxVersion` allowed inside each - If `storefrontSupport.sfra` is present, `storefrontSupport.sfnext` must also be present (with a valid `minVersion`). Fail validation on SFRA-only declarations. - `storefrontSupport` must be present in **both** the root manifest and `commerce-app.json` with matching values ## Step 5: Validate package contents ```bash if [[ "$INPUT_KIND" == "zip" ]]; then unzip -l "$ZIP" | head -30 unzip -l "$ZIP" | grep -E "\.DS_Store|__MACOSX" || echo " ✓ no junk files" else ls -la "$CAP_ROOT" | head -30 find "$CAP_ROOT" \( -name '.DS_Store' -o -name '__MACOSX' \) -print | head \ || echo " ✓ no junk files" fi # Required files (works for both inputs) for f in commerce-app.json README.md app-configuration/tasksList.json; do [[ -e "$CAP_ROOT/$f" ]] && echo " ✓ $f" || echo " ✗ MISSING: $f" done ``` Check: - Single root: `commerce-<appName>-app-v<version>/` (ZIP only — for `INPUT_KIND=dir`, the directory IS the root) - No junk hidden files (`.DS_Store`, `__MACOSX`, `.env`, secrets) — **except** `.project`, which every cartridge root MUST include (see Step 6b) - No registry paths (`tax/`, `domain/`) at the ZIP root (ZIP only) - Required: `commerce-app.json`, `README.md`, `app-configuration/tasksList.json` ## Step 6: Detect architecture ```bash HAS_UI=$([[ -d "$CAP_ROOT/storefront-next" ]] && echo 1 || echo 0) HAS_BACKEND=$([[ -d "$CAP_ROOT/cartridges" ]] && echo 1 || echo 0) ``` Determine: - **UI-only:** Has `storefront-next/`, NO `cartridges/` - **Backend-only:** Has `cartridges/`, NO `storefront-next/` - **Fullstack:** Has both ## Step 6b: Validate cartridge `.project` files (Backend-only/Fullstack) **Skip if UI-only** (`HAS_BACKEND=0`). Every immediate child directory of `cartridges/site_cartridges/` and `cartridges/bm_cartridges/` must contain a `.project` file. It may be empty (auto-created by scaffolding/packaging) or a real, non-empty Eclipse `.project` file — both PASS. Missing is a FAIL. ```bash bash .github/scripts/validate-cartridge-project.sh "$CAP_ROOT" ``` ## Step 7: Validate commerce-app.json ```bash jq . "$CAP_ROOT/commerce-app.json" ``` Required fields: - `id`, `name`, `description`, `domain`, `version` - `publisher.name`, `publisher.url`, `publisher.support` Version must match root manifest version. Optional fields (validate if present): - `storefrontSupport.sfnext.minVersion` - valid semver (`X.Y.Z` or `X.Y.Z-prerelease`) - `storefrontSupport.sfnext.maxVersion` - valid semver, optional (`X.Y.Z` or `X.Y.Z-prerelease`) - `storefrontSupport.sfra.minVersion` - valid semver (`X.Y.Z` or `X.Y.Z-prerelease`) - `storefrontSupport.sfra.maxVersion` - valid semver, optional (`X.Y.Z` or `X.Y.Z-prerelease`) - If `storefrontSupport.sfra` is present, `storefrontSupport.sfnext` must also be present (with a valid `minVersion`). Fail validation on SFRA-only declarations. - Values must match the corresponding fields in the root manifest entry ## Step 8: Validate storefront files (UI-only/Fullstack) **Skip if Backend-only.** Required: - `storefront-next/src/extensions/<appName>/target-config.json` — entry point; declares `components[]`, `actionHooks[]`, etc., each pointing at a `path` under the extension directory. For each entry referenced from `target-config.json`, verify the file exists: ```bash TC="$CAP_ROOT/storefront-next/src/extensions/<appName>/target-config.json" jq -r '[.components[]?.path, .actionHooks[]?.handler, .routes[]?.handler] | .[] | select(.)' "$TC" \ | while read -r p; do [[ -f "$CAP_ROOT/storefront-next/src/$p" ]] \ && echo " ✓ $p" \ || echo " ✗ MISSING: $p" done ``` If the extension ships translations, they live under `storefront-next/src/extensions/<appName>/locales/<locale>/translations.json`. Locale set is app-specific — not a fixed allowlist. ## Step 9: Validate impex (Backend-only/Fullstack) **Skip if UI-only.** ```bash find "$CAP_ROOT/impex/" -name "*.xml" -exec xmllint --noout {} \; ``` See `references/impex-validation.md` for detailed rules: - Services use dotted notation - Install/uninstall pairs match - Uninstall `services.xml` uses credential → profile → service order with `mode="delete"` (same XSD sequence as install; BM UI service-first delete does not apply to IMPEX) - Attribute IDs use camelCase with app prefix - SITEID placeholder (not actual site ID) - No hardcoded credentials ## Step 10: Verify catalog.json - **Existing app:** catalog.json exists, unchanged in PR - **New app:** catalog.json has INIT values: ```json { "latest": {"version": "INIT", "tag": "INIT"}, "versions": [] } ``` ## Step 10b: Validate app-configuration/ JSON via CI scripts Run the same scripts CI runs against `$CAP_ROOT` (already set in Step 1). Each is the source of truth for its schema; scripts print errors to stderr and exit non-zero on failure. ```bash # tasksList.json schema (required file) bash .github/scripts/validate-tasks-list.sh "$CAP_ROOT/app-configuration/tasksList.json" # adminComponents.json schema (optional file) [[ -f "$CAP_ROOT/app-configuration/adminComponents.json" ]] && \ bash .github/scripts/validate-admin-components.sh "$CAP_ROOT/app-configuration/adminComponents.json" # app-shipped translations (optional directory; cross-references the two files above) bash .github/scripts/validate-translations.sh "$CAP_ROOT" ``` Each script prints schema details and exits non-zero with errors on stderr. See the script header comments for the schema contract. ## Step 11: Validate manifest-level translations The CAP-internal translations live under `app-configuration/translations/` (validated in Step 10b). The **manifest-level** `commerce-apps-manifest/translations/en-US.json` holds the marketplace `name` / `description` shown on the app card: ```bash jq '."<appName>"' commerce-apps-manifest/translations/en-US.json ``` Check: - Entry exists in `en-US.json` (minimum) - Has `name` and `description` fields - Valid JSON structure ## Step 12: Validate icon ```bash ICON_NAME=$(jq -r '[.[] | select(type=="array")] | flatten | .[] | select(.id == "<appName>") | .iconName' \ commerce-apps-manifest/manifest.json) [[ -f "$CAP_ROOT/icons/$ICON_NAME" ]] && echo " ✓ icon in package" || echo " - not in package" [[ -f "commerce-apps-manifest/icons/$ICON_NAME" ]] && echo " ✓ icon in registry" || echo " ✗ MISSING in registry" ``` Icon filename must match `iconName` field exactly. CI extracts automatically. ## Step 13: Security scan ```bash bash .github/scripts/security-scan.sh "$CAP_ROOT/" ``` **If blocking findings (exit 1):** FAIL - fix issues first. **If warnings only:** Continue with warnings for review. See `references/security-scan.md` for details. ## Step 14: Clean up Only remove the temp dir if Step 1 created one (`INPUT_KIND=zip`). When the user passed an extracted CAP root, leave it alone. ```bash [[ -n "$EXTRACT_DIR" ]] && rm -rf "$EXTRACT_DIR" ``` ## Report results - **✅ PASS** - All validations passed, ready for `/submit-app` - **❌ FAIL** - List specific issues with file paths and line numbers Provide fix recommendations for each issue.
View on GitHub