Skip to content

feat(metadata): support pricing tier and territory availability#3594

Draft
EvanBacon wants to merge 2 commits intomainfrom
@bacon/metadata-pricing-availability
Draft

feat(metadata): support pricing tier and territory availability#3594
EvanBacon wants to merge 2 commits intomainfrom
@bacon/metadata-pricing-availability

Conversation

@EvanBacon
Copy link
Copy Markdown
Contributor

@EvanBacon EvanBacon commented Apr 9, 2026

Summary

EAS metadata previously had no surface area for app pricing or territory availability — every new app stayed at free / worldwide forever, even after a metadata:push. This adds two new declarative blocks (pricing and availability) and a new PricingTask that handles both pull and push via @expo/apple-utils.

{
  "configVersion": 0,
  "apple": {
    "pricing": {
      "tier": "0",
      "schedule": [
        { "startDate": "2099-01-01T00:00:00Z", "tier": "5" }
      ]
    },
    "availability": {
      "territories": ["USA", "GBR", "JPN"]
      // or: "territories": "all"
    }
  }
}
  • New PricingTask (packages/eas-cli/src/metadata/apple/tasks/pricing.ts) registered alongside the other Apple tasks. Prepares state by refetching the App with prices + availableTerritories includes, downloads into the schema, uploads via App.updateAsync({ appPriceTier, territories }).
  • availability.territories: "all" is expanded at upload time against Territory.getAsync so we don't drift if Apple adds territories.
  • Reader/writer methods + JSON-schema definitions for the two new blocks.
  • 14 unit tests covering: free tier default, paid tier, scheduled price changes, single / multiple / all territories, combined pricing+availability calls, and the prepare-fail path.

What works now vs what's blocked

Working now (legacy API):

  • pricing.tier — sets the active price tier via App.updateAsync({ appPriceTier })
  • availability.territories — sets territory availability via App.updateAsync({ territories })
  • pricing.schedule on pull — reads future-dated appPrices records into the config for visibility

Blocked on @expo/apple-utils bump (expo/third-party#147):

  • pricing.schedule on push — requires AppPriceSchedule.createAsync which replaces the entire prior schedule (not a diff/merge). Currently logs a warning and skips.
  • Modern price point resolution — requires AppPricePoint.getForAppAsync to map tier names to appPricePointId values.
  • Modern schedule read — App.getPriceScheduleAsync() for the singular appPriceSchedule relationship.
  • Territory availability in the modern API is managed via TerritoryAvailability, separate from pricing. The legacy App.updateAsync({ territories }) path works for both old and new pricing-model accounts.

Dependencies

  • expo/third-party#147 — adds AppPriceSchedule, AppPricePoint, AppPrice extensions to @expo/apple-utils. Schedule push will be wired once this is published.

Test plan

  • yarn jest src/metadata/apple/tasks/__tests__/pricing.test.ts (14/14 passing)
  • yarn jest src/metadata (164 tests passing; 4 pre-existing suite failures unrelated to this PR — @expo/eas-json workspace resolution)
  • yarn lint (0 new errors/warnings)
  • yarn fmt --check (clean)
  • Manual metadata:pull against a real app to verify round-tripping
  • Manual metadata:push against a sandbox app to verify both legacy and new pricing accounts
  • Deferred: Schedule push end-to-end test after @expo/apple-utils bump

Open questions

  1. Is priceTier still the right concept? Apple migrated to "base territory" pricing in late 2023, where pricing is anchored to one base territory and a pricePoint id rather than a global tier. The legacy appPrices / App.updateAsync({ appPriceTier }) endpoints still work for many existing apps but accounts on the new model may 404 the legacy paths. @expo/apple-utils only exposes the legacy endpoint today, so the implementation uses it and gracefully no-ops if prepareAsync fails. The schema is intentionally forward-compatible: adding a basePricePoint field later is non-breaking.

  2. Territory validation. The runtime resolver normalizes / dedupes the codes but does not validate them against the live Territory list (only the 'all' path fetches it). We could add a strict validator, but Apple already returns a clean error on bad codes and the upfront fetch costs a round trip.

🤖 Generated with Claude Code

EAS metadata previously had no way to set the App Store price tier or
territory availability, so every new app sat at free / worldwide
forever. Add `pricing` and `availability` blocks to the metadata schema
plus a new `PricingTask` that pulls and pushes both via the legacy
`App.updateAsync({ appPriceTier, territories })` endpoint exposed by
@expo/apple-utils.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 9, 2026

Subscribed to pull request

File Patterns Mentions
**/* @douglowder
packages/eas-cli/schema/** @byCedric
packages/eas-cli/src/metadata/** @byCedric

Generated by CodeMention

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 9, 2026

Codecov Report

❌ Patch coverage is 85.39326% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.47%. Comparing base (8c170af) to head (06dbc19).
⚠️ Report is 31 commits behind head on main.

Files with missing lines Patch % Lines
...ckages/eas-cli/src/metadata/apple/config/writer.ts 0.00% 11 Missing ⚠️
...ckages/eas-cli/src/metadata/apple/tasks/pricing.ts 97.27% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3594      +/-   ##
==========================================
+ Coverage   54.26%   54.47%   +0.22%     
==========================================
  Files         821      822       +1     
  Lines       35327    35438     +111     
  Branches     7363     7397      +34     
==========================================
+ Hits        19166    19301     +135     
+ Misses      16074    16046      -28     
- Partials       87       91       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@EvanBacon EvanBacon marked this pull request as draft April 10, 2026 00:13
…rty#147

Document the new API surface from expo/third-party#147 that will enable
schedule push once @expo/apple-utils is bumped:
- AppPriceSchedule.createAsync (replaces entire schedule, no merge)
- AppPricePoint.getForAppAsync (resolve tiers to price point IDs)
- App.getPriceScheduleAsync (read current schedule)

Update the schedule-not-pushed warning to reference the tracking PR.
Add comments clarifying territory availability uses the legacy path
which is separate from pricing in the modern ASC API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

❌ It looks like a changelog entry is missing for this PR. Add it manually to CHANGELOG.md.
⏩ If this PR doesn't require a changelog entry, such as if it's an internal change that doesn't affect the user experience, you can add the "no changelog" label to the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant