<?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: Amir Shekari</title>
    <description>The latest articles on DEV Community by Amir Shekari (@vanenshi).</description>
    <link>https://dev.to/vanenshi</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F396441%2Ff0f3c1cc-4113-4940-9954-5c600c062f6c.jpeg</url>
      <title>DEV Community: Amir Shekari</title>
      <link>https://dev.to/vanenshi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vanenshi"/>
    <language>en</language>
    <item>
      <title>Beta and Production Builds in Expo - Fully Local, No EAS Required</title>
      <dc:creator>Amir Shekari</dc:creator>
      <pubDate>Sun, 05 Jul 2026 11:32:17 +0000</pubDate>
      <link>https://dev.to/vanenshi/beta-and-production-builds-in-expo-fully-local-no-eas-required-3mo5</link>
      <guid>https://dev.to/vanenshi/beta-and-production-builds-in-expo-fully-local-no-eas-required-3mo5</guid>
      <description>&lt;p&gt;Ever installed a beta build on your phone, and it silently replaced your production app? Then you opened it, and it was logged into your production account, talking to your production API? Me too. That's the day I decided my beta and production builds needed to be two completely separate apps.&lt;/p&gt;

&lt;p&gt;The usual answer is "just use EAS build profiles." And EAS is fine, but you don't need it for this. Everything EAS does with build variants is powered by something that already lives in your project: &lt;a href="https://docs.expo.dev/workflow/continuous-native-generation/" rel="noopener noreferrer"&gt;Continuous Native Generation&lt;/a&gt;. The &lt;code&gt;android&lt;/code&gt; and &lt;code&gt;ios&lt;/code&gt; folders in an Expo project are generated output, the same way a &lt;code&gt;build&lt;/code&gt; folder is. Your app config is the source of truth, and &lt;a href="https://docs.expo.dev/more/expo-cli/#prebuild" rel="noopener noreferrer"&gt;&lt;code&gt;npx expo prebuild&lt;/code&gt;&lt;/a&gt; regenerates the native projects from it. That means we can generate a &lt;em&gt;different&lt;/em&gt; native project per environment, on our own machine, with nothing but the Expo CLI.&lt;/p&gt;

&lt;p&gt;Let's build that setup step by step. I'm using Expo SDK 57 here, but nothing in this post is SDK-specific — the same approach works at least back to SDK 50. The app is called MyApp; swap in your own name. Everything below is a real, working project you can clone and run: &lt;a href="https://github.com/vanenshi/expo-build-variants" rel="noopener noreferrer"&gt;vanenshi/expo-build-variants&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4bey256y8yffjyh6bvoq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4bey256y8yffjyh6bvoq.png" alt="One phone home screen with both apps installed side by side: blue " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: delete your native folders (mentally, at least)
&lt;/h2&gt;

&lt;p&gt;If you have &lt;code&gt;android/&lt;/code&gt; and &lt;code&gt;ios/&lt;/code&gt; checked into git and you've been editing them by hand, this approach won't work, because prebuild will overwrite your edits. So the first decision is: the app config owns the native projects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .gitignore
/android
/ios
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;New projects created with &lt;code&gt;create-expo-app&lt;/code&gt; already do this — the Expo docs &lt;a href="https://docs.expo.dev/workflow/continuous-native-generation/" rel="noopener noreferrer"&gt;state that the &lt;code&gt;android&lt;/code&gt; and &lt;code&gt;ios&lt;/code&gt; directories are added to &lt;code&gt;.gitignore&lt;/code&gt; automatically&lt;/a&gt;. If yours is an older project, add the two lines yourself, and move any manual native edits into &lt;a href="https://docs.expo.dev/config-plugins/introduction/" rel="noopener noreferrer"&gt;config plugins&lt;/a&gt; first.&lt;/p&gt;

&lt;p&gt;Why does this matter for environments? Because once the native projects are generated, we can generate them differently per environment. One command gives you a beta app, another gives you the production app, and they never share a single native file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: make the config dynamic
&lt;/h2&gt;

&lt;p&gt;Rename &lt;code&gt;app.json&lt;/code&gt; to &lt;code&gt;app.config.ts&lt;/code&gt; (Expo picks up &lt;a href="https://docs.expo.dev/workflow/configuration/#dynamic-configuration" rel="noopener noreferrer"&gt;dynamic config&lt;/a&gt; automatically) and drive it with a single variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ExpoConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expo/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// default to beta: the safe variant is the one you get by accident&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;APP_VARIANT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APP_VARIANT&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;beta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;IS_BETA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;APP_VARIANT&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;beta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExpoConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MyApp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;myapp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ios&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bundleIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IS_BETA&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;com.example.myapp.beta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;com.example.myapp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;android&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IS_BETA&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;com.example.myapp.beta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;com.example.myapp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole trick. &lt;code&gt;APP_VARIANT=prod npx expo prebuild&lt;/code&gt; gives you the production native project; run it without the variable and you get beta. Defaulting to beta is deliberate — if anyone (you, a script, CI) forgets the variable, the accident produces a harmless beta build, never a production one. This is, incidentally, exactly the mechanism Expo's own &lt;a href="https://docs.expo.dev/tutorial/eas/multiple-app-variants/" rel="noopener noreferrer"&gt;multiple app variants tutorial&lt;/a&gt; uses — they just set &lt;code&gt;APP_VARIANT&lt;/code&gt; from &lt;code&gt;eas.json&lt;/code&gt; profiles instead of your shell.&lt;/p&gt;

&lt;p&gt;Note that &lt;code&gt;APP_VARIANT&lt;/code&gt; only exists at prebuild time, on your machine. It never ships in the app, and the app never reads it. That's deliberate, and step 5 shows what the app reads instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: why a separate namespace, not just an env variable
&lt;/h2&gt;

&lt;p&gt;You might be tempted to keep one bundle identifier and switch the API URL with an env variable. Please don't. Let's go over a scenario together.&lt;/p&gt;

&lt;p&gt;Say your beta and production builds share the bundle id &lt;code&gt;com.example.myapp&lt;/code&gt;. On the device, they're the same app. That means they share the keychain, &lt;code&gt;AsyncStorage&lt;/code&gt;, SQLite files, push tokens, everything. A tester installs the beta over their production install, and the beta happily reads production tokens from the keychain and starts firing requests at your beta backend with a production session. Or the other way around, which is worse.&lt;/p&gt;

&lt;p&gt;Now say beta lives at &lt;code&gt;com.example.myapp.beta&lt;/code&gt;. The OS treats it as a different app. Separate sandbox, separate keychain, separate storage. There is no code path, no forgotten &lt;code&gt;if&lt;/code&gt;, no misconfigured env file that can leak production data into the beta build, because the operating system itself is the wall between them. An env variable is a promise; a namespace is a guarantee.&lt;/p&gt;

&lt;p&gt;There's a second win here, and for your QA team it might be the bigger one: both apps install side by side on the same phone. The Expo docs call this out directly: &lt;a href="https://docs.expo.dev/tutorial/eas/multiple-app-variants/" rel="noopener noreferrer"&gt;each variant needs a unique Android application ID and iOS bundle identifier to enable simultaneous installations on one device&lt;/a&gt;. A tester can reproduce a bug in production, switch to the beta build, and verify the fix in under a minute, on the same device, with both sessions alive. No uninstalling, no logging back in, no "wait, which build was I on?" With a shared bundle id, every switch between environments destroys the other install and its state; with separate namespaces, comparing beta against production becomes a two-tap operation. Once your QA has worked this way, they will not let you go back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: give each variant its own display name (and icon)
&lt;/h2&gt;

&lt;p&gt;Two apps on one home screen, both called "MyApp" with the same icon? Your testers will file bugs against the wrong build within a week. So we want the beta to say "MyApp Beta" under a visibly different icon.&lt;/p&gt;

&lt;p&gt;Here's where most guides tell you to change the &lt;code&gt;name&lt;/code&gt; field per variant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IS_BETA&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MyApp Beta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MyApp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't do that either. Why? Changing &lt;code&gt;name&lt;/code&gt; changes every name reference in the app. Prebuild derives the native project name from it: the Xcode project name, the scheme, iOS folder names, Android module names. Change it and you've effectively renamed the whole native project, which means a full prebuild and a full native rebuild every time you switch variants. But all we need to change is the display name, the one string under the icon.&lt;/p&gt;

&lt;p&gt;The thing we actually want to change is tiny: &lt;code&gt;CFBundleDisplayName&lt;/code&gt; in &lt;code&gt;Info.plist&lt;/code&gt; on iOS, and &lt;code&gt;app_name&lt;/code&gt; in &lt;code&gt;strings.xml&lt;/code&gt; on Android. That's exactly what the &lt;code&gt;withDisplayName&lt;/code&gt; plugin from &lt;a href="https://github.com/vanenshi/expo-plugins" rel="noopener noreferrer"&gt;@vanenshi/expo-plugins&lt;/a&gt; does, and nothing else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx expo &lt;span class="nb"&gt;install&lt;/span&gt; @vanenshi/expo-plugins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app.config.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExpoConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MyApp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// stays stable, native project never gets renamed&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@vanenshi/expo-plugins/display-name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IS_BETA&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MyApp Beta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MyApp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now switching variants touches two plist/xml strings instead of the entire project structure. The home screen says "MyApp Beta", and your Xcode project doesn't even notice. You can verify it yourself after a beta prebuild:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;grep &lt;/span&gt;app_name android/app/src/main/res/values/strings.xml
&amp;lt;string &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app_name"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;MyApp Beta&amp;lt;/string&amp;gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;/usr/libexec/PlistBuddy &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"Print :CFBundleDisplayName"&lt;/span&gt; ios/MyApp/Info.plist
MyApp Beta
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the path: it's still &lt;code&gt;ios/MyApp/&lt;/code&gt;, not &lt;code&gt;ios/MyAppBeta/&lt;/code&gt;. The project structure stayed put.&lt;/p&gt;

&lt;p&gt;The icon works the same way — it's just another config field, so branch it on the variant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IS_BETA&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./assets/icon-beta.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./assets/icon.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One field, both platforms.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://github.com/vanenshi/expo-build-variants" rel="noopener noreferrer"&gt;example repo&lt;/a&gt; the production icon is blue and the beta icon is orange. Nobody taps the wrong app anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: select runtime config by application id, not by env
&lt;/h2&gt;

&lt;p&gt;So how does the app itself know which backend to talk to? The reflex answer is &lt;code&gt;.env&lt;/code&gt; files. That doesn't work well in Expo: &lt;a href="https://docs.expo.dev/guides/environment-variables/" rel="noopener noreferrer"&gt;&lt;code&gt;EXPO_PUBLIC_*&lt;/code&gt; variables get inlined into the JS bundle at build time&lt;/a&gt; — every &lt;code&gt;process.env.EXPO_PUBLIC_X&lt;/code&gt; reference is replaced with its value when the bundle is created — and there's no real separation between environments. Each lifecycle (dev server, prebuild, the build itself) reads its own environment, so it's easy to bundle beta code with whatever env happened to be loaded at that moment. One stale shell variable and your beta build ships with the production URL baked in.&lt;/p&gt;

&lt;p&gt;We already gave each variant a unique, OS-enforced identity in step 3: the application id. The app can ask the OS who it is, and pick its config from that. The same namespace that isolates the storage now selects the config, and the two can never disagree.&lt;/p&gt;

&lt;p&gt;Install &lt;a href="https://docs.expo.dev/versions/v57.0.0/sdk/application/" rel="noopener noreferrer"&gt;&lt;code&gt;expo-application&lt;/code&gt;&lt;/a&gt; and make a config module keyed by application id:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx expo &lt;span class="nb"&gt;install &lt;/span&gt;expo-application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/config/index.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Application&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expo-application&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AppConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;beta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AppConfig&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;com.example.myapp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://jsonplaceholder.typicode.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;com.example.myapp.beta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://dummyjson.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;beta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;applicationId&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`No config found for application id: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The example repo uses two public fake-data APIs — &lt;a href="https://jsonplaceholder.typicode.com" rel="noopener noreferrer"&gt;JSONPlaceholder&lt;/a&gt; standing in for production, &lt;a href="https://dummyjson.com" rel="noopener noreferrer"&gt;DummyJSON&lt;/a&gt; standing in for beta — so you can actually watch the two variants hit two different backends. In your app these would be &lt;code&gt;api.myapp.com&lt;/code&gt; and &lt;code&gt;api.beta.myapp.com&lt;/code&gt;.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// anywhere in the app&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/todos/1`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Walk through what just happened. There is no env variable to load, forget, or leak. The binary identifies itself through &lt;code&gt;Application.applicationId&lt;/code&gt;, which per the docs is &lt;a href="https://docs.expo.dev/versions/v57.0.0/sdk/application/#applicationapplicationid" rel="noopener noreferrer"&gt;the application ID on Android and the bundle ID on iOS&lt;/a&gt; — the identity the OS installed the app under, unfakeable at runtime. If the app is &lt;code&gt;com.example.myapp.beta&lt;/code&gt;, it gets the beta config, period. Build it with the wrong env loaded, build it on a colleague's machine, build it in CI at 3am: the mapping can't drift, because it isn't an input to the build at all. And the &lt;code&gt;throw&lt;/code&gt; at the bottom means an unknown id fails loudly on launch instead of quietly talking to the wrong backend.&lt;/p&gt;

&lt;p&gt;Two footnotes on that same mechanism:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inside Expo Go, &lt;code&gt;Application.applicationId&lt;/code&gt; returns Expo Go's own id (and on web it's &lt;code&gt;null&lt;/code&gt;) — which makes sense once you remember what it is: the identity of the &lt;em&gt;installed binary&lt;/em&gt;. So this setup assumes &lt;a href="https://docs.expo.dev/develop/development-builds/introduction/" rel="noopener noreferrer"&gt;development builds&lt;/a&gt;, which you're already using if you run &lt;code&gt;expo run:ios&lt;/code&gt; / &lt;code&gt;expo run:android&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;This file ships inside the JS bundle, so it's for config, not secrets. Anyone can extract these URLs from the binary. Real secrets stay on the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since the app now knows its environment at runtime, gating beta-only features becomes a one-liner. The example app has a Debug tab and a debug screen that only exist in the beta build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;beta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NativeTabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Trigger&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"debug"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NativeTabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Trigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Debug&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;NativeTabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Trigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;NativeTabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Trigger&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No feature flag service, no env check — the OS-assigned identity drives it. And unlike an env-var check, it can't be true in the wrong binary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvqhqyip4vxz9k5exlphf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvqhqyip4vxz9k5exlphf.png" alt="Side by side: production variant showing application id com.example.myapp and jsonplaceholder API with a single Home tab, next to the beta variant showing com.example.myapp.beta, the dummyjson API, an extra Debug tab and an " width="800" height="751"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: build it, locally
&lt;/h2&gt;

&lt;p&gt;Now the payoff. No EAS queue, no cloud, just your machine (with the usual &lt;a href="https://docs.expo.dev/guides/local-app-development/" rel="noopener noreferrer"&gt;local build prerequisites&lt;/a&gt;: Xcode for iOS, Android Studio + JDK for Android):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# beta&lt;/span&gt;
&lt;span class="nv"&gt;APP_VARIANT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;beta npx expo prebuild &lt;span class="nt"&gt;--clean&lt;/span&gt;
npx expo run:ios &lt;span class="nt"&gt;--configuration&lt;/span&gt; Release
npx expo run:android &lt;span class="nt"&gt;--variant&lt;/span&gt; release

&lt;span class="c"&gt;# production&lt;/span&gt;
&lt;span class="nv"&gt;APP_VARIANT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;prod npx expo prebuild &lt;span class="nt"&gt;--clean&lt;/span&gt;
npx expo run:ios &lt;span class="nt"&gt;--configuration&lt;/span&gt; Release
npx expo run:android &lt;span class="nt"&gt;--variant&lt;/span&gt; release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;APP_VARIANT&lt;/code&gt; only appears on the prebuild line. That's the whole point of step 2: the variable's only job is to decide what native project gets generated. Once &lt;code&gt;ios/&lt;/code&gt; and &lt;code&gt;android/&lt;/code&gt; exist, they &lt;em&gt;are&lt;/em&gt; the beta app (or the production app) — bundle id, icon, name, everything baked in — and &lt;code&gt;run:ios&lt;/code&gt; / &lt;code&gt;run:android&lt;/code&gt; just build whatever is there. Setting the variable on the run commands would do nothing.&lt;/p&gt;

&lt;p&gt;Wiping the native folders before regenerating matters here: layering a prebuild on top of existing files &lt;a href="https://docs.expo.dev/workflow/continuous-native-generation/#clean" rel="noopener noreferrer"&gt;"may not produce the same results in some cases"&lt;/a&gt; — a half-beta, half-production Franken-project is exactly the failure mode we're avoiding. As of SDK 57, &lt;code&gt;expo prebuild&lt;/code&gt; does a clean regeneration by default, so the &lt;code&gt;--clean&lt;/code&gt; flag above is redundant — I keep it because it's harmless, it makes the intent explicit, and on older SDKs (where layering was the default) it's still required. Since the folders are gitignored and disposable, wiping them costs you nothing.&lt;/p&gt;

&lt;p&gt;For distributable artifacts, prebuild first and then use the native tooling directly. On Android:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;APP_VARIANT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;beta npx expo prebuild &lt;span class="nt"&gt;--clean&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;android &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./gradlew assembleRelease
&lt;span class="c"&gt;# apk lands in android/app/build/outputs/apk/release/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On iOS, open &lt;code&gt;ios/MyApp.xcworkspace&lt;/code&gt; and archive from Xcode, or script it with &lt;code&gt;xcodebuild archive&lt;/code&gt; if you're wiring this into CI.&lt;/p&gt;

&lt;p&gt;Wrap the whole thing in package scripts and you're done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"expo start --dev-client"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prebuild"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cross-env APP_VARIANT=beta expo prebuild --clean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prebuild:prod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cross-env APP_VARIANT=prod expo prebuild --clean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ios"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"expo run:ios"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"android"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"expo run:android"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two choices worth explaining. &lt;code&gt;cross-env&lt;/code&gt; so the variable also works on Windows. And beta is the default — both in the bare &lt;code&gt;prebuild&lt;/code&gt; script and as the fallback inside &lt;code&gt;app.config.ts&lt;/code&gt; when &lt;code&gt;APP_VARIANT&lt;/code&gt; isn't set. On a dev machine the build you crank out twenty times a day is the beta one, so it gets the short name, and production is the one you have to spell out with &lt;code&gt;prebuild:prod&lt;/code&gt;. Wrong-way-around defaults are how production builds accidentally ship from a dev machine.&lt;/p&gt;

&lt;p&gt;And if you do use EAS elsewhere, the same variant setup drives &lt;code&gt;eas build --local&lt;/code&gt; too — one &lt;code&gt;eas.json&lt;/code&gt; with a &lt;code&gt;beta&lt;/code&gt; and a &lt;code&gt;production&lt;/code&gt; profile, each setting &lt;code&gt;APP_VARIANT&lt;/code&gt;, and &lt;code&gt;eas build -e beta -p ios --local&lt;/code&gt; produces the same beta app on your own machine. The example repo ships those scripts as &lt;code&gt;ios:beta:build&lt;/code&gt; / &lt;code&gt;ios:prod:build&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One sanity check that makes the whole thing feel real — run the beta prebuild and look at what came out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ APP_VARIANT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;beta npx expo prebuild &lt;span class="nt"&gt;--clean&lt;/span&gt;
✔ Finished prebuild

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;grep &lt;/span&gt;applicationId android/app/build.gradle
        applicationId &lt;span class="s1"&gt;'com.example.myapp.beta'&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;grep &lt;/span&gt;PRODUCT_BUNDLE_IDENTIFIER ios/MyApp.xcodeproj/project.pbxproj | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
        PRODUCT_BUNDLE_IDENTIFIER &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"com.example.myapp.beta"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it again without &lt;code&gt;APP_VARIANT&lt;/code&gt; and both flip back to &lt;code&gt;com.example.myapp&lt;/code&gt;. Two apps, one config file, zero cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we ended up with
&lt;/h2&gt;

&lt;p&gt;Two apps that can live on the same phone. The beta cannot read a single byte of production data, because the OS won't let it. Runtime config is selected by the application id the OS installed the app under, so there's no env file that can drift between builds. Beta-only features are gated by that same identity. Switching between variants is one prebuild-time variable, and thanks to &lt;code&gt;withDisplayName&lt;/code&gt; we get distinct home-screen names without ever renaming the native project. All of it runs on your laptop with the standard Expo CLI.&lt;/p&gt;

&lt;p&gt;The complete working example is at &lt;a href="https://github.com/vanenshi/expo-build-variants" rel="noopener noreferrer"&gt;vanenshi/expo-build-variants&lt;/a&gt; — clone it, run &lt;code&gt;npm run prebuild &amp;amp;&amp;amp; npm run ios&lt;/code&gt;, then &lt;code&gt;npm run prebuild:prod &amp;amp;&amp;amp; npm run ios&lt;/code&gt; again, and you'll have both variants on one simulator.&lt;/p&gt;

&lt;p&gt;If you set this up and something breaks, or you want a third variant like staging (it's just another entry in the config map and another branch in &lt;code&gt;app.config.ts&lt;/code&gt;), let me know.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>reactnative</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Spec Anchor Development: The Methodology That Replaced Our AI Chaos</title>
      <dc:creator>Amir Shekari</dc:creator>
      <pubDate>Wed, 20 May 2026 18:58:55 +0000</pubDate>
      <link>https://dev.to/vanenshi/spec-anchor-development-the-methodology-that-replaced-our-ai-chaos-40pf</link>
      <guid>https://dev.to/vanenshi/spec-anchor-development-the-methodology-that-replaced-our-ai-chaos-40pf</guid>
      <description>&lt;p&gt;This is how we went from a team where everyone used AI their own way to one with a consistent, reviewable, onboardable codebase. Three methodologies, six months, one that actually stuck.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with "everyone uses AI their way"
&lt;/h2&gt;

&lt;p&gt;When AI coding tools started getting good, our team did what most teams do: let people figure it out themselves. Seniors, mids, two fresh graduates, everyone had a workflow, everyone was shipping, and nobody was producing the same kind of code.&lt;/p&gt;

&lt;p&gt;The cracks showed up in review. Some PRs ignored project conventions entirely. Others were technically correct but bloated, AI-optimized for performance problems we didn't have. Commit histories were noise. Junior developers, who needed the most structure, had the least context to judge whether what the AI gave them was actually good.&lt;/p&gt;

&lt;p&gt;This isn't really an AI problem. It's what happens when you hand a power tool to a team with no shared standards and assume it'll work out.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step one: Rules
&lt;/h2&gt;

&lt;p&gt;The first fix was the simplest. Back then Cursor was the dominant tool (this was before agent modes and Codex and Claude Code) and it had a rules system. I spent a day writing a real ruleset: naming conventions, commit format, complexity limits, anti-patterns we'd already seen, with examples of what good and bad looked like.&lt;/p&gt;

&lt;p&gt;On the target repo, it worked immediately. PRs got more consistent. Reviews got faster. One technique worth knowing: ask the AI to compare its own output against the rules and flag deviations. Works well as a lightweight quality gate.&lt;/p&gt;

&lt;p&gt;Worth noting: this isn't a Cursor thing. Every serious AI coding tool today has an equivalent. Claude Code has its &lt;code&gt;CLAUDE.md&lt;/code&gt;, Windsurf has rules, Copilot has instructions, Gemini Code Assist has context files. The mechanism varies, the principle doesn't. Write shared rules, put them in the project, and every developer on every tool benefits from them.&lt;/p&gt;

&lt;p&gt;The ceiling became obvious pretty fast though. Rules govern style and structure. They don't help when a developer asks the AI to build an entire feature in one prompt, which leads to bloated context, degraded coherence, and output that loses the thread halfway through.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step two: SpecKit
&lt;/h2&gt;

&lt;p&gt;SpecKit appeared around that time. The premise: break work into discrete specs before writing code, so the AI handles smaller, well-defined units instead of open-ended feature requests. I tested it personally for a week, got good results, and rolled it into our AI-assisted repos.&lt;/p&gt;

&lt;p&gt;Then the overhead became the problem.&lt;/p&gt;

&lt;p&gt;SpecKit's step count was more than we needed. Every feature added a new spec to an ever-growing list. After a few months it felt like a glorified plan mode. The list existed, nobody referenced it after the feature shipped, and it wasn't connected to any product-level decision. New engineers couldn't use it to understand why something was built a certain way.&lt;/p&gt;

&lt;p&gt;We were generating documentation. We weren't capturing knowledge.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Spec Anchor Development
&lt;/h2&gt;

&lt;p&gt;After more research I found Spec Anchor Development, a variant of SDD (Specification-Driven Development) where the spec isn't a step in a checklist. It's a persistent artifact, written before the build, used during it, kept after as a reference.&lt;/p&gt;

&lt;p&gt;That's the actual difference from SpecKit. In SpecKit, specs accumulate. In Spec Anchor Development, each spec is tied to a specific feature or decision, lean enough to actually be read later.&lt;/p&gt;

&lt;p&gt;What a spec anchor does in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The developer (not the AI) defines what's being built before the AI touches anything&lt;/li&gt;
&lt;li&gt;The AI gets a bounded scope: no guessing, no scope creep&lt;/li&gt;
&lt;li&gt;When someone asks "why was this built this way," there's a document that answers it&lt;/li&gt;
&lt;li&gt;When a decision affects the broader system, it feeds into an ADR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one changed things more than I expected. Once we configured the system to generate or update ADRs alongside spec anchors, we stopped losing institutional knowledge. New engineers could read the decision trail. Senior engineers stopped re-explaining the same architectural context in every code review.&lt;/p&gt;




&lt;h2&gt;
  
  
  OpenSpec
&lt;/h2&gt;

&lt;p&gt;OpenSpec is the main tool for Spec Anchor Development. What makes it work for a mixed-seniority team is that it doesn't try to do too much. The spec template has what you need and stops there. Fast enough that developers don't resent writing specs, structured enough that the output is consistent across the team regardless of who wrote it.&lt;/p&gt;

&lt;p&gt;We ran the full setup (OpenSpec with ADR generation) for three months. A few things that actually changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code quality became consistent across seniority levels, not just in senior-owned repos&lt;/li&gt;
&lt;li&gt;The mandatory spec step forced developers to slow down before coding. That sounds like friction. It caught scope problems that would have been code problems. Net positive.&lt;/li&gt;
&lt;li&gt;Product and engineering could both read the spec layer, no translation required&lt;/li&gt;
&lt;li&gt;Onboarding got easier. New engineers read specs and ADRs before reading code, and came in with actual context&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What to take from this if you're setting up something similar
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Rules first, but don't expect them to scale.&lt;/strong&gt; Whatever tool your team uses, it almost certainly has a rules or instructions system. Use it. It's the fastest win available and worth doing regardless of what else you adopt. It won't solve the feature-scoping problem; it's not supposed to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test before you roll out.&lt;/strong&gt; Run a personal evaluation for a week before putting a methodology in front of your team. The team tax for a bad methodology is real and slow to undo. SpecKit wasn't wrong for every team; it was wrong for ours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A spec is not a ticket.&lt;/strong&gt; A well-written spec anchor defines what the AI is building and what it isn't. That constraint is what stops the "huge prompt, degraded output" failure most teams hit on larger features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The planning habit matters more than the AI workflow.&lt;/strong&gt; The best thing this system did was change how developers thought before they opened their editor. Spending time in the spec phase forced actual thinking about scope, edge cases, and intent. The AI just made the habit systematic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't skip ADRs.&lt;/strong&gt; If specs don't connect to a record of why decisions were made, you'll reconstruct that context from scratch in every retro and every onboarding. It's the part teams skip first and regret most.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>specdriven</category>
    </item>
    <item>
      <title>Why I Ditched SQS for Step Functions (and Ended Up Contributing to SST)</title>
      <dc:creator>Amir Shekari</dc:creator>
      <pubDate>Mon, 11 May 2026 15:31:26 +0000</pubDate>
      <link>https://dev.to/vanenshi/scaling-the-inbox-how-i-contributed-the-step-functions-construct-to-sst-3ccp</link>
      <guid>https://dev.to/vanenshi/scaling-the-inbox-how-i-contributed-the-step-functions-construct-to-sst-3ccp</guid>
      <description>&lt;p&gt;Every developer knows the "happy path"—that glorious moment when your code works perfectly on a small sample size. But when the "happy path" meets the reality of 100,000+ emails and strict API rate limits, things tend to break.&lt;/p&gt;

&lt;p&gt;This is the story of how a bottleneck in my AI startup, Glim, led me to build and contribute the Step Functions component to the SST (Serverless Stack) ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: The Gmail "Wall"
&lt;/h2&gt;

&lt;p&gt;I was building &lt;strong&gt;Glim&lt;/strong&gt;, an AI assistant designed to help users manage their chaos-ridden inboxes. We offered real-time filtering and a powerful bulk-cleanup tool that could archive or delete thousands of emails based on specific criteria like sender, date, or domain.&lt;/p&gt;

&lt;p&gt;To make this work, I had to implement a &lt;a href="https://developers.google.com/workspace/gmail/api/guides/sync" rel="noopener noreferrer"&gt;full inbox sync&lt;/a&gt;. According to the Google API documentation, this is a two-step process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetch a list of all email IDs.&lt;/li&gt;
&lt;li&gt;Iterate through that list and perform a &lt;code&gt;messages.get&lt;/code&gt; request for each individual email's full data.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Math of the 429
&lt;/h3&gt;

&lt;p&gt;Google’s rate limits are notoriously strict and follow a specific &lt;a href="https://developers.google.com/workspace/gmail/api/reference/quota" rel="noopener noreferrer"&gt;quota system&lt;/a&gt;. The limit consists of two primary factors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-second Unit Limit:&lt;/strong&gt; You are allowed 100 quota units per second.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request Weight:&lt;/strong&gt; Each type of request has a "weight." A &lt;code&gt;messages.get&lt;/code&gt; request costs &lt;strong&gt;20 units&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means you can only fetch &lt;strong&gt;5 emails per second&lt;/strong&gt; (or roughly 300 per minute). If you violate this, Google hits you with a &lt;strong&gt;429 Too Many Requests&lt;/strong&gt; error and an exponential block that gets longer every time you retry too early.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt One: A SQS Disaster
&lt;/h2&gt;

&lt;p&gt;My first architecture was a classic serverless pattern: &lt;strong&gt;Lambda + SQS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I bundled 20 email IDs into each SQS message. To respect the rate limits, I throttled the message publishing to one message per second. I set a 60-second visibility timeout, figuring that if a batch hit a 429, SQS would simply retry it a minute later based on standard AWS rules.&lt;/p&gt;

&lt;p&gt;It worked... until it didn't.&lt;/p&gt;

&lt;p&gt;For users with fewer than 40,000 emails, it was fine. But for power users with massive inboxes, the math fell apart. Multiple SQS batches would hit the rate limit simultaneously. The 60-second window wasn't long enough to clear the Google block, leading to more retries, more 429s, and a cascading failure that increased the block time exponentially. I realized I didn't just need a queue; I needed a &lt;strong&gt;State Machine&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: The "Scatter-Gather" Step Function
&lt;/h2&gt;

&lt;p&gt;I needed a way to strictly control the flow of execution. If "Batch 1" hit a rate limit, the entire process needed to pause until that block cleared before "Batch 2" even started.&lt;/p&gt;

&lt;p&gt;I decided to move to a &lt;strong&gt;Scatter-Gather&lt;/strong&gt; pattern using AWS Step Functions. This allowed me to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fetch IDs:&lt;/strong&gt; Fetch 500 IDs at a time (even fetching the IDs can hit limits once you pass 40k emails).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict Flow Control:&lt;/strong&gt; Process the batch, respect the rate limit, then move to the next 500.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Software-Level Backoff:&lt;/strong&gt; Implement an exponential wait step within the flow so that retries don't stack on top of each other at the API gateway level.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Hurdle: SST Didn't Speak Step Functions
&lt;/h2&gt;

&lt;p&gt;At the time, Glim was built entirely on &lt;strong&gt;SST (Serverless Stack)&lt;/strong&gt;. I loved the developer experience of SST, but there was a major missing piece: &lt;strong&gt;it didn't have a built-in component for Step Functions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I had two choices: manually write raw Pulumi code (which would feel out of place in my clean SST codebase) or build a native SST component myself.&lt;/p&gt;

&lt;p&gt;I chose the latter. I designed the component using a &lt;strong&gt;linked-node architecture&lt;/strong&gt;. I visualized the state machine as a circular tree where each node connects the steps of the SF. I wrote the logic to compile this tree into the exact JSON format required for Pulumi (the backend of SST) and pushed it to AWS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Giving Back to the Community
&lt;/h2&gt;

&lt;p&gt;Once I had a working version, I didn't want to keep it in a silo. I forked the SST repository, integrated my new &lt;code&gt;StepFunctions&lt;/code&gt; component, and used it to successfully fix the Glim inbox sync. It handled 100,000+ emails without breaking a sweat.&lt;/p&gt;

&lt;p&gt;Seeing it work in production, I opened a &lt;a href="https://github.com/anomalyco/sst/pull/5691" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After several rounds of refinement, the code was merged. Today, that component, born out of a desperate need to sync 100,000 emails without hitting a Google rate limit, is available for everyone in the SST ecosystem to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Queues aren't Orchestrators:&lt;/strong&gt; SQS is great for decoupling, but when you need to "stop the world" to wait for a rate limit, Step Functions are superior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understand the Quota:&lt;/strong&gt; If I hadn't dug into the 20-unit weight of a &lt;code&gt;messages.get&lt;/code&gt; request, I would have kept guessing why my Lambda was failing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scratch your own itch:&lt;/strong&gt; The best open-source contributions come from solving real-world production problems.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, whether you're syncing a massive inbox or orchestrating a complex AI pipeline, you can do it natively within SST. Happy coding!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>sst</category>
    </item>
    <item>
      <title>Boosting VS Code Productivity - Custom Labels for React Component Files</title>
      <dc:creator>Amir Shekari</dc:creator>
      <pubDate>Fri, 05 Jul 2024 11:33:48 +0000</pubDate>
      <link>https://dev.to/vanenshi/boosting-vs-code-productivity-custom-labels-for-react-component-files-4c47</link>
      <guid>https://dev.to/vanenshi/boosting-vs-code-productivity-custom-labels-for-react-component-files-4c47</guid>
      <description>&lt;p&gt;Ever felt like you're drowning in a sea of &lt;code&gt;index.ts&lt;/code&gt; files? Trust me, I've been there. Working on a massive React project, I found myself constantly lost in a maze of identical-looking files. But then I stumbled upon a game-changer: VS Code's custom labels feature. Let me tell you how it transformed my coding life.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with &lt;code&gt;index.ts&lt;/code&gt; Overload
&lt;/h2&gt;

&lt;p&gt;Picture this: You're deep in the zone, juggling multiple components, when suddenly you need to find that one specific &lt;code&gt;index.ts&lt;/code&gt; file. You open the search bar, and bam! A flood of indistinguishable results. Frustrating, right? That's exactly where I was until I discovered the magic of custom labels.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyyctlubc5tvy1g11jvll.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyyctlubc5tvy1g11jvll.png" alt=" " width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Custom Labels
&lt;/h2&gt;

&lt;p&gt;So, what's the deal with these custom labels? In a nutshell, they're VS Code's way of letting you personalize how file names appear in your editor tabs and search bar. For us React devs drowning in &lt;code&gt;index.ts&lt;/code&gt; files, it's like throwing us a lifeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Custom Labels Step by Step
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Enable Custom Labels
&lt;/h3&gt;

&lt;p&gt;First, you'll need to enable custom labels in your VS Code settings. Just add this line to your settings.json:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"workbench.editor.customLabels.enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Define Patterns for &lt;code&gt;index.ts&lt;/code&gt; Files
&lt;/h3&gt;

&lt;p&gt;Now, here's where the real magic happens. Add this bit to tell VS Code to replace those generic &lt;code&gt;index.ts&lt;/code&gt; names with their parent directory names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"workbench.editor.customLabels.patterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"**/index.{ts,js,tsx,jsx}"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${dirname}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Customize for Specific Directories
&lt;/h3&gt;

&lt;p&gt;If you're like me and have a specific folder where all your components live, you can target just that directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"workbench.editor.customLabels.patterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"components/*/index.{ts,js,tsx,jsx}"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${dirname}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhfdek4anx3chhiib5xqh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhfdek4anx3chhiib5xqh.png" alt=" " width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Benefits of Custom Labels
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Increased Readability&lt;/strong&gt;: Instead of a sea of &lt;code&gt;index.tsx&lt;/code&gt; tabs, you'll see "Button", "Header", and so on. It's like your editor suddenly learned to speak your language!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Efficient Searching&lt;/strong&gt;: Searching for specific components became a breeze.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reduced Mental Load&lt;/strong&gt;: No more second-guessing which &lt;code&gt;index.ts&lt;/code&gt; I was looking at.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, fellow coders, if you're tired of playing "Where's Waldo?" with your &lt;code&gt;index.ts&lt;/code&gt; files, give custom labels a shot. It's a small change that packs a big punch in your daily coding life. Trust me, your future self will thank you!&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>productivity</category>
      <category>react</category>
    </item>
    <item>
      <title>Building Safe and Dynamic URLs with TypeScript</title>
      <dc:creator>Amir Shekari</dc:creator>
      <pubDate>Fri, 13 Oct 2023 19:58:13 +0000</pubDate>
      <link>https://dev.to/vanenshi/crafting-dynamic-urls-with-typescript-2li</link>
      <guid>https://dev.to/vanenshi/crafting-dynamic-urls-with-typescript-2li</guid>
      <description>&lt;p&gt;When developing applications, one of the common tasks is constructing URLs, especially when they contain dynamic parts, like IDs. Ensuring the correctness of these URLs can be tedious, leading to potential runtime errors if not done right. This is where TypeScript can lend a hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Need for urlConverter
&lt;/h2&gt;

&lt;p&gt;Using string-based routes can be error-prone. Miss a single character or forget to replace a dynamic segment, and the whole URL can break. By using TypeScript, we can create a utility that helps in generating these URLs while ensuring their correctness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the urlConverter Step by Step
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Organizing URLs
&lt;/h3&gt;

&lt;p&gt;One of the first steps is to have all URLs organized in one place. Enums in TypeScript provide an excellent way to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;UserUrls&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;getUserUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/users/[userId]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getMyUserURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/users/me&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// other URLs can go here...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Extracting Dynamic Parameters
&lt;/h3&gt;

&lt;p&gt;For URLs with dynamic parts, we need a way to recognize and replace them. A custom TypeScript type can help identify these parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;IsParameter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Part&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Part&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;ParamName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]`&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;ParamName&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FilteredParts&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Path&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;PartA&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;PartB&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;IsParameter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PartA&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;FilteredParts&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PartB&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IsParameter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cm"&gt;/**
 * A very special type that parse a template string to it's object
 * source: https://lihautan.com/extract-parameters-type-from-string-literal-types-with-typescript/
 * type ParamObject = Params&amp;lt;'/purchase/[shopId]/[itemId]'&amp;gt;;
 * type ParamObject = {
 *      shopId: string;
 *      itemId: string;
 *    }
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;FilteredParts&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This type works to find any segment enclosed within [].&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Confirming the Presence of Parameters
&lt;/h3&gt;

&lt;p&gt;To ensure that if a URL requires parameters, they're provided, we can use another type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;HasParams&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Path&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will check if a route needs parameters.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Creating the urlConverter Function
&lt;/h3&gt;

&lt;p&gt;With all the above in place, we can now create our utility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AllEnums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UserUrls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;urlConverter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;AllEnums&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;routeKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;paramsArr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HasParams&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;routeKey&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;routeKey&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid route key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;paramsArr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;paramKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;paramKey&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;paramKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage Example
&lt;/h2&gt;

&lt;p&gt;Generating a reset URL with a given token can be done easily:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getUserUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;urlConverter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UserUrls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getUserUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getUserUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: `/users/12`&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;urlConverter&lt;/code&gt; utility ensures that constructing dynamic URLs is straightforward and less error-prone. Leveraging TypeScript allows for catching potential mistakes at compile-time rather than runtime, resulting in more robust applications.&lt;/p&gt;




&lt;p&gt;I hope this approach helps in your development journey, ensuring cleaner and safer URL generation with TypeScript.&lt;/p&gt;

</description>
      <category>typescript</category>
    </item>
    <item>
      <title>Protect Production Database from Wrong Migrations</title>
      <dc:creator>Amir Shekari</dc:creator>
      <pubDate>Tue, 25 Apr 2023 15:03:58 +0000</pubDate>
      <link>https://dev.to/vanenshi/how-i-saved-my-production-database-with-one-simple-console-message-4fjm</link>
      <guid>https://dev.to/vanenshi/how-i-saved-my-production-database-with-one-simple-console-message-4fjm</guid>
      <description>&lt;p&gt;As a .NET developer, I had a close call when I applied a migration to my production database without changing the database connection string. I was mortified when I realized my mistake, but I quickly got to work finding a solution to prevent it from happening again.&lt;/p&gt;

&lt;p&gt;That's when I came up with the idea to add a console message that displays the migration details before applying it. This simple addition gives me the chance to review the migration details and ensure that I am working with the correct database before making any changes.&lt;/p&gt;

&lt;p&gt;With this new safeguard in place, I can breathe easy knowing that my production database is safe from accidental alterations. It just goes to show that sometimes the simplest solutions can be the most effective.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationDbContextFactory&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IDesignTimeDbContextFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ApplicationDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ApplicationDbContext&lt;/span&gt; &lt;span class="nf"&gt;CreateDbContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ... Some code to generate dbContextBuilder&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ApplicationDbContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbContextBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// This is where magic happends&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pendingMigrations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPendingMigrations&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"*********************************************\n"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"This command is going to apply migrations with following details"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ConnectionString: "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForegroundColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConsoleColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Yellow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ResetColor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Migrations:\n\t"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForegroundColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConsoleColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Yellow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n\t"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pendingMigrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ResetColor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"*********************************************"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Do You confirm? (Y/N)"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;userInput&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadLine&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userInput&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="s"&gt;"Y"&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="s"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForegroundColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConsoleColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Aborted!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;More info about the &lt;a href="https://learn.microsoft.com/en-us/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli" rel="noopener noreferrer"&gt;&lt;code&gt;ApplicationDbContextFactory&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>database</category>
    </item>
    <item>
      <title>Toggle your VPN connection with a shortcut, in Manjaro</title>
      <dc:creator>Amir Shekari</dc:creator>
      <pubDate>Mon, 31 May 2021 15:55:55 +0000</pubDate>
      <link>https://dev.to/vanenshi/toggle-your-vpn-connection-with-a-shortcut-in-manjaro-10ie</link>
      <guid>https://dev.to/vanenshi/toggle-your-vpn-connection-with-a-shortcut-in-manjaro-10ie</guid>
      <description>&lt;p&gt;This is a little script that allows you to toggle your VPN connection by running a bash script. &lt;br&gt;
I mostly use it by assigning it to a shortcut (meta + v).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


</description>
      <category>linux</category>
      <category>arch</category>
      <category>bash</category>
      <category>manjaro</category>
    </item>
  </channel>
</rss>
