├── README.md ├── scripts ├── requirements.txt ├── customer_segmentation.sh ├── high_inventory_discount.sh ├── nightly_validate.sh ├── enrich_products.py └── review_catalog.py ├── shopctl └── .shopconfig.yml └── .github └── workflows ├── actions └── setup-shopctl │ └── action.yml ├── pipeline.yml └── enrich-products.yml /README.md: -------------------------------------------------------------------------------- 1 | # ci-test 2 | -------------------------------------------------------------------------------- /scripts/requirements.txt: -------------------------------------------------------------------------------- 1 | openai 2 | -------------------------------------------------------------------------------- /shopctl/.shopconfig.yml: -------------------------------------------------------------------------------- 1 | ver: v0 2 | contexts: 3 | - alias: devauto1 4 | store: devauto1.myshopify.com 5 | currentContext: devauto1 6 | -------------------------------------------------------------------------------- /.github/workflows/actions/setup-shopctl/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup ShopCTL 2 | description: Installs Go and ShopCTL CLI 3 | runs: 4 | using: "composite" 5 | steps: 6 | - name: Set up Go 7 | uses: actions/setup-go@v5 8 | with: 9 | go-version: "1.24" 10 | 11 | - name: Install ShopCTL 12 | shell: bash 13 | run: | 14 | sudo apt-get update 15 | sudo apt-get install -y libx11-dev 16 | go install github.com/ankitpokhrel/shopctl/cmd/shopctl@main 17 | echo "$HOME/go/bin" >> "$GITHUB_PATH" 18 | -------------------------------------------------------------------------------- /scripts/customer_segmentation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | export TZ="Europe/Berlin" 5 | 6 | DAY_START=$(date -d '1 days ago' +%Y-%m-%d) 7 | 8 | ############################################################################### 9 | # 1. Get top 20 customers that spent more than $100 in the last 7 days and 10 | # has accepted marketing emails. 11 | ############################################################################### 12 | echo "🔍 Scanning customers updated since $DAY_START ..." 13 | customers=$(shopctl customer list --total-spent ">=0" \ 14 | --accepts-marketing --columns id,first_name,last_name,email,amount_spent \ 15 | --csv --no-headers --with-sensitive-data --limit 20) 16 | 17 | if [[ -z "$customers" ]]; then 18 | echo "🟢 No customers who spent more than \$100 since $DAY_START — nothing to do" 19 | exit 0 20 | fi 21 | 22 | echo "id,name,email,spent,proposed_discount_amount" > weekly_customer_discounts.csv 23 | 24 | ############################################################################### 25 | # 2. Propose a 30% discount amount if the spent >= 200. 26 | ############################################################################### 27 | while IFS=$',' read -r id fn ln email spent; do 28 | rate=0.20; (( $(echo "$spent >= 200" | bc) )) && rate=0.30 29 | coupon=$(awk -v s="$spent" -v r="$rate" 'BEGIN{print int((s*r)+0.999)}') 30 | echo "\"$fn $ln\",$email,$spent,$coupon" >> weekly_customer_discounts.csv 31 | done <<< "$customers" 32 | 33 | ############################################################################### 34 | # 3. Print the result. 35 | ############################################################################### 36 | if [[ $(wc -l < weekly_customer_discounts.csv) -le 1 ]]; then 37 | echo "✅ No discounts added for any customers" 38 | else 39 | cat weekly_customer_discounts.csv 40 | fi 41 | -------------------------------------------------------------------------------- /scripts/high_inventory_discount.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | export TZ="Europe/Berlin" 5 | 6 | DAY_START=$(date -d '1 day ago' +%Y-%m-%d) 7 | 8 | ############################################################################### 9 | # 1. Find products updated in the last 24 hours and has large inventory. 10 | ############################################################################### 11 | echo "🔍 Scanning products with high inventory since $DAY_START ..." 12 | products=$(shopctl product list "inventory_total:>=300" --updated=">=$DAY_START" --columns id,title --csv --no-headers) 13 | 14 | if [[ -z "$products" ]]; then 15 | echo "🟢 No high inventory updated products since $DAY_START — nothing to do" 16 | exit 0 17 | fi 18 | 19 | # Create CSV summary file 20 | echo "product_id,product_title,variant_id,old_price,new_price" > inventory_discounts.csv 21 | 22 | ############################################################################### 23 | # 2. Apply a 10% discount only if the resulting price is still ≥ cost × 1.15. 24 | ############################################################################### 25 | while IFS=',' read -r pid title; do 26 | variants=$(shopctl product variant list $pid --columns id,price,unit_cost --csv --no-headers) 27 | [[ -z "$variants" ]] && continue 28 | 29 | while IFS=',' read -r variant_id price unit_cost; do 30 | new_price=$(echo "scale=2; $price * 0.9" | bc) # 10% discount 31 | margin_ok=$(echo "$new_price >= ($unit_cost*1.15)" | bc) 32 | 33 | if [[ $margin_ok -eq 1 ]]; then 34 | shopctl product variant edit $pid --id $variant_id --price "$new_price" 35 | echo "$pid,\"$title\",$variant_id,$price,$new_price" >> inventory_discounts.csv 36 | fi 37 | done <<< "$variants" 38 | done <<< "$products" 39 | 40 | ############################################################################### 41 | # 3. Print the result. 42 | ############################################################################### 43 | if [[ $(wc -l < inventory_discounts.csv) -le 1 ]]; then 44 | echo "✅ No discounts added to any of the products" 45 | else 46 | cat inventory_discounts.csv 47 | fi 48 | 49 | -------------------------------------------------------------------------------- /scripts/nightly_validate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | export TZ="Europe/Berlin" 5 | 6 | DAY_START=$(date -d '1 day ago' +%Y-%m-%d) 7 | 8 | # Helper that appends a validation message to 9 | # the global `reason` variable. 10 | add_reason() { 11 | if [[ -z "$reason" ]]; then 12 | reason="$1" 13 | else 14 | reason+="; $1" 15 | fi 16 | } 17 | 18 | ############################################################################### 19 | # 1. Get 100 products that changed in the last 24h. 20 | ############################################################################### 21 | echo "🔍 Scanning products updated since $DAY_START ..." 22 | products=$(shopctl product list --columns id,status --updated ">=$DAY_START" --csv --no-headers --limit 100) 23 | 24 | if [[ -z "$products" ]]; then 25 | echo "🟢 No products updated since $DAY_START — nothing to validate" 26 | exit 0 27 | fi 28 | 29 | echo "product_id,variant_id,variant_title,sku,price,status,reason" > invalid_products.csv 30 | invalid=0 31 | 32 | ############################################################################### 33 | # 2. For each product, check variants for empty SKU OR price out of range. 34 | ############################################################################### 35 | while IFS=',' read -r id status; do 36 | pid="gid://shopify/Product/${id}" 37 | 38 | variants=$(shopctl product variant list "$pid" --columns id,title,sku,price --csv --no-headers) 39 | [[ -z "$variants" ]] && continue 40 | 41 | while IFS=',' read -r vid_raw title sku price; do 42 | vid="gid://shopify/ProductVariant/${vid_raw}" 43 | reason="" 44 | 45 | [[ -z "$sku" ]] && add_reason "Empty SKU" 46 | 47 | out_of_range=$(echo "$price < 25 || $price > 100" | bc || true) 48 | [[ $out_of_range -eq 1 ]] && add_reason "Price $price out of \$25–\$100" 49 | 50 | if [[ -n "$reason" ]]; then 51 | { 52 | printf '%s,%s,"%s",%s,%s,%s,%s\n' \ 53 | "$pid" "$vid" "$title" "$sku" "$price" "$status" "$reason" 54 | } >> invalid_products.csv 55 | invalid=$((invalid + 1)) 56 | fi 57 | done <<< "$variants" 58 | done <<< "$products" 59 | 60 | ############################################################################### 61 | # 3. Print the result. 62 | ############################################################################### 63 | if (( invalid )); then 64 | echo "::error::${invalid} invalid variant(s) found" 65 | cat invalid_products.csv 66 | exit 1 67 | else 68 | echo "✅ All checked variants passed" 69 | fi 70 | 71 | -------------------------------------------------------------------------------- /.github/workflows/pipeline.yml: -------------------------------------------------------------------------------- 1 | name: Discount + Validation Pipeline 2 | 3 | on: 4 | schedule: 5 | - cron: "0 1 * * *" # Every day at 1 AM UTC 6 | workflow_dispatch: 7 | 8 | jobs: 9 | validate-products: 10 | runs-on: ubuntu-latest 11 | env: 12 | SHOPIFY_ACCESS_TOKEN: ${{ secrets.SHOPIFY_ACCESS_TOKEN }} 13 | SHOPIFY_CONFIG_HOME: ${{ github.workspace }} 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup ShopCTL 19 | uses: ./.github/workflows/actions/setup-shopctl 20 | 21 | - name: Run validation script 22 | run: | 23 | chmod +x scripts/nightly_validate.sh 24 | ./scripts/nightly_validate.sh | tee validation.log 25 | 26 | - name: Upload log 27 | uses: actions/upload-artifact@v4 28 | with: 29 | name: validation-log 30 | path: | 31 | validation.log 32 | invalid_products.csv 33 | 34 | high-inventory-discount: 35 | runs-on: ubuntu-latest 36 | env: 37 | SHOPIFY_ACCESS_TOKEN: ${{ secrets.SHOPIFY_ACCESS_TOKEN }} 38 | steps: 39 | - name: Checkout repository 40 | uses: actions/checkout@v4 41 | 42 | - name: Setup ShopCTL 43 | uses: ./.github/workflows/actions/setup-shopctl 44 | 45 | - name: Run inventory discount script 46 | run: | 47 | chmod +x scripts/high_inventory_discount.sh 48 | ./scripts/high_inventory_discount.sh | tee inventory.log 49 | 50 | - name: Upload log 51 | uses: actions/upload-artifact@v4 52 | with: 53 | name: inventory-log 54 | path: | 55 | inventory.log 56 | inventory_discounts.csv 57 | 58 | customer-discount: 59 | runs-on: ubuntu-latest 60 | env: 61 | SHOPIFY_ACCESS_TOKEN: ${{ secrets.SHOPIFY_ACCESS_TOKEN }} 62 | steps: 63 | - name: Checkout repository 64 | uses: actions/checkout@v4 65 | 66 | - name: Setup ShopCTL 67 | uses: ./.github/workflows/actions/setup-shopctl 68 | 69 | - name: Run customer discount script 70 | run: | 71 | chmod +x scripts/customer_segmentation.sh 72 | ./scripts/customer_segmentation.sh | tee customer.log 73 | 74 | - name: Upload log 75 | uses: actions/upload-artifact@v4 76 | with: 77 | name: customer-log 78 | path: | 79 | customer.log 80 | weekly_customer_discounts.csv 81 | 82 | notify: 83 | runs-on: ubuntu-latest 84 | needs: [validate-products, high-inventory-discount, customer-discount] 85 | steps: 86 | - name: Download all logs 87 | uses: actions/download-artifact@v4 88 | with: 89 | path: logs 90 | 91 | - name: Print logs (debug) 92 | run: | 93 | cat logs/validation-log/validation.log 94 | cat logs/inventory-log/inventory.log 95 | cat logs/customer-log/customer.log 96 | -------------------------------------------------------------------------------- /scripts/enrich_products.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import json 4 | import csv 5 | import tarfile 6 | import tempfile 7 | 8 | from openai import OpenAI 9 | from time import sleep 10 | 11 | client = OpenAI() 12 | BATCH_SIZE = 15 13 | 14 | # --- Function Schema for GPT --- 15 | function_def = { 16 | "name": "enrich_products", 17 | "description": "Generate enriched product fields for a batch of Shopify products.", 18 | "parameters": { 19 | "type": "object", 20 | "properties": { 21 | "products": { 22 | "type": "array", 23 | "items": { 24 | "type": "object", 25 | "properties": { 26 | "product_id": {"type": "string"}, 27 | "new_title": {"type": "string"}, 28 | "seo_title": {"type": "string"}, 29 | "seo_description": {"type": "string"}, 30 | }, 31 | "required": ["product_id", "new_title", "seo_title", "seo_description"] 32 | } 33 | } 34 | }, 35 | "required": ["products"] 36 | } 37 | } 38 | 39 | # --- Extract product.json files --- 40 | def extract_products_from_tar(tar_path): 41 | temp_dir = tempfile.mkdtemp() 42 | with tarfile.open(tar_path, "r:gz") as tar: 43 | def safe_extract_filter(tarinfo, path): 44 | tarinfo.name = os.path.normpath(tarinfo.name).lstrip(os.sep) 45 | return tarinfo 46 | tar.extractall(path=temp_dir, filter=safe_extract_filter) 47 | 48 | product_dir = os.path.join(temp_dir, "products") 49 | if not os.path.isdir(product_dir): 50 | raise FileNotFoundError("products/ folder not found in archive") 51 | 52 | product_data = [] 53 | for folder in os.listdir(product_dir): 54 | path = os.path.join(product_dir, folder, "product.json") 55 | if os.path.isfile(path): 56 | with open(path, "r", encoding="utf-8") as f: 57 | try: 58 | product = json.load(f) 59 | product_data.append(product) 60 | except json.JSONDecodeError: 61 | print(f"⚠️ Skipping {folder}: invalid JSON") 62 | return product_data 63 | 64 | # --- Prompt builder (user content) --- 65 | def build_batch_prompt(batch): 66 | return f""" 67 | Enrich each product below by generating: 68 | - new_title: improved title (if needed) 69 | - seo_title: keyword-rich, short title for SEO 70 | - seo_description: 1–2 sentence summary for search engines 71 | 72 | Use the 'enrich_products' function to return a JSON array of enriched results. 73 | 74 | Input: 75 | {json.dumps([ 76 | { 77 | "product_id": str(p["id"]), 78 | "title": p.get("title", ""), 79 | "description": p.get("body_html", ""), 80 | "tags": p.get("tags", []), 81 | } for p in batch 82 | ], indent=2)} 83 | """ 84 | 85 | # --- GPT-4 Function Call API --- 86 | def enrich_batch(batch): 87 | prompt = build_batch_prompt(batch) 88 | 89 | try: 90 | response = client.chat.completions.create( 91 | model="gpt-4-0613", 92 | messages=[ 93 | {"role": "user", "content": prompt} 94 | ], 95 | tools=[{ 96 | "type": "function", 97 | "function": function_def 98 | }], 99 | tool_choice={ 100 | "type": "function", 101 | "function": {"name": "enrich_products"} 102 | }, 103 | ) 104 | 105 | args = json.loads(response.choices[0].message.tool_calls[0].function.arguments) 106 | return args["products"] 107 | 108 | except Exception as e: 109 | print(f"[ERROR] Failed to enrich batch: {e}") 110 | sleep(2) 111 | return [] 112 | 113 | # --- Main --- 114 | def main(tar_path, output_csv): 115 | print("📦 Reading archive:", tar_path) 116 | products = extract_products_from_tar(tar_path) 117 | 118 | enriched_rows = [] 119 | for i in range(0, len(products), BATCH_SIZE): 120 | batch = products[i:i + BATCH_SIZE] 121 | print(f"✨ Enriching products {i+1} to {i+len(batch)}...") 122 | enriched_rows.extend(enrich_batch(batch)) 123 | 124 | print(f"💾 Writing enriched data to: {output_csv}") 125 | with open(output_csv, "w", newline="", encoding="utf-8") as f: 126 | writer = csv.DictWriter(f, fieldnames=[ 127 | "product_id", "new_title", "seo_title", "seo_description" 128 | ]) 129 | writer.writeheader() 130 | writer.writerows(enriched_rows) 131 | 132 | print("✅ Done. Enriched", len(enriched_rows), "products.") 133 | 134 | if __name__ == "__main__": 135 | if len(sys.argv) != 3: 136 | print("Usage: enrich_products.py ") 137 | sys.exit(1) 138 | main(sys.argv[1], sys.argv[2]) 139 | -------------------------------------------------------------------------------- /scripts/review_catalog.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import json 4 | import tarfile 5 | import tempfile 6 | from openai import OpenAI 7 | 8 | client = OpenAI() 9 | 10 | # --- Extract lightweight product info for review --- 11 | def extract_reviewable_products(tar_path, max_items=30): 12 | temp_dir = tempfile.mkdtemp() 13 | with tarfile.open(tar_path, "r:gz") as tar: 14 | def safe_extract_filter(tarinfo, path): 15 | tarinfo.name = os.path.normpath(tarinfo.name).lstrip(os.sep) 16 | return tarinfo 17 | tar.extractall(path=temp_dir, filter=safe_extract_filter) 18 | 19 | product_dir = os.path.join(temp_dir, "products") 20 | products = [] 21 | 22 | for folder in os.listdir(product_dir): 23 | product_path = os.path.join(product_dir, folder, "product.json") 24 | variant_path = os.path.join(product_dir, folder, "product_variants.json") 25 | 26 | if os.path.isfile(product_path): 27 | with open(product_path, "r", encoding="utf-8") as f: 28 | try: 29 | product = json.load(f) 30 | except json.JSONDecodeError: 31 | continue 32 | 33 | product_entry = { 34 | "id": str(product.get("id")), 35 | "productType": product.get("productType", ""), 36 | "tags": product.get("tags", []), 37 | "status": product.get("status", ""), 38 | "publishedScope": product.get("publishedScope", ""), 39 | "vendor": product.get("vendor", ""), 40 | "mediaCount": product.get("mediaCount", 0), 41 | "variantsCount": product.get("variantsCount", 0), 42 | "variants": [] 43 | } 44 | 45 | if os.path.isfile(variant_path): 46 | with open(variant_path, "r", encoding="utf-8") as vf: 47 | variant_data = json.load(vf) 48 | variants = variant_data.get("variants", {}).get("nodes", []) 49 | 50 | for v in variants: 51 | print(v) 52 | variant = { 53 | "title": v.get("title", ""), 54 | "sku": v.get("sku", ""), 55 | "price": v.get("price", ""), 56 | "inventoryQuantity": v.get("inventoryQuantity", 0), 57 | "inventoryPolicy": v.get("inventoryPolicy", ""), 58 | "requiresShipping": v.get("requiresShipping", True), 59 | "taxable": v.get("taxable", True), 60 | "optionValues": v.get("optionValues", []), 61 | "availableForSale": v.get("availableForSale", False), 62 | "sellableOnlineQuantity": v.get("sellableOnlineQuantity", 0) 63 | } 64 | product_entry["variants"].append(variant) 65 | 66 | products.append(product_entry) 67 | 68 | if len(products) >= max_items: 69 | break 70 | 71 | return products 72 | 73 | # --- Send data to GPT for catalog insights --- 74 | def generate_catalog_review(products): 75 | prompt = f""" 76 | You are an expert eCommerce catalog auditor. The product titles and descriptions have already been optimized, so exclude those. 77 | 78 | Please review the following product catalog sample and identify: 79 | 1. Issues or inconsistencies in tags, product types, or variants 80 | 2. Missing or inconsistent inventory information 81 | 3. Gaps in product configuration or variant structure 82 | 4. Duplicate or overly similar products 83 | 5. General recommendations to improve catalog quality and completeness 84 | 85 | Respond in clear, concise Markdown. 86 | 87 | Sample products: 88 | {json.dumps(products, indent=2)} 89 | """ 90 | 91 | response = client.chat.completions.create( 92 | model="gpt-4", 93 | messages=[{"role": "user", "content": prompt}], 94 | temperature=0.7 95 | ) 96 | 97 | return response.choices[0].message.content.strip() 98 | 99 | # --- Main entry --- 100 | def main(tar_path, output_md): 101 | print("📦 Extracting reviewable product data...") 102 | products = extract_reviewable_products(tar_path) 103 | 104 | print(f"🧠 Generating catalog review for {len(products)} products...") 105 | summary = generate_catalog_review(products) 106 | 107 | with open(output_md, "w", encoding="utf-8") as f: 108 | f.write(summary) 109 | 110 | print("✅ Catalog review saved to:", output_md) 111 | 112 | if __name__ == "__main__": 113 | if len(sys.argv) != 3: 114 | print("Usage: review_products.py ") 115 | sys.exit(1) 116 | main(sys.argv[1], sys.argv[2]) 117 | -------------------------------------------------------------------------------- /.github/workflows/enrich-products.yml: -------------------------------------------------------------------------------- 1 | name: Shopify Product Enrichment 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | export-products: 8 | runs-on: ubuntu-latest 9 | env: 10 | SHOPIFY_ACCESS_TOKEN: ${{ secrets.SHOPIFY_ACCESS_TOKEN }} 11 | SHOPIFY_CONFIG_HOME: ${{ github.workspace }} 12 | outputs: 13 | has-data: ${{ steps.check.outputs.has_data }} 14 | 15 | steps: 16 | - name: Checkout repo 17 | uses: actions/checkout@v3 18 | 19 | - name: Setup ShopCTL 20 | uses: ./.github/workflows/actions/setup-shopctl 21 | 22 | - name: Export products 23 | run: | 24 | mkdir -p data 25 | shopctl export -r product -o data/ -n latest_products -vvv 26 | 27 | - name: Check if export has data 28 | id: check 29 | run: | 30 | if [ -s data/latest_products.tar.gz ]; then 31 | echo "has_data=true" >> "$GITHUB_OUTPUT" 32 | else 33 | echo "has_data=false" >> "$GITHUB_OUTPUT" 34 | echo "No products found to process" 35 | fi 36 | 37 | - name: Upload exported products 38 | if: steps.check.outputs.has_data == 'true' 39 | uses: actions/upload-artifact@v4 40 | with: 41 | name: exported-products 42 | path: data/latest_products.tar.gz 43 | 44 | review-catalog: 45 | needs: export-products 46 | runs-on: ubuntu-latest 47 | env: 48 | OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} 49 | 50 | steps: 51 | - name: Checkout repo 52 | uses: actions/checkout@v3 53 | 54 | - name: Download product export 55 | uses: actions/download-artifact@v4 56 | with: 57 | name: exported-products 58 | path: data/ 59 | 60 | - name: Set up Python 61 | uses: actions/setup-python@v5 62 | with: 63 | python-version: "3.13" 64 | 65 | - name: Install dependencies 66 | run: pip install openai 67 | 68 | - name: Run catalog review script 69 | run: | 70 | python scripts/review_catalog.py \ 71 | data/latest_products.tar.gz \ 72 | data/review_summary.md 73 | 74 | - name: Upload catalog summary 75 | uses: actions/upload-artifact@v4 76 | with: 77 | name: catalog-review-summary 78 | path: data/review_summary.md 79 | 80 | - name: Final summary 81 | run: echo "✅ Shopify product catalog review completed!" 82 | 83 | enrich-products: 84 | needs: export-products 85 | if: ${{ always() && needs.export-products.outputs.has-data == 'true' }} 86 | runs-on: ubuntu-latest 87 | env: 88 | OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} 89 | 90 | steps: 91 | - name: Checkout repo 92 | uses: actions/checkout@v3 93 | 94 | - name: Download exported products 95 | uses: actions/download-artifact@v4 96 | with: 97 | name: exported-products 98 | path: data/ 99 | 100 | - name: Set up Python 101 | uses: actions/setup-python@v5 102 | with: 103 | python-version: "3.13" 104 | 105 | - name: Install dependencies 106 | run: pip install -r scripts/requirements.txt 107 | 108 | - name: Run enrichment script 109 | run: | 110 | python scripts/enrich_products.py \ 111 | data/latest_products.tar.gz \ 112 | data/enriched_products.csv 113 | 114 | - name: Upload enriched data 115 | uses: actions/upload-artifact@v4 116 | with: 117 | name: enriched-products 118 | path: data/enriched_products.csv 119 | 120 | update-products: 121 | needs: enrich-products 122 | runs-on: ubuntu-latest 123 | env: 124 | SHOPIFY_ACCESS_TOKEN: ${{ secrets.SHOPIFY_ACCESS_TOKEN }} 125 | SHOPIFY_CONFIG_HOME: ${{ github.workspace }} 126 | 127 | steps: 128 | - name: Checkout repo 129 | uses: actions/checkout@v3 130 | 131 | - name: Setup ShopCTL 132 | uses: ./.github/workflows/actions/setup-shopctl 133 | 134 | - name: Download enriched products 135 | uses: actions/download-artifact@v4 136 | with: 137 | name: enriched-products 138 | path: data/ 139 | 140 | - name: Apply updates using shopctl 141 | run: | 142 | mkdir -p logs 143 | touch logs/audit.txt 144 | 145 | while IFS=, read -r pid new_title seo_title seo_desc; do 146 | # Strip leading/trailing quotes 147 | seo_desc="${seo_desc%\"}" 148 | seo_desc="${seo_desc#\"}" 149 | 150 | if output=$(shopctl product update "$pid" \ 151 | --title "$new_title" \ 152 | --seo-title "$seo_title" \ 153 | --seo-desc "$seo_desc" 2>&1); then 154 | echo "$pid,success" >> logs/audit.txt 155 | else 156 | sanitized_error=$(echo "$output" | tr '\n' ' ' | sed 's/,/ /g') 157 | echo "$pid,failure,$sanitized_error" >> logs/audit.txt 158 | fi 159 | done < <(tail -n +2 data/enriched_products.csv) 160 | 161 | - name: Upload audit log 162 | uses: actions/upload-artifact@v4 163 | with: 164 | name: product-audit-log 165 | path: logs/audit.txt 166 | 167 | - name: Final summary 168 | run: echo "✅ Shopify product enrichment and updates completed!" 169 | 170 | notify: 171 | needs: [review-catalog, update-products] 172 | runs-on: ubuntu-latest 173 | 174 | steps: 175 | - name: Download audit log 176 | uses: actions/download-artifact@v4 177 | with: 178 | name: product-audit-log 179 | path: logs/ 180 | 181 | - name: Download catalog review 182 | uses: actions/download-artifact@v4 183 | with: 184 | name: catalog-review-summary 185 | path: data/ 186 | 187 | - name: Print audit summary 188 | run: | 189 | ls -lah logs/ 190 | ls -lah data/ 191 | echo "🧾 Shopify Product Update Audit" 192 | echo "-------------------------------" 193 | 194 | total=$(wc -l < logs/audit.txt) 195 | updated=$(grep -c ',success' logs/audit.txt || true) 196 | failed=$(grep -c ',failure' logs/audit.txt || true) 197 | 198 | echo "✅ Success: $updated" 199 | echo "❌ Failed: $failed" 200 | echo "📦 Total Processed: $total" 201 | echo "" 202 | echo "📋 Detailed Audit:" 203 | cat logs/audit.txt 204 | 205 | - name: Print catalog review summary 206 | run: | 207 | echo "" 208 | echo "🧠 Catalog Review Summary" 209 | echo "-------------------------" 210 | cat data/review_summary.md 211 | --------------------------------------------------------------------------------