From c52201e359ce9f1d207a15e3ceb865fabde2904a Mon Sep 17 00:00:00 2001 From: Gray Gilmore Date: Wed, 11 Mar 2026 10:49:20 -0700 Subject: [PATCH 1/2] Capture curl exit code before re-enabling set -e in api_request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The exit code was captured after set -e, meaning $? always reflected the exit status of 'set -e' itself (always 0) rather than curl's exit code. This made the HTTP error check dead code — a failed curl would never trigger the error log. Move 'local exit_code="$?"' before 'set -e' so it captures curl's actual exit status, matching the correct pattern already used in fetch_access_token. --- entrypoint.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index 7c4b2a7..e17a8d9 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -74,9 +74,8 @@ api_request() { 1> "$out" \ 2> "$err" fi - set -e - local exit_code="$?" + set -e local errors="$(cat "$out" | jq '.errors')" if [[ $exit_code != '0' ]]; then From 98d4917c1de11179fd0603b84994ea0823da411f Mon Sep 17 00:00:00 2001 From: Gray Gilmore Date: Wed, 11 Mar 2026 10:55:04 -0700 Subject: [PATCH 2/2] Add Dev Dashboard app authentication via client_id/client_secret As of January 2026, Shopify no longer allows creating new custom apps. New apps are created through the Dev Dashboard and authenticated with a client_id + client_secret, which are exchanged for a short-lived access token via the client_credentials OAuth grant. Changes: - action.yml: add client_id and client_secret inputs; mark access_token as legacy - entrypoint.sh: add fetch_access_token() that POSTs to /admin/oauth/access_token; replace the static auth block with a 4-branch resolver (client_id+secret preferred, partial-pair error, legacy access_token fallback, no-auth error); use ${VAR:-} syntax throughout to be safe under set -u - README.md: restructure auth docs with Dev Dashboard as the recommended path and legacy custom app as the secondary path; update primary usage example --- README.md | 61 ++++++++++++++++++++++++++++++++++++++++++--------- action.yml | 10 +++++++-- entrypoint.sh | 59 +++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 114 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index d9c13f8..e9286e0 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ jobs: uses: shopify/lighthouse-ci-action@v1 with: store: ${{ secrets.SHOP_STORE }} - access_token: ${{ secrets.SHOP_ACCESS_TOKEN }} + client_id: ${{ secrets.SHOP_CLIENT_ID }} + client_secret: ${{ secrets.SHOP_CLIENT_SECRET }} lhci_github_app_token: ${{ secrets.LHCI_GITHUB_APP_TOKEN }} lhci_min_score_performance: 0.9 lhci_min_score_accessibility: 0.9 @@ -32,7 +33,33 @@ jobs: ## Authentication -Authentication is done with [Custom App access tokens](https://shopify.dev/apps/auth/admin-app-access-tokens). +### Dev Dashboard App (recommended — required for apps created after Jan 2026) + +1. [Create an app via the Shopify Dev Dashboard](https://shopify.dev/docs/apps/build/dev-dashboard/create-apps-using-dev-dashboard). +2. When creating the app version, configure these required access scopes: + - `read_products` + - `write_themes` +3. Install the app on your store. +4. Copy the `client_id` and `client_secret` from the app credentials. +5. Add the following to your repository's [GitHub Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository): + - `SHOP_CLIENT_ID`: the client ID + - `SHOP_CLIENT_SECRET`: the client secret + - `SHOP_STORE`: Shopify store `.myshopify.com` URL + +Tokens are fetched automatically at the start of each action run and are valid for 24 hours — well beyond the duration of a typical run. + +```yml +- uses: shopify/lighthouse-ci-action@v1 + with: + store: ${{ secrets.SHOP_STORE }} + client_id: ${{ secrets.SHOP_CLIENT_ID }} + client_secret: ${{ secrets.SHOP_CLIENT_SECRET }} +``` + +### Legacy Custom App (for apps created before Jan 2026) + +> [!IMPORTANT] +> As of January 1, 2026 Shopify no longer allows creating new custom apps. Existing custom apps continue to work with this method. 1. [Create the app](https://help.shopify.com/en/manual/apps/custom-apps#create-and-install-a-custom-app). 2. Click the `Configure Admin API Scopes` button. @@ -46,19 +73,33 @@ Authentication is done with [Custom App access tokens](https://shopify.dev/apps/ - `SHOP_ACCESS_TOKEN`: the Admin API access token - `SHOP_STORE`: Shopify store `.myshopify.com` URL +```yml +- uses: shopify/lighthouse-ci-action@v1 + with: + store: ${{ secrets.SHOP_STORE }} + access_token: ${{ secrets.SHOP_ACCESS_TOKEN }} +``` + ## Configuration The `shopify/lighthouse-ci-action` accepts the following arguments: -* `access_token` - (required) see [Authentication](#authentication) +**Authentication (one method required):** +* `client_id` - Client ID for a Dev Dashboard app (use with `client_secret`) +* `client_secret` - Client secret for a Dev Dashboard app (use with `client_id`) +* `access_token` - Legacy custom app access token (for apps created before Jan 2026) + +**Store:** * `store` - (required) Shopify store Admin URL, e.g. `my-store.myshopify.com`. -* `password` - (optional) For password protected shops -* `product_handle` - (optional) Product handle to run the product page Lighthouse run on. Defaults to the first product. -* `theme_root` - (optional) The root folder for the theme files that will be uploaded. Defaults to `.` -* `collection_handle` - (optional) Collection handle to run the product page Lighthouse run on. Defaults to the first collection. -* `pull_theme` - (optional) The ID or name of a theme from which the settings and JSON templates should be used. If not provided Lighthouse will be run against the theme's default settings. -* `lhci_min_score_performance` - (optional, default: 0.6) Minimum performance score for a passed audit (must be between 0 and 1). -* `lhci_min_score_accessibility` - (optional, default: 0.9) Minimum accessibility score for a passed audit + +**Optional:** +* `password` - For password protected shops +* `product_handle` - Product handle to run the product page Lighthouse run on. Defaults to the first product. +* `theme_root` - The root folder for the theme files that will be uploaded. Defaults to `.` +* `collection_handle` - Collection handle to run the collection page Lighthouse run on. Defaults to the first collection. +* `pull_theme` - The ID or name of a theme from which the settings and JSON templates should be used. If not provided Lighthouse will be run against the theme's default settings. +* `lhci_min_score_performance` - (default: 0.6) Minimum performance score for a passed audit (must be between 0 and 1). +* `lhci_min_score_accessibility` - (default: 0.9) Minimum accessibility score for a passed audit For the GitHub Status Checks on PR. One of the two arguments is required: diff --git a/action.yml b/action.yml index 8f4ce44..de9a310 100644 --- a/action.yml +++ b/action.yml @@ -6,9 +6,15 @@ branding: description: 'Run Lighthouse CI on Shopify themes directly from GitHub' inputs: access_token: - description: 'Custom app access token' + description: 'Legacy custom app access token (for apps created before Jan 2026)' # required: false because you can still authenticate with private - # app credentials + # app credentials, or with client_id/client_secret + required: false + client_id: + description: 'Client ID for Dev Dashboard app (use with client_secret instead of access_token)' + required: false + client_secret: + description: 'Client secret for Dev Dashboard app (use with client_id instead of access_token)' required: false store: description: '.myshopify.com URL' diff --git a/entrypoint.sh b/entrypoint.sh index e17a8d9..b200a83 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -19,7 +19,8 @@ [[ -n "$INPUT_PULL_THEME" ]] && export SHOP_PULL_THEME="$INPUT_PULL_THEME" # Authentication creds -export SHOP_ACCESS_TOKEN="$INPUT_ACCESS_TOKEN" +[[ -n "$INPUT_CLIENT_ID" ]] && export SHOP_CLIENT_ID="$INPUT_CLIENT_ID" +[[ -n "$INPUT_CLIENT_SECRET" ]] && export SHOP_CLIENT_SECRET="$INPUT_CLIENT_SECRET" # Authentication creds (deprecated) [[ -n "$INPUT_APP_ID" ]] && export SHOP_APP_ID="$INPUT_APP_ID" @@ -92,6 +93,42 @@ api_request() { cat "$out" } +fetch_access_token() { + local token_response_file + token_response_file="$(mktemp)" + local token_error_file + token_error_file="$(mktemp)" + + # Redirect stderr to prevent client_secret from appearing in logs on failure. + # set +e mirrors the pattern in api_request — prevents set -e from killing + # the script before our custom error message can run. + set +e + curl -sS -f -X POST \ + "https://${SHOPIFY_SHOP}/admin/oauth/access_token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "grant_type=client_credentials" \ + --data-urlencode "client_id=${SHOP_CLIENT_ID}" \ + --data-urlencode "client_secret=${SHOP_CLIENT_SECRET}" \ + 1> "$token_response_file" \ + 2> "$token_error_file" + local exit_code="$?" + set -e + + if [[ $exit_code != '0' ]]; then + log "Failed to fetch access token. Verify your client_id and client_secret are correct and that your Dev Dashboard app has the required scopes: read_products, write_themes." + return 1 + fi + + local token + token="$(jq -r '.access_token' "$token_response_file")" + if [[ -z "$token" || "$token" == "null" ]]; then + log "Failed to fetch access token: response did not contain a valid token. Verify your Dev Dashboard app has the required scopes: read_products, write_themes." + return 1 + fi + + echo "$token" +} + cleanup() { if [[ -n "${theme+x}" ]]; then step "Disposing development theme" @@ -124,12 +161,26 @@ YAML export CI=1 export SHOPIFY_SHOP="${SHOP_STORE#*(https://|http://)}" -if [[ -n "$SHOP_ACCESS_TOKEN" ]]; then - export SHOPIFY_PASSWORD="$SHOP_ACCESS_TOKEN" +# Resolve access token — prefer client_id/client_secret (Dev Dashboard apps), +# fall back to static access_token (legacy custom apps) +if [[ -n "${SHOP_CLIENT_ID:-}" && -n "${SHOP_CLIENT_SECRET:-}" ]]; then + if [[ -n "${INPUT_ACCESS_TOKEN:-}" ]]; then + log "WARNING: Both access_token and client_id/client_secret were provided. Using client_id/client_secret." + fi + SHOP_ACCESS_TOKEN="$(fetch_access_token)" + export SHOP_ACCESS_TOKEN +elif [[ -n "${SHOP_CLIENT_ID:-}" || -n "${SHOP_CLIENT_SECRET:-}" ]]; then + log "Error: Both client_id and client_secret are required for Dev Dashboard app authentication." + exit 1 +elif [[ -n "${INPUT_ACCESS_TOKEN:-}" ]]; then + export SHOP_ACCESS_TOKEN="$INPUT_ACCESS_TOKEN" else - export SHOPIFY_PASSWORD="$SHOP_APP_PASSWORD" + log "Error: Authentication required. Provide either (client_id + client_secret) for a Dev Dashboard app, or access_token for a legacy custom app." + exit 1 fi +export SHOPIFY_PASSWORD="$SHOP_ACCESS_TOKEN" + export SHOPIFY_FLAG_STORE="$SHOPIFY_SHOP" export SHOPIFY_CLI_THEME_TOKEN="$SHOPIFY_PASSWORD" export SHOPIFY_CLI_TTY=0