<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Desert Sky Labs</title>
    <description>The latest articles on DEV Community by Desert Sky Labs (@desertskylabs).</description>
    <link>https://dev.to/desertskylabs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3873118%2F932284a9-0ad3-4c83-8b76-0e809ded2d21.png</url>
      <title>DEV Community: Desert Sky Labs</title>
      <link>https://dev.to/desertskylabs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/desertskylabs"/>
    <language>en</language>
    <item>
      <title>How I Got an Expo tvOS App to TestFlight From Windows Without Buying a Mac First</title>
      <dc:creator>Desert Sky Labs</dc:creator>
      <pubDate>Sun, 19 Apr 2026 23:25:55 +0000</pubDate>
      <link>https://dev.to/desertskylabs/how-i-got-an-expo-tvos-app-to-testflight-from-windows-without-buying-a-mac-first-358p</link>
      <guid>https://dev.to/desertskylabs/how-i-got-an-expo-tvos-app-to-testflight-from-windows-without-buying-a-mac-first-358p</guid>
      <description>&lt;p&gt;I got my tvOS app working without a Mac and I want to help you do it too! I spent like $400 in tokens on this and thousands of Github actions minutes and almost gave up multiple times, so let me save you the headaches and the money. &lt;/p&gt;

&lt;p&gt;As a homeschool mom of 5 (ages 2-11) I'm hyper aware of all the AI slop out there. I set out to make my kids a slop-free TV experience that I could cater to our homeschool learning goals and my kids' pet projects. We already use the AppleTV for our regular screen time and for our Jellyfin server, so a tvOS app seemed like a natural place for this newbie to start. Wow was I wrong! I do not have a Mac, I've got a raspberry pi and a windows laptop. &lt;/p&gt;

&lt;p&gt;I burned through GitHub's free 2,000 macOS runner minutes faster than I expected, spent hundreds of dollars in AI tokens, and got fooled by green CI runs that had not actually produced a usable TestFlight build.&lt;/p&gt;

&lt;p&gt;If you are trying to do this from Windows, the docs get fuzzy right where the expensive mistakes start.&lt;/p&gt;

&lt;p&gt;The internet got very unhelpful very quickly. Most advice either pointed back to Expo docs that skipped the ugly parts, or assumed I already owned a Mac and knew my way around Xcode.&lt;/p&gt;

&lt;p&gt;A lot of the advice is either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;too vague to trust&lt;/li&gt;
&lt;li&gt;written for people who already own a Mac&lt;/li&gt;
&lt;li&gt;technically true but missing the part that was actually breaking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So here is the version I wish I could have found first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual goal
&lt;/h2&gt;

&lt;p&gt;I was building a real kids TV app with Expo and React Native.&lt;/p&gt;

&lt;p&gt;The goal was pretty simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;get the app building&lt;/li&gt;
&lt;li&gt;get it uploaded&lt;/li&gt;
&lt;li&gt;get it into TestFlight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reality was... not that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this went sideways
&lt;/h2&gt;

&lt;p&gt;The biggest obstacle is that tvOS sits in an annoying middle ground.&lt;br&gt;
It is close enough to iOS that people talk about it like it should mostly work.&lt;br&gt;
But it is different enough that the weird parts matter a lot.&lt;/p&gt;

&lt;p&gt;That means you can lose a lot of time in places like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;build tooling&lt;/li&gt;
&lt;li&gt;asset requirements&lt;/li&gt;
&lt;li&gt;App Store metadata&lt;/li&gt;
&lt;li&gt;upload verification&lt;/li&gt;
&lt;li&gt;credentials&lt;/li&gt;
&lt;li&gt;versioning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the worst part is that you can think you are done when you are not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap that wasted the most time
&lt;/h2&gt;

&lt;p&gt;A green CI run is not always a successful submission.&lt;/p&gt;

&lt;p&gt;That one deserves to be said twice.&lt;/p&gt;

&lt;p&gt;A green CI run is not always a successful submission.&lt;/p&gt;

&lt;p&gt;I had runs that looked successful even though the upload step had not actually landed the build where it needed to go.&lt;br&gt;
So if you are only watching the big green checkmark, you can waste a ridiculous amount of time celebrating the wrong thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually mattered for me
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Treat tvOS like its own platform
&lt;/h3&gt;

&lt;p&gt;Not like iOS with a couch.&lt;/p&gt;

&lt;p&gt;That mindset shift helps a lot.&lt;br&gt;
Because if you assume everything should behave like iOS, you keep debugging the wrong layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Clean native regeneration mattered
&lt;/h3&gt;

&lt;p&gt;Stale native state can poison later builds.&lt;br&gt;
If you are changing app metadata, build numbers, assets, or submission flow, be suspicious of old generated native files.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Upload verification mattered more than the workflow summary
&lt;/h3&gt;

&lt;p&gt;This was the false-green problem.&lt;br&gt;
I needed to verify the upload output itself, not just trust the workflow result.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Version and build number are not the same problem
&lt;/h3&gt;

&lt;p&gt;Apple is very happy to let you learn this the annoying way.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Asset requirements are picky enough to ruin your afternoon
&lt;/h3&gt;

&lt;p&gt;If your tvOS assets are wrong, Apple will absolutely let you find that out late.&lt;/p&gt;

&lt;h2&gt;
  
  
  The path that finally made sense
&lt;/h2&gt;

&lt;p&gt;I am not putting the full copy-paste workflow in this free post, but I do want to tell you the shape of the path that finally stopped the bleeding:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use a GitHub Actions macOS runner, not a Linux runner&lt;/li&gt;
&lt;li&gt;Use Xcode 26+ on that runner, because that was the fix that finally got me past the Swift 6 wall&lt;/li&gt;
&lt;li&gt;Regenerate the native project cleanly before the build&lt;/li&gt;
&lt;li&gt;Make sure &lt;code&gt;EXPO_TV=1&lt;/code&gt; is actually set so you are building the tvOS variant&lt;/li&gt;
&lt;li&gt;Build on the runner with local EAS, so you can control the environment&lt;/li&gt;
&lt;li&gt;Expect EAS to still use the &lt;code&gt;ios&lt;/code&gt; platform flag for this part, because tvOS is still awkward in the tooling&lt;/li&gt;
&lt;li&gt;Upload with &lt;code&gt;xcrun altool&lt;/code&gt; using &lt;code&gt;--type appletvos&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fail the workflow unless the upload log explicitly confirms success&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That was the high-level path.&lt;br&gt;
For me, GitHub Actions plus &lt;code&gt;xcrun altool&lt;/code&gt; was the successful path. The more generic EAS-only path created more confusion than it solved.&lt;br&gt;
The exact workflow yaml, config snippets, and troubleshooting flow are what I put in the paid version.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell anyone doing this from Windows
&lt;/h2&gt;

&lt;p&gt;If you already have an Expo or React Native app and you are trying to get to TestFlight without buying a Mac first, this is the order I would think about it in:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Make sure the app actually belongs on tvOS
&lt;/h3&gt;

&lt;p&gt;This sounds obvious, but it matters.&lt;br&gt;
If your app is mostly forms, settings screens, or touch-first interactions, tvOS may not be worth the build pain.&lt;br&gt;
If your app is media-first, learning-first, or browse-and-play, it makes a lot more sense.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Get App Store Connect set up correctly before your first real build
&lt;/h3&gt;

&lt;p&gt;This is one of the easiest ways to waste time.&lt;br&gt;
Before you trigger expensive builds, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;tvOS app&lt;/strong&gt; created in App Store Connect, not an iOS app&lt;/li&gt;
&lt;li&gt;the correct bundle identifier&lt;/li&gt;
&lt;li&gt;the numeric App Store Connect app ID&lt;/li&gt;
&lt;li&gt;an App Store Connect API key&lt;/li&gt;
&lt;li&gt;the API key's &lt;strong&gt;Key ID&lt;/strong&gt; and &lt;strong&gt;Issuer ID&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;.p8&lt;/code&gt; file saved somewhere safe, because Apple only lets you download it once&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of that is wrong, you can burn a lot of build time chasing a problem that was never in your app code.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Get the testing flow settled early
&lt;/h3&gt;

&lt;p&gt;This helped more than I expected.&lt;/p&gt;

&lt;p&gt;Before you obsess over app code, make sure the delivery path is at least understandable on a real Apple TV:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;install the TestFlight app on the Apple TV&lt;/li&gt;
&lt;li&gt;add or redeem the tester access early&lt;/li&gt;
&lt;li&gt;watch where the build shows up&lt;/li&gt;
&lt;li&gt;pay attention to the version and build numbers Apple is actually showing you&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When my app showed up as &lt;strong&gt;incompatible&lt;/strong&gt;, that was useful information. It ruled out some guesses and pointed back toward platform, provisioning, and submission issues. And when it finally moved from &lt;strong&gt;incompatible&lt;/strong&gt; to installable, that was one of the clearest signs that the path was finally right. When the build number was not changing the way I expected, that was also a clue that the new build was not actually getting through the path I thought it was.&lt;/p&gt;

&lt;p&gt;That is why I would work on the flow earlier than feels natural. Sometimes the bug is in the delivery pipeline not in the app.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Protect yourself from GitHub Actions minute burn
&lt;/h3&gt;

&lt;p&gt;This is where I got hurt.&lt;/p&gt;

&lt;p&gt;GitHub offers 2,000 free GitHub Actions minutes. Those will disappear faster than you think on macOS runners. A full run can easily take around 15 to 20 minutes, and when you are iterating on workflow syntax, auth, file paths, signing, invalid &lt;code&gt;eas.json&lt;/code&gt; fields, or tvOS-specific config, the minutes pile up fast.&lt;/p&gt;

&lt;p&gt;A few things I would do immediately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use &lt;code&gt;workflow_dispatch&lt;/code&gt; while you are stabilizing the workflow&lt;/li&gt;
&lt;li&gt;set a GitHub billing limit before you start experimenting&lt;/li&gt;
&lt;li&gt;do as much validation locally as you can before kicking off another macOS run&lt;/li&gt;
&lt;li&gt;do not leave a broken poll loop chewing runner time for no reason (ask me how I know!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mistake was not just "GitHub is expensive." The mistake was using expensive macOS time to discover fixable config problems one run at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Clean-regenerate native files when you change important tvOS config
&lt;/h3&gt;

&lt;p&gt;If you change assets, metadata, bundle IDs, versioning, or native plugin config, do not assume the old generated iOS project is still trustworthy.&lt;/p&gt;

&lt;p&gt;In my case, clean native regeneration mattered. Old native state can keep confusing you long after you think you fixed the real issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Treat version and build number as separate levers
&lt;/h3&gt;

&lt;p&gt;This one is sneaky.&lt;/p&gt;

&lt;p&gt;If Apple has already seen a version string, changing only the build number may not save you.&lt;br&gt;
You need to understand both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;version&lt;/code&gt; is the release version users see&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;buildNumber&lt;/code&gt; is the internal increment for that version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The safe rule, if you are trying to stop wasting time, is this:&lt;br&gt;
if Apple has already touched that version, bump the version before the next serious submission attempt.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Get the asset requirements right early
&lt;/h3&gt;

&lt;p&gt;These are published in pieces elsewhere, but it helps to see them in one place.&lt;/p&gt;

&lt;p&gt;Apple validates exact pixel dimensions. Close is not good enough.&lt;br&gt;
And yes, the 2x assets matter.&lt;/p&gt;

&lt;h4&gt;
  
  
  tvOS app icons
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Asset&lt;/th&gt;
&lt;th&gt;1x&lt;/th&gt;
&lt;th&gt;2x&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;App Icon Small&lt;/td&gt;
&lt;td&gt;400x240&lt;/td&gt;
&lt;td&gt;800x480&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App Icon Large&lt;/td&gt;
&lt;td&gt;1280x768&lt;/td&gt;
&lt;td&gt;2560x1536&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  tvOS top shelf images
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Asset&lt;/th&gt;
&lt;th&gt;1x&lt;/th&gt;
&lt;th&gt;2x&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Top Shelf&lt;/td&gt;
&lt;td&gt;1920x720&lt;/td&gt;
&lt;td&gt;3840x1440&lt;/td&gt;
&lt;td&gt;Deprecated, but may still be checked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top Shelf Wide&lt;/td&gt;
&lt;td&gt;2320x720&lt;/td&gt;
&lt;td&gt;4640x1440&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The practical takeaway:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create both 1x and 2x assets&lt;/li&gt;
&lt;li&gt;use the exact dimensions above&lt;/li&gt;
&lt;li&gt;do not assume reusing one file for multiple slots will pass validation&lt;/li&gt;
&lt;li&gt;verify your asset catalog is actually generated correctly in the app bundle&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8. Do not trust the workflow summary, trust the upload proof
&lt;/h3&gt;

&lt;p&gt;If your workflow turns green but the upload step does not clearly prove success, you are not done.&lt;/p&gt;

&lt;p&gt;For me, the important thing was checking the upload output itself.&lt;br&gt;
The workflow needed to fail unless &lt;code&gt;altool&lt;/code&gt; actually confirmed the archive upload succeeded, not just finish without an obvious crash.&lt;br&gt;
If your logs do not contain a clear success message like &lt;code&gt;No errors uploading archive&lt;/code&gt;, treat that run as suspect.&lt;/p&gt;

&lt;p&gt;That one change saves real time, because it kills the false-green problem early.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this post is and is not
&lt;/h2&gt;

&lt;p&gt;This is not a full end-to-end course.&lt;br&gt;
It is the honest summary of where the real pain showed up.&lt;/p&gt;

&lt;p&gt;If you are looking for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a broad introduction to tvOS development&lt;/li&gt;
&lt;li&gt;a beginner coding tutorial&lt;/li&gt;
&lt;li&gt;a claim that this whole process is smooth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not that.&lt;/p&gt;

&lt;p&gt;If you are looking for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a realistic path&lt;/li&gt;
&lt;li&gt;hard-earned gotchas&lt;/li&gt;
&lt;li&gt;the places the docs get fuzzy&lt;/li&gt;
&lt;li&gt;a faster route than brute force trial and error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then yes. This is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want the cleaned-up kit
&lt;/h2&gt;

&lt;p&gt;After fighting through this, I cleaned up the working path into a paid kit with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the exact GitHub Actions workflow yaml&lt;/li&gt;
&lt;li&gt;the config snippets&lt;/li&gt;
&lt;li&gt;the asset checklist&lt;/li&gt;
&lt;li&gt;the upload verification pattern&lt;/li&gt;
&lt;li&gt;troubleshooting notes&lt;/li&gt;
&lt;li&gt;the common mistakes I hit so you do not have to hit all of them yourself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are in the exact situation I was in, that version is for you.&lt;/p&gt;

&lt;p&gt;If this free post got you most of the way there, great. Truly.&lt;br&gt;
If it saved you time and you do not need the full kit, the &lt;del&gt;coffee&lt;/del&gt; Diet Dr Pepper link is there too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;This whole corner of the stack is still weirdly under-documented.&lt;br&gt;
That means two things can be true at once:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;yes, it is possible&lt;/li&gt;
&lt;li&gt;yes, it is still more annoying than it should be&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you are in the middle of it right now, I get it. Dang.&lt;/p&gt;

&lt;p&gt;I will keep cleaning this up as I go so the next person does not have to reconstruct the path from build logs and vibes.&lt;/p&gt;

</description>
      <category>expo</category>
      <category>reactnative</category>
      <category>tvos</category>
      <category>ios</category>
    </item>
  </channel>
</rss>
