Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions .github/workflows/ci-staging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: CI / PR Preview

on:
pull_request:
branches:
- staging

jobs:
build-and-preview:
runs-on: ubuntu-latest
env:
SHOPIFY_STORE: ${{ secrets.SHOPIFY_STORE }}
SHOPIFY_ADMIN_ACCESS_TOKEN: ${{ secrets.SHOPIFY_ADMIN_ACCESS_TOKEN }}
SHOPIFY_API_VERSION: ${{ secrets.SHOPIFY_API_VERSION }}
PRODUCT_HANDLE: ${{ secrets.PRODUCT_HANDLE }}
steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'

- name: Install dependencies
run: npm ci

- name: Validate Shopify secrets
id: validate
run: |
missing=""
for var in SHOPIFY_STORE SHOPIFY_ADMIN_ACCESS_TOKEN SHOPIFY_API_VERSION; do
if [ -z "${!var}" ]; then
missing="$missing\n- $var"
fi
done
if [ -n "$missing" ]; then
echo "missing_secrets<<EOF" >> $GITHUB_OUTPUT
echo "$missing" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
fi
shell: bash

- name: Comment missing secrets on PR
if: steps.validate.outputs.missing_secrets
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
body="🚨 Missing required Shopify secrets in CI workflow:$\n${{ steps.validate.outputs.missing_secrets }}$\nCreate these secrets under Settings → Secrets → Actions."
gh pr comment ${{ github.event.pull_request.number }} --body "$body"
exit 1

- name: Create unpublished theme and upload assets
if: ${{ !steps.validate.outputs.missing_secrets }}
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
STORE: ${{ env.SHOPIFY_STORE }}
TOKEN: ${{ env.SHOPIFY_ADMIN_ACCESS_TOKEN }}
API_VERSION: ${{ env.SHOPIFY_API_VERSION }}
run: |
set -e
THEME_NAME="pr-${PR_NUMBER}"
CREATE_RES=$(curl -s -X POST "https://${STORE}.myshopify.com/admin/api/${API_VERSION}/themes.json" \
-H "X-Shopify-Access-Token: ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"theme\":{\"name\":\"${THEME_NAME}\",\"role\":\"unpublished\"}}")
THEME_ID=$(echo "$CREATE_RES" | jq -r '.theme.id')
if [ -z "$THEME_ID" ] || [ "$THEME_ID" = "null" ]; then
echo "Failed to create theme: $CREATE_RES"
exit 1
fi
echo "Created theme $THEME_ID"
echo "THEME_ID=$THEME_ID" >> $GITHUB_ENV
# Upload all files to the theme
for file in $(git ls-files); do
key="$file"
# encode file contents in base64 (no line breaks)
base64content=$(base64 -w 0 "$file")
curl -s -X PUT "https://${STORE}.myshopify.com/admin/api/${API_VERSION}/themes/${THEME_ID}/assets.json" \
-H "X-Shopify-Access-Token: ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"asset\":{\"key\":\"${key}\",\"attachment\":\"${base64content}\"}}" > /dev/null
done
echo "PREVIEW_BASE_URL=https://${STORE}.myshopify.com/?preview_theme_id=${THEME_ID}" >> $GITHUB_ENV
shell: bash

- name: Run visual tests
if: ${{ !steps.validate.outputs.missing_secrets }}
env:
PREVIEW_BASE_URL: ${{ env.PREVIEW_BASE_URL }}
run: |
npm run test:visual

- name: Post preview comment
if: ${{ !steps.validate.outputs.missing_secrets }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
body="🔍 **Preview your changes**: ${{ env.PREVIEW_BASE_URL }}"
gh pr comment ${{ github.event.pull_request.number }} --body "$body"
70 changes: 70 additions & 0 deletions .github/workflows/deploy-staging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Deploy to Staging Theme

on:
push:
branches:
- staging

jobs:
deploy:
runs-on: ubuntu-latest
env:
SHOPIFY_STORE: ${{ secrets.SHOPIFY_STORE }}
SHOPIFY_ADMIN_ACCESS_TOKEN: ${{ secrets.SHOPIFY_ADMIN_ACCESS_TOKEN }}
SHOPIFY_API_VERSION: ${{ secrets.SHOPIFY_API_VERSION }}
SHOPIFY_STAGING_THEME_ID: ${{ secrets.SHOPIFY_STAGING_THEME_ID }}
steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'

- name: Install dependencies
run: npm ci

- name: Build (optional)
run: npm run build --if-present

- name: Validate Shopify secrets
id: validate
run: |
missing=""
for var in SHOPIFY_STORE SHOPIFY_ADMIN_ACCESS_TOKEN SHOPIFY_API_VERSION SHOPIFY_STAGING_THEME_ID; do
if [ -z "${!var}" ]; then
missing="$missing\n- $var"
fi
done
if [ -n "$missing" ]; then
echo "missing_secrets<<EOF" >> $GITHUB_OUTPUT
echo "$missing" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
fi
shell: bash

- name: Exit if secrets missing
if: steps.validate.outputs.missing_secrets
run: |
echo "::error::Missing Shopify secrets:${{ steps.validate.outputs.missing_secrets }}"
exit 1

- name: Upload assets to staging theme
if: ${{ !steps.validate.outputs.missing_secrets }}
env:
STORE: ${{ env.SHOPIFY_STORE }}
TOKEN: ${{ env.SHOPIFY_ADMIN_ACCESS_TOKEN }}
API_VERSION: ${{ env.SHOPIFY_API_VERSION }}
THEME_ID: ${{ env.SHOPIFY_STAGING_THEME_ID }}
run: |
set -e
for file in $(git ls-files); do
key="$file"
base64content=$(base64 -w 0 "$file")
curl -s -X PUT "https://${STORE}.myshopify.com/admin/api/${API_VERSION}/themes/${THEME_ID}/assets.json" \
-H "X-Shopify-Access-Token: ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"asset\":{\"key\":\"${key}\",\"attachment\":\"${base64content}\"}}" > /dev/null
done
echo "Deployed to staging theme ${THEME_ID}"
51 changes: 51 additions & 0 deletions .github/workflows/pr-close.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Cleanup PR Preview Theme

on:
pull_request:
types: [closed]

jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Validate secrets
id: validate
env:
SHOPIFY_STORE: ${{ secrets.SHOPIFY_STORE }}
SHOPIFY_ADMIN_ACCESS_TOKEN: ${{ secrets.SHOPIFY_ADMIN_ACCESS_TOKEN }}
SHOPIFY_API_VERSION: ${{ secrets.SHOPIFY_API_VERSION }}
run: |
missing=""
for var in SHOPIFY_STORE SHOPIFY_ADMIN_ACCESS_TOKEN SHOPIFY_API_VERSION; do
if [ -z "${!var}" ]; then
missing="$missing\n- $var"
fi
done
if [ -n "$missing" ]; then
echo "missing_secrets<<EOF" >> $GITHUB_OUTPUT
echo "$missing" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
fi
- name: Exit early if secrets missing
if: steps.validate.outputs.missing_secrets
run: echo "Skipping theme deletion due to missing Shopify secrets."
- name: Delete preview theme
if: ${{ !steps.validate.outputs.missing_secrets }}
env:
STORE: ${{ secrets.SHOPIFY_STORE }}
TOKEN: ${{ secrets.SHOPIFY_ADMIN_ACCESS_TOKEN }}
API_VERSION: ${{ secrets.SHOPIFY_API_VERSION }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
set -e
# fetch all themes and filter for the PR theme name
themes_json=$(curl -s -X GET "https://${STORE}.myshopify.com/admin/api/${API_VERSION}/themes.json" \
-H "X-Shopify-Access-Token: ${TOKEN}")
theme_id=$(echo "$themes_json" | jq -r ".themes[] | select(.name==\"pr-${PR_NUMBER}\") | .id")
if [ -n "$theme_id" ] && [ "$theme_id" != "null" ]; then
curl -s -X DELETE "https://${STORE}.myshopify.com/admin/api/${API_VERSION}/themes/${theme_id}.json" \
-H "X-Shopify-Access-Token: ${TOKEN}"
echo "Deleted preview theme ${theme_id}"
else
echo "No preview theme to delete for PR ${PR_NUMBER}."
fi
26 changes: 26 additions & 0 deletions .github/workflows/setup-metaobjects.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Setup Metaobjects

on:
workflow_dispatch:

jobs:
metaobjects:
runs-on: ubuntu-latest
env:
SHOPIFY_STORE: ${{ secrets.SHOPIFY_STORE }}
SHOPIFY_ADMIN_ACCESS_TOKEN: ${{ secrets.SHOPIFY_ADMIN_ACCESS_TOKEN }}
SHOPIFY_API_VERSION: ${{ secrets.SHOPIFY_API_VERSION }}
steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'

- name: Install dependencies
run: npm ci

- name: Ensure metaobjects and metafields
run: npm run shopify:defs
47 changes: 47 additions & 0 deletions assets/ui.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* QFlex Utility Styles
*
* This stylesheet contains reusable utility classes for the QFlex theme. All
* bespoke styling previously defined inline has been extracted here to
* improve maintainability and enable composition. Feel free to extend
* these rules or incorporate them into a larger Tailwind-esque framework.
*/

.qflex-hero .eyebrow,
.qflex-pdp .eyebrow {
font-size: var(--font-size-small, 0.875rem);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.25rem;
}

.qflex-hero .rating,
.qflex-pdp .rating {
font-weight: 600;
margin-bottom: 0.5rem;
}

.qflex-hero .review-count,
.qflex-pdp .review-count {
margin-left: 0.25rem;
opacity: 0.7;
}

.qflex-hero .usp-pills,
.qflex-pdp .usp-pills {
list-style: none;
padding: 0;
margin: 0;
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}

.qflex-hero .usp-pills li,
.qflex-pdp .usp-pills li {
background-color: rgba(var(--color-background-contrast, 0,0,0), 0.08);
padding: 0.25rem 0.5rem;
border-radius: 999px;
font-size: 0.75rem;
line-height: 1;
}
Loading
Loading