diff --git a/scripts/setup_storefront_env b/scripts/setup_storefront_env index ce03864e2..73e2d9962 100755 --- a/scripts/setup_storefront_env +++ b/scripts/setup_storefront_env @@ -5,6 +5,7 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ROOT_ENV="${ROOT_DIR}/.env" +ROOT_ENV_LOCAL="${ROOT_DIR}/.env.local" ANDROID_ENV="${ROOT_DIR}/platforms/android/samples/CheckoutKitAndroidDemo/.env" SWIFT_DEMO_XCCONFIG="${ROOT_DIR}/platforms/swift/Samples/CheckoutKitSwiftDemo/Storefront.xcconfig" SWIFT_ACCELERATED_XCCONFIG="${ROOT_DIR}/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/Storefront.xcconfig" @@ -94,15 +95,37 @@ read_env_value() { sub(/[[:space:]]*$/, "", candidate) if (candidate == key) { value = substr(line, index(line, "=") + 1) - print value - exit + found = 1 } } + END { if (found) print value } ' "$file")" strip_outer_quotes "$raw_value" } +env_keys() { + local file="$1" + + [[ -f "$file" ]] || return 0 + + awk ' + /^[[:space:]]*#/ || /^[[:space:]]*\/\// || /^[[:space:]]*$/ { next } + $0 !~ /=/ { next } + { + line = $0 + sub(/^[[:space:]]*/, "", line) + candidate = line + sub(/=.*/, "", candidate) + sub(/[[:space:]]*$/, "", candidate) + if (candidate != "" && !(candidate in seen)) { + seen[candidate] = 1 + print candidate + } + } + ' "$file" +} + env_has_key() { local key="$1" local file="$2" @@ -127,6 +150,36 @@ env_has_key() { ' "$file" } +# Value resolution reads .env.local ahead of .env, so an entry there wins. +# Decisions about writing .env deliberately do not use these: .env.local must +# never be baked into .env, or the override would become permanent. +read_root_value() { + local key="$1" + + if env_has_key "$key" "$ROOT_ENV_LOCAL"; then + read_env_value "$key" "$ROOT_ENV_LOCAL" + return 0 + fi + + read_env_value "$key" "$ROOT_ENV" +} + +root_has_key() { + local key="$1" + + env_has_key "$key" "$ROOT_ENV_LOCAL" || env_has_key "$key" "$ROOT_ENV" +} + +report_local_overrides() { + local keys + + keys="$(env_keys "$ROOT_ENV_LOCAL" | paste -sd, - | sed -e 's/,/, /g')" + [[ -n "$keys" ]] || return 0 + + echo ".env.local overrides .env for: ${keys}" + echo "Nothing writes to .env.local, so remove a line there to fall back to .env." +} + is_missing_required_value() { local value="$1" @@ -175,7 +228,7 @@ required_config_value() { local file_value local env_value - file_value="$(read_env_value "$key" "$ROOT_ENV")" + file_value="$(read_root_value "$key")" if ! is_missing_required_value "$file_value"; then printf '%s' "$file_value" return 0 @@ -192,8 +245,8 @@ root_or_source_value() { shift local root_value - if env_has_key "$key" "$ROOT_ENV"; then - root_value="$(read_env_value "$key" "$ROOT_ENV")" + if root_has_key "$key"; then + root_value="$(read_root_value "$key")" if ! is_placeholder_value "$root_value"; then printf '%s' "$root_value" return 0 @@ -208,8 +261,8 @@ root_or_source_nonempty_value() { shift local root_value - if env_has_key "$key" "$ROOT_ENV"; then - root_value="$(read_env_value "$key" "$ROOT_ENV")" + if root_has_key "$key"; then + root_value="$(read_root_value "$key")" if [[ -n "$root_value" ]] && ! is_placeholder_value "$root_value"; then printf '%s' "$root_value" return 0 @@ -408,8 +461,8 @@ load_values() { "$(read_env_value STOREFRONT_ACCESS_TOKEN "$SWIFT_ACCELERATED_XCCONFIG")")" API_VERSION_VALUE="$(first_config_value \ - "$(read_env_value API_VERSION "$ROOT_ENV")" \ - "$(read_env_value STOREFRONT_VERSION "$ROOT_ENV")" \ + "$(read_root_value API_VERSION)" \ + "$(read_root_value STOREFRONT_VERSION)" \ "$(env_fallback API_VERSION)" \ "$(env_fallback STOREFRONT_VERSION)" \ "$(read_env_value API_VERSION "$ANDROID_ENV")" \ @@ -703,8 +756,8 @@ ensure_root_env() { exit 1 fi - if is_missing_required_value "$(read_env_value STOREFRONT_DOMAIN "$ROOT_ENV")" || - is_missing_required_value "$(read_env_value STOREFRONT_ACCESS_TOKEN "$ROOT_ENV")"; then + if is_missing_required_value "$(read_root_value STOREFRONT_DOMAIN)" || + is_missing_required_value "$(read_root_value STOREFRONT_ACCESS_TOKEN)"; then echo "Root .env is missing required storefront configuration." >&2 exit 1 fi @@ -738,6 +791,17 @@ ensure_root_env() { echo "Normalizing root storefront configuration at .env." fi + # Resolved values carry .env.local overrides, so writing them back would make an + # override permanent. Leave .env alone and say why instead. + if [[ -f "$ROOT_ENV_LOCAL" ]]; then + if [[ "$root_needs_write" == "true" ]]; then + echo "Leaving .env alone because .env.local exists. Remove .env.local to let .env be rewritten." + fi + + load_values + return 0 + fi + if [[ "$root_needs_write" == "true" ]]; then collect_missing_values generate_root_env >"$ROOT_ENV" @@ -795,6 +859,7 @@ check_generated_files() { echo "Sample app storefront configuration is up to date." } +report_local_overrides ensure_root_env if [[ "$mode" == "check" ]]; then diff --git a/scripts/test_setup_storefront_env b/scripts/test_setup_storefront_env index d2fa56797..f57013af9 100755 --- a/scripts/test_setup_storefront_env +++ b/scripts/test_setup_storefront_env @@ -62,6 +62,40 @@ assert_not_contains() { fi } +write_canonical_root_env() { + local path="$1" + local domain="$2" + + cat >"$path" <>"$fixture/.env" + + "$fixture/scripts/setup_storefront_env" --skip-optional-prompts >"$output" 2>&1 + assert_output_is_sanitized "$output" + + assert_contains "$android_env" "STOREFRONT_DOMAIN=later-store.example.myshopify.com" + assert_not_contains "$android_env" "synthetic-store.example.myshopify.com" +} + +test_env_local_overrides_root_env() { + local fixture output android_env root_before local_before + fixture="$(make_fixture)" + output="$fixture/output.log" + android_env="$fixture/platforms/android/samples/CheckoutKitAndroidDemo/.env" + root_before="$fixture/root_before" + local_before="$fixture/local_before" + + write_canonical_root_env "$fixture/.env" synthetic-store.example.myshopify.com + cat >"$fixture/.env.local" <<'EOF' +STOREFRONT_DOMAIN=overridden-store.example.myshopify.com +STOREFRONT_ACCESS_TOKEN=overridden-token +EOF + cp "$fixture/.env" "$root_before" + cp "$fixture/.env.local" "$local_before" + + "$fixture/scripts/setup_storefront_env" --skip-optional-prompts >"$output" 2>&1 + assert_output_is_sanitized "$output" + + assert_contains "$android_env" "STOREFRONT_DOMAIN=overridden-store.example.myshopify.com" + assert_contains "$android_env" "STOREFRONT_ACCESS_TOKEN=overridden-token" + assert_contains "$android_env" "EMAIL=checkout-kit@example.com" + assert_file_is_unchanged "$fixture/.env" "$root_before" + assert_file_is_unchanged "$fixture/.env.local" "$local_before" +} + +test_env_local_warning_names_only_keys() { + local fixture output + fixture="$(make_fixture)" + output="$fixture/output.log" + + write_canonical_root_env "$fixture/.env" synthetic-store.example.myshopify.com + cat >"$fixture/.env.local" <<'EOF' +STOREFRONT_DOMAIN=overridden-store.example.myshopify.com +STOREFRONT_ACCESS_TOKEN=overridden-token +EOF + + "$fixture/scripts/setup_storefront_env" --skip-optional-prompts >"$output" 2>&1 + assert_output_is_sanitized "$output" + + assert_contains "$output" ".env.local" + assert_contains "$output" "STOREFRONT_DOMAIN" + assert_contains "$output" "STOREFRONT_ACCESS_TOKEN" + assert_not_contains "$output" "EMAIL" +} + +test_env_local_is_never_baked_into_root_env() { + local fixture output root_before + fixture="$(make_fixture)" + output="$fixture/output.log" + root_before="$fixture/root_before" + + cat >"$fixture/.env" <<'EOF' +STOREFRONT_DOMAIN=synthetic-store.example.myshopify.com +STOREFRONT_ACCESS_TOKEN=synthetic-token +EOF + printf 'STOREFRONT_DOMAIN=%s\n' overridden-store.example.myshopify.com >"$fixture/.env.local" + cp "$fixture/.env" "$root_before" + + "$fixture/scripts/setup_storefront_env" --skip-optional-prompts >"$output" 2>&1 + assert_output_is_sanitized "$output" + + assert_file_is_unchanged "$fixture/.env" "$root_before" + assert_contains "$output" "Leaving .env alone" + assert_contains "$fixture/platforms/android/samples/CheckoutKitAndroidDemo/.env" \ + "STOREFRONT_DOMAIN=overridden-store.example.myshopify.com" +} + test_sync_and_check test_required_values_only test_buyer_address_defaults_match_the_e2e_shop_country @@ -387,5 +508,9 @@ test_blank_customer_account_api_version_defaults test_development_team_follows_env_and_clears test_development_team_is_added_to_an_existing_env test_migration_from_platform_config +test_duplicate_keys_resolve_last_wins +test_env_local_overrides_root_env +test_env_local_warning_names_only_keys +test_env_local_is_never_baked_into_root_env echo "setup_storefront_env synthetic tests passed."