From 814e23b383fa3b2aadab3da9f9a9a2cd8bbdfdc0 Mon Sep 17 00:00:00 2001 From: Daniel van der Ploeg Date: Tue, 14 Apr 2026 12:02:40 +0930 Subject: [PATCH 1/6] feat GSM-29: add shopify theme pr checks Includes a Shopify theme linter with annotations and automatically deploy a preview theme --- .../shopify-theme-problem-matchers/action.yml | 20 +++ .../theme-check.json | 18 ++ .github/workflows/shopify-theme-pr.yml | 164 ++++++++++++++++++ .../shopify-theme-preview-cleanup.yml | 37 ++++ 4 files changed, 239 insertions(+) create mode 100644 .github/actions/shopify-theme-problem-matchers/action.yml create mode 100644 .github/actions/shopify-theme-problem-matchers/theme-check.json create mode 100644 .github/workflows/shopify-theme-pr.yml create mode 100644 .github/workflows/shopify-theme-preview-cleanup.yml diff --git a/.github/actions/shopify-theme-problem-matchers/action.yml b/.github/actions/shopify-theme-problem-matchers/action.yml new file mode 100644 index 0000000..4d31f29 --- /dev/null +++ b/.github/actions/shopify-theme-problem-matchers/action.yml @@ -0,0 +1,20 @@ +name: "Register Shopify Theme Problem Matchers" +description: "Registers or removes problem matchers for Shopify Theme Check" + +inputs: + action: + description: "Whether to add or remove matchers" + required: false + default: "add" + +runs: + using: composite + steps: + - if: inputs.action == 'add' + shell: bash + run: | + echo "::add-matcher::${{ github.action_path }}/theme-check.json" + - if: inputs.action == 'remove' + shell: bash + run: | + echo "::remove-matcher owner=theme-check::" diff --git a/.github/actions/shopify-theme-problem-matchers/theme-check.json b/.github/actions/shopify-theme-problem-matchers/theme-check.json new file mode 100644 index 0000000..0ae2681 --- /dev/null +++ b/.github/actions/shopify-theme-problem-matchers/theme-check.json @@ -0,0 +1,18 @@ +{ + "problemMatcher": [ + { + "owner": "theme-check", + "pattern": [ + { + "regexp": "^(.+):(\\d+):(\\d+):\\s+(error|warning|info)\\s+\\[(.+?)\\]\\s+(.+)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "code": 5, + "message": 6 + } + ] + } + ] +} diff --git a/.github/workflows/shopify-theme-pr.yml b/.github/workflows/shopify-theme-pr.yml new file mode 100644 index 0000000..6f82187 --- /dev/null +++ b/.github/workflows/shopify-theme-pr.yml @@ -0,0 +1,164 @@ +name: Shopify Theme PR Checks + +on: + workflow_call: + inputs: + working-directory: + description: 'Working directory for the theme' + required: false + type: string + default: '.' + fail-level: + description: 'Theme Check fail level (error, suggestion, style)' + required: false + type: string + default: 'error' + shopify-store: + description: 'Shopify store URL (e.g. example.myshopify.com). Required for preview deploy.' + required: false + type: string + deploy-preview: + description: 'Deploy an unpublished preview theme per PR' + required: false + type: boolean + default: false + pr-number: + description: 'The pull request number from the caller. Pass github.event.pull_request.number.' + required: true + type: string + secrets: + SHOPIFY_CLI_THEME_TOKEN: + description: 'Theme Access app password for preview deploys' + required: false + +jobs: + theme-check: + name: Theme Check + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 'lts/*' + + - name: Install Shopify CLI + run: npm install -g @shopify/cli@latest + + - name: Register problem matcher + uses: aligent/workflows/.github/actions/shopify-theme-problem-matchers@main + + - name: Run Theme Check + working-directory: ${{ inputs.working-directory }} + env: + INPUTS_FAIL_LEVEL: ${{ inputs.fail-level }} + run: | + shopify theme check --output json > theme-check-report.json 2>/dev/null || true + + # Transform JSON report into a format the problem matcher can parse + # Theme Check offenses use 0-indexed rows/columns; annotations need 1-indexed + jq -r ' + .[] | .path as $path | + .offenses[] | + "\($path):\(.start_row + 1):\(.start_column + 1): \(.severity) [\(.check)] \(.message)" + ' theme-check-report.json + + error_count=$(jq '[.[].errorCount] | add // 0' theme-check-report.json) + warning_count=$(jq '[.[].warningCount] | add // 0' theme-check-report.json) + echo "Theme Check: ${error_count} error(s), ${warning_count} warning(s)" + + fail_level="${INPUTS_FAIL_LEVEL}" + if [ "$fail_level" = "error" ] && [ "$error_count" -gt 0 ]; then + exit 1 + elif [ "$fail_level" = "warning" ] && [ $((error_count + warning_count)) -gt 0 ]; then + exit 1 + elif [ "$fail_level" = "suggestion" ] || [ "$fail_level" = "style" ]; then + total=$(jq '[.[].offenses | length] | add // 0' theme-check-report.json) + if [ "$total" -gt 0 ]; then + exit 1 + fi + fi + + - name: Remove problem matcher + if: always() + uses: aligent/workflows/.github/actions/shopify-theme-problem-matchers@main + with: + action: remove + + preview: + name: Preview Theme + needs: theme-check + if: inputs.deploy-preview + runs-on: ubuntu-latest + env: + SHOPIFY_CLI_THEME_TOKEN: ${{ secrets.SHOPIFY_CLI_THEME_TOKEN }} + SHOPIFY_FLAG_STORE: ${{ inputs.shopify-store }} + PR_THEME_NAME: "PR-#${{ inputs.pr-number }}" + steps: + - name: Validate inputs + run: | + if [ -z "${SHOPIFY_FLAG_STORE}" ]; then + echo "::error::shopify-store input is required when deploy-preview is enabled" + exit 1 + fi + if [ -z "${SHOPIFY_CLI_THEME_TOKEN}" ]; then + echo "::error::SHOPIFY_CLI_THEME_TOKEN secret is required when deploy-preview is enabled" + exit 1 + fi + + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 'lts/*' + + - name: Install Shopify CLI + run: npm install -g @shopify/cli@latest + + - name: Push unpublished theme + working-directory: ${{ inputs.working-directory }} + run: | + shopify theme push \ + --unpublished \ + --json \ + --theme "$PR_THEME_NAME" \ + > push.json + + - name: Extract preview URL + id: preview + run: | + url=$(jq -r '.theme.preview_url // empty' "${{ inputs.working-directory }}/push.json") + echo "url=$url" >> "$GITHUB_OUTPUT" + + - name: Comment preview link on PR + env: + GH_TOKEN: ${{ github.token }} + PREVIEW_URL: ${{ steps.preview.outputs.url }} + PR_NUMBER: ${{ inputs.pr-number }} + run: | + MARKER="" + BODY="${MARKER} + **Preview theme:** ${PR_THEME_NAME} + + **Preview URL:** ${PREVIEW_URL}" + + # Find an existing comment with the marker + COMMENT_ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \ + --jq ".[] | select(.body | startswith(\"${MARKER}\")) | .id" \ + | head -n1) + + if [ -n "$COMMENT_ID" ]; then + gh api "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" \ + --method PATCH \ + --field body="$BODY" + else + gh pr comment "$PR_NUMBER" --body "$BODY" + fi diff --git a/.github/workflows/shopify-theme-preview-cleanup.yml b/.github/workflows/shopify-theme-preview-cleanup.yml new file mode 100644 index 0000000..d92c009 --- /dev/null +++ b/.github/workflows/shopify-theme-preview-cleanup.yml @@ -0,0 +1,37 @@ +name: Shopify Theme Preview Cleanup + +on: + workflow_call: + inputs: + shopify-store: + description: 'Shopify store URL (e.g. example.myshopify.com)' + required: true + type: string + pr-number: + description: 'The pull request number from the caller. Pass github.event.pull_request.number.' + required: true + type: string + secrets: + SHOPIFY_CLI_THEME_TOKEN: + description: 'Theme Access app password' + required: true + +jobs: + cleanup: + name: Cleanup preview + runs-on: ubuntu-latest + env: + SHOPIFY_CLI_THEME_TOKEN: ${{ secrets.SHOPIFY_CLI_THEME_TOKEN }} + SHOPIFY_FLAG_STORE: ${{ inputs.shopify-store }} + PR_THEME_NAME: "PR-#${{ inputs.pr-number }}" + steps: + - name: Setup Node.js + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 'lts/*' + + - name: Install Shopify CLI + run: npm install -g @shopify/cli@latest + + - name: Delete preview theme (ignore if missing) + run: shopify theme delete --theme "$PR_THEME_NAME" --force || true From 192ab132e410a89b69d67055a7f34fb41c279540 Mon Sep 17 00:00:00 2001 From: Daniel van der Ploeg Date: Tue, 14 Apr 2026 12:04:53 +0930 Subject: [PATCH 2/6] feat GSM-29: add emojis --- .github/workflows/shopify-theme-pr.yml | 2 +- .github/workflows/shopify-theme-preview-cleanup.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/shopify-theme-pr.yml b/.github/workflows/shopify-theme-pr.yml index 6f82187..67e8bf5 100644 --- a/.github/workflows/shopify-theme-pr.yml +++ b/.github/workflows/shopify-theme-pr.yml @@ -1,4 +1,4 @@ -name: Shopify Theme PR Checks +name: 🎨 Shopify Theme PR Checks on: workflow_call: diff --git a/.github/workflows/shopify-theme-preview-cleanup.yml b/.github/workflows/shopify-theme-preview-cleanup.yml index d92c009..3c27f7e 100644 --- a/.github/workflows/shopify-theme-preview-cleanup.yml +++ b/.github/workflows/shopify-theme-preview-cleanup.yml @@ -1,4 +1,4 @@ -name: Shopify Theme Preview Cleanup +name: 🧹 Shopify Theme Preview Cleanup on: workflow_call: From 0e7f53b928d93f6629095239199f4b4febba3f20 Mon Sep 17 00:00:00 2001 From: Daniel van der Ploeg Date: Tue, 14 Apr 2026 13:00:52 +0930 Subject: [PATCH 3/6] chore GSM-29: add docs --- docs/shopify-theme-pr.md | 74 +++++++++++++++++++++++++++ docs/shopify-theme-preview-cleanup.md | 66 ++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 docs/shopify-theme-pr.md create mode 100644 docs/shopify-theme-preview-cleanup.md diff --git a/docs/shopify-theme-pr.md b/docs/shopify-theme-pr.md new file mode 100644 index 0000000..f08972c --- /dev/null +++ b/docs/shopify-theme-pr.md @@ -0,0 +1,74 @@ +# Shopify Theme PR Checks + +A reusable workflow for running quality checks on Shopify theme pull requests, with optional preview theme deployment. + +#### **Features** +- **Theme Check linting**: Runs Shopify Theme Check with configurable fail levels and GitHub problem matchers for inline annotations +- **Preview deployments**: Optionally deploys an unpublished preview theme per PR, with a comment linking to the preview URL +- **Idempotent PR comments**: Preview URL comments are updated in place rather than duplicated on subsequent pushes + +#### **Inputs** +| Name | Required | Type | Default | Description | +|------|----------|------|---------|-------------| +| working-directory | :x: | string | `.` | Working directory for the theme | +| fail-level | :x: | string | `error` | Theme Check fail level (`error`, `warning`, `suggestion`, `style`) | +| shopify-store | :x: | string | | Shopify store URL (e.g. `example.myshopify.com`). Required when `deploy-preview` is enabled | +| deploy-preview | :x: | boolean | `false` | Deploy an unpublished preview theme per PR | +| pr-number | :heavy_check_mark: | string | | The pull request number from the caller. Pass `github.event.pull_request.number` | + +#### **Secrets** +| Name | Required | Description | +|------|----------|-------------| +| SHOPIFY_CLI_THEME_TOKEN | :x: | Theme Access app password for preview deploys. Required when `deploy-preview` is enabled | + +#### **Jobs** +| Job | Description | +|-----|-------------| +| `theme-check` | Installs Shopify CLI, runs Theme Check, and surfaces offences as GitHub annotations via problem matchers | +| `preview` | Pushes the theme as an unpublished preview and comments the preview URL on the PR. Only runs when `deploy-preview` is `true` and `theme-check` passes | + +#### **Example Usage** + +**Basic theme linting only:** +```yaml +on: + pull_request: + +jobs: + theme-checks: + uses: aligent/workflows/.github/workflows/shopify-theme-pr.yml@main + with: + pr-number: ${{ github.event.pull_request.number }} +``` + +**With preview deployment:** +```yaml +on: + pull_request: + +jobs: + theme-checks: + uses: aligent/workflows/.github/workflows/shopify-theme-pr.yml@main + with: + pr-number: ${{ github.event.pull_request.number }} + deploy-preview: true + shopify-store: example.myshopify.com + secrets: + SHOPIFY_CLI_THEME_TOKEN: ${{ secrets.SHOPIFY_CLI_THEME_TOKEN }} +``` + +**Monorepo with stricter fail level:** +```yaml +on: + pull_request: + paths: + - 'themes/storefront/**' + +jobs: + theme-checks: + uses: aligent/workflows/.github/workflows/shopify-theme-pr.yml@main + with: + working-directory: themes/storefront + fail-level: warning + pr-number: ${{ github.event.pull_request.number }} +``` diff --git a/docs/shopify-theme-preview-cleanup.md b/docs/shopify-theme-preview-cleanup.md new file mode 100644 index 0000000..1d9bd34 --- /dev/null +++ b/docs/shopify-theme-preview-cleanup.md @@ -0,0 +1,66 @@ +# Shopify Theme Preview Cleanup + +A reusable workflow for deleting preview themes created by the [Shopify Theme PR Checks](shopify-theme-pr.md) workflow when a pull request is closed or merged. + +#### **Features** +- **Automatic cleanup**: Removes the unpublished preview theme associated with a PR +- **Safe deletion**: Silently succeeds if the theme has already been removed + +#### **Inputs** +| Name | Required | Type | Default | Description | +|------|----------|------|---------|-------------| +| shopify-store | :heavy_check_mark: | string | | Shopify store URL (e.g. `example.myshopify.com`) | +| pr-number | :heavy_check_mark: | string | | The pull request number from the caller. Pass `github.event.pull_request.number` | + +#### **Secrets** +| Name | Required | Description | +|------|----------|-------------| +| SHOPIFY_CLI_THEME_TOKEN | :heavy_check_mark: | Theme Access app password | + +#### **Example Usage** + +**Cleanup on PR close:** +```yaml +on: + pull_request: + types: [closed] + +jobs: + cleanup-preview: + uses: aligent/workflows/.github/workflows/shopify-theme-preview-cleanup.yml@main + with: + shopify-store: example.myshopify.com + pr-number: ${{ github.event.pull_request.number }} + secrets: + SHOPIFY_CLI_THEME_TOKEN: ${{ secrets.SHOPIFY_CLI_THEME_TOKEN }} +``` + +**Combined PR and cleanup workflows:** +```yaml +# .github/workflows/shopify-theme.yml +name: Shopify Theme + +on: + pull_request: + types: [opened, synchronize, reopened, closed] + +jobs: + pr-checks: + if: github.event.action != 'closed' + uses: aligent/workflows/.github/workflows/shopify-theme-pr.yml@main + with: + pr-number: ${{ github.event.pull_request.number }} + deploy-preview: true + shopify-store: example.myshopify.com + secrets: + SHOPIFY_CLI_THEME_TOKEN: ${{ secrets.SHOPIFY_CLI_THEME_TOKEN }} + + cleanup-preview: + if: github.event.action == 'closed' + uses: aligent/workflows/.github/workflows/shopify-theme-preview-cleanup.yml@main + with: + shopify-store: example.myshopify.com + pr-number: ${{ github.event.pull_request.number }} + secrets: + SHOPIFY_CLI_THEME_TOKEN: ${{ secrets.SHOPIFY_CLI_THEME_TOKEN }} +``` From 289b9a8e462b87bba1c876d578694c9399170f56 Mon Sep 17 00:00:00 2001 From: Daniel van der Ploeg Date: Tue, 14 Apr 2026 14:41:47 +0930 Subject: [PATCH 4/6] fix GSM-29: resolve security issues --- .github/workflows/shopify-theme-pr.yml | 6 ++++-- .github/workflows/shopify-theme-preview-cleanup.yml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/shopify-theme-pr.yml b/.github/workflows/shopify-theme-pr.yml index 67e8bf5..e77d688 100644 --- a/.github/workflows/shopify-theme-pr.yml +++ b/.github/workflows/shopify-theme-pr.yml @@ -95,7 +95,7 @@ jobs: if: inputs.deploy-preview runs-on: ubuntu-latest env: - SHOPIFY_CLI_THEME_TOKEN: ${{ secrets.SHOPIFY_CLI_THEME_TOKEN }} + SHOPIFY_CLI_THEME_TOKEN: ${{ secrets.SHOPIFY_CLI_THEME_TOKEN }} # zizmor: ignore[secrets-outside-env] reusable workflow; caller controls environment scoping SHOPIFY_FLAG_STORE: ${{ inputs.shopify-store }} PR_THEME_NAME: "PR-#${{ inputs.pr-number }}" steps: @@ -134,8 +134,10 @@ jobs: - name: Extract preview URL id: preview + env: + WORKING_DIRECTORY: ${{ inputs.working-directory }} run: | - url=$(jq -r '.theme.preview_url // empty' "${{ inputs.working-directory }}/push.json") + url=$(jq -r '.theme.preview_url // empty' "$WORKING_DIRECTORY/push.json") echo "url=$url" >> "$GITHUB_OUTPUT" - name: Comment preview link on PR diff --git a/.github/workflows/shopify-theme-preview-cleanup.yml b/.github/workflows/shopify-theme-preview-cleanup.yml index 3c27f7e..4bc4c3c 100644 --- a/.github/workflows/shopify-theme-preview-cleanup.yml +++ b/.github/workflows/shopify-theme-preview-cleanup.yml @@ -21,7 +21,7 @@ jobs: name: Cleanup preview runs-on: ubuntu-latest env: - SHOPIFY_CLI_THEME_TOKEN: ${{ secrets.SHOPIFY_CLI_THEME_TOKEN }} + SHOPIFY_CLI_THEME_TOKEN: ${{ secrets.SHOPIFY_CLI_THEME_TOKEN }} # zizmor: ignore[secrets-outside-env] reusable workflow; caller controls environment scoping SHOPIFY_FLAG_STORE: ${{ inputs.shopify-store }} PR_THEME_NAME: "PR-#${{ inputs.pr-number }}" steps: From 394d310c179e47f36c2cc78ff91bc39ff10b7506 Mon Sep 17 00:00:00 2001 From: Daniel van der Ploeg Date: Tue, 14 Apr 2026 14:43:10 +0930 Subject: [PATCH 5/6] chore GSM-29: reduce line length --- .github/workflows/shopify-theme-pr.yml | 3 ++- .github/workflows/shopify-theme-preview-cleanup.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/shopify-theme-pr.yml b/.github/workflows/shopify-theme-pr.yml index e77d688..a90bcf9 100644 --- a/.github/workflows/shopify-theme-pr.yml +++ b/.github/workflows/shopify-theme-pr.yml @@ -95,7 +95,8 @@ jobs: if: inputs.deploy-preview runs-on: ubuntu-latest env: - SHOPIFY_CLI_THEME_TOKEN: ${{ secrets.SHOPIFY_CLI_THEME_TOKEN }} # zizmor: ignore[secrets-outside-env] reusable workflow; caller controls environment scoping + # zizmor: ignore[secrets-outside-env] caller controls environment scoping + SHOPIFY_CLI_THEME_TOKEN: ${{ secrets.SHOPIFY_CLI_THEME_TOKEN }} SHOPIFY_FLAG_STORE: ${{ inputs.shopify-store }} PR_THEME_NAME: "PR-#${{ inputs.pr-number }}" steps: diff --git a/.github/workflows/shopify-theme-preview-cleanup.yml b/.github/workflows/shopify-theme-preview-cleanup.yml index 4bc4c3c..9d1221c 100644 --- a/.github/workflows/shopify-theme-preview-cleanup.yml +++ b/.github/workflows/shopify-theme-preview-cleanup.yml @@ -21,7 +21,8 @@ jobs: name: Cleanup preview runs-on: ubuntu-latest env: - SHOPIFY_CLI_THEME_TOKEN: ${{ secrets.SHOPIFY_CLI_THEME_TOKEN }} # zizmor: ignore[secrets-outside-env] reusable workflow; caller controls environment scoping + # zizmor: ignore[secrets-outside-env] caller controls environment scoping + SHOPIFY_CLI_THEME_TOKEN: ${{ secrets.SHOPIFY_CLI_THEME_TOKEN }} SHOPIFY_FLAG_STORE: ${{ inputs.shopify-store }} PR_THEME_NAME: "PR-#${{ inputs.pr-number }}" steps: From 646497f375cdad2068ca6db6523d4b240659e070 Mon Sep 17 00:00:00 2001 From: Daniel van der Ploeg Date: Fri, 17 Apr 2026 14:03:42 +0930 Subject: [PATCH 6/6] feat GSM-29: add npm caching for Shopify CLI install --- .github/workflows/shopify-theme-pr.yml | 12 ++++++++++++ .github/workflows/shopify-theme-preview-cleanup.yml | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/.github/workflows/shopify-theme-pr.yml b/.github/workflows/shopify-theme-pr.yml index a90bcf9..43a706d 100644 --- a/.github/workflows/shopify-theme-pr.yml +++ b/.github/workflows/shopify-theme-pr.yml @@ -46,6 +46,12 @@ jobs: with: node-version: 'lts/*' + - name: Cache npm + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: ~/.npm + key: ${{ runner.os }}-npm-shopify-cli + - name: Install Shopify CLI run: npm install -g @shopify/cli@latest @@ -121,6 +127,12 @@ jobs: with: node-version: 'lts/*' + - name: Cache npm + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: ~/.npm + key: ${{ runner.os }}-npm-shopify-cli + - name: Install Shopify CLI run: npm install -g @shopify/cli@latest diff --git a/.github/workflows/shopify-theme-preview-cleanup.yml b/.github/workflows/shopify-theme-preview-cleanup.yml index 9d1221c..84e5601 100644 --- a/.github/workflows/shopify-theme-preview-cleanup.yml +++ b/.github/workflows/shopify-theme-preview-cleanup.yml @@ -31,6 +31,12 @@ jobs: with: node-version: 'lts/*' + - name: Cache npm + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: ~/.npm + key: ${{ runner.os }}-npm-shopify-cli + - name: Install Shopify CLI run: npm install -g @shopify/cli@latest