@radhya/mach 2.5.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Cloud Build & Delivery for React Native, Expo, and Flutter**
4
4
 
5
- Mach is a CLI that lets you build, sign, and submit React Native and Expo apps from the cloud. Flutter support is modeled in configuration and documentation so it can be added without changing the command surface.
5
+ Mach is a CLI that builds, signs, audits, versions, and submits Flutter, React Native, and Expo apps locally or in the cloud.
6
6
 
7
7
  ## Installation
8
8
 
@@ -21,7 +21,7 @@ mach push send --user user_123 --title "Hello" --body "Your update is ready."
21
21
  mach push status --days 30
22
22
  ```
23
23
 
24
- MACH Push supports Expo development builds and bare React Native. It
24
+ MACH Push supports Flutter, Expo development builds, and bare React Native. It
25
25
  adds a working default permission, registration, receive, and tap flow while
26
26
  leaving an editable callback file in the application. The app config only marks
27
27
  push as enabled; MACH stores encrypted
@@ -41,6 +41,7 @@ mach link
41
41
  # Run a cloud build
42
42
  mach build --platform ios
43
43
  mach build --platform android
44
+ mach build --platform web # Flutter projects
44
45
  ```
45
46
 
46
47
  ## Commands
@@ -61,7 +62,7 @@ mach build --platform android
61
62
  | `mach env` | Manage, list, and download environment variables and secrets |
62
63
  | `mach version` | Show and seed DB-owned native version counters |
63
64
  | `mach config` | Show resolved configuration for a build profile |
64
- | `mach start` | Start the Metro development server |
65
+ | `mach start` | Start Flutter or the React Native/Expo development server |
65
66
  | `mach device` | Register test devices for ad-hoc distribution |
66
67
  | `mach audit` | Check project health, delivery impact, and security locally |
67
68
  | `mach agent` | Manage the local device discovery agent for Install Hub |
@@ -109,7 +110,8 @@ Useful options:
109
110
 
110
111
  ## Cloud Builds
111
112
 
112
- Trigger a build in the cloud — Mach handles zipping, uploading, compiling, and returning your IPA or APK.
113
+ Trigger a build in the cloud — Mach handles source upload, framework-specific
114
+ compilation, signing, and delivery of IPA, APK, or AAB artifacts.
113
115
 
114
116
  ```bash
115
117
  # iOS build (production)
@@ -140,9 +142,9 @@ Supported values:
140
142
  | ---------------- | ------------ | ------------------------------------------------------------------------------ |
141
143
  | `react-native` | Supported | Uses committed `ios/` and `android/` native projects. No Expo prebuild runs. |
142
144
  | `expo` | Supported | Runs Expo prebuild before native compilation and applies Expo-specific guards. |
143
- | `flutter` | Planned | Reserved in config/docs. Builds fail clearly until Flutter builders are added. |
145
+ | `flutter` | Supported | Uses Flutter/Dart with managed Android/iOS signing, web artifacts, caches, symbols, and submissions. |
144
146
 
145
- `mach init` and `mach link` create `framework: "react-native"` by default. Change it to `"expo"` for Expo projects before running builds. This explicit config prevents a React Native change from affecting Expo projects, and avoids fragile package scanning in CI.
147
+ `mach init` and `mach link` detect the selected app and save its framework. The saved value remains explicit, preventing one framework's build behavior from affecting another in CI.
146
148
 
147
149
  Use the framework guide that matches your app. The command surface stays the same; only `framework` changes the build pipeline:
148
150
 
@@ -153,7 +155,7 @@ Use the framework guide that matches your app. The command surface stays the sam
153
155
 
154
156
  ## OTA Updates
155
157
 
156
- Mach OTA lets you publish JavaScript and asset updates through Mach without using EAS Update. Mach hosts the update artifacts and serves the right update by channel, platform, and runtime version. Your app never needs storage credentials.
158
+ Mach OTA uses a framework-specific provider: Mach-hosted `expo-updates` for Expo and bare React Native, and customer-owned Shorebird for Flutter. Mach never stores `SHOREBIRD_TOKEN`.
157
159
 
158
160
  ```bash
159
161
  mach ota setup
@@ -164,6 +166,15 @@ mach ota rollback --update-id <updateId>
164
166
  mach ota promote --update-id <updateId>
165
167
  ```
166
168
 
169
+ For Flutter, the equivalent workflow is:
170
+
171
+ ```bash
172
+ mach ota setup --profile staging
173
+ mach ota release --platform all --profile staging
174
+ mach ota publish --platform all --profile staging --track staging --release-version latest
175
+ mach ota promote --release-version 1.2.0+42 --patch-number 1
176
+ ```
177
+
167
178
  `mach ota setup` uses the open-source `expo-updates` runtime for both Expo and bare React Native apps while keeping publishing, manifests, assets, channels, metrics, and rollback on Mach. It writes the `ota` config, installs or refreshes the compatible project-local plugin when approved, and wires `@radhya/mach/expo-plugin`. Updates are served from Mach instead of EAS Update.
168
179
 
169
180
  For bare React Native, setup first installs Expo Modules when needed, then uses the installed `expo-updates configuration:syncnative` tool to update the committed Android and iOS projects. It does not run Expo prebuild or replace the native folders. Run setup from a clean branch so you can review the package and native integration changes.
@@ -178,6 +189,8 @@ Runtime precedence is `--runtime-version`, `ota.runtimeVersion`, `ota.runtimeVer
178
189
 
179
190
  Bare React Native uses the same Expo Updates protocol and the same Mach endpoint as Expo. `mach-react-native` remains reserved for a future Mach-owned native runtime and cannot currently be selected for publishing.
180
191
 
192
+ Flutter setup initializes Shorebird, records only `engine: "shorebird"` and the default track, and delegates release/patch storage and delivery directly to the customer's Shorebird account. CI reads `SHOREBIRD_TOKEN` only from its environment. Native or asset changes still require a new base release; Dart-only compatible fixes can be patched.
193
+
181
194
  See [OTA Updates](docs/ota.md) for setup behavior, bare React Native requirements, runtime matching, and validation.
182
195
 
183
196
  ## Deep Links
@@ -216,7 +229,7 @@ mach deeplink setup \
216
229
  --output public
217
230
  ```
218
231
 
219
- `setup` writes compact `deepLinks` settings into `mach.config.json`, generates Apple AASA and Android `assetlinks.json` files, and wires an Expo config plugin for Expo projects. The interactive flow can also enable iOS Web Credentials for HTTPS OAuth callbacks and Android Login Credentials for website password and passkey sharing; both remain disabled by default. For Expo apps, Mach prefers `@radhya/mach/expo-plugin`; interactive setup offers to install `@radhya/mach` locally, and `--yes` or `--install-plugin` installs it automatically. Use `--no-install-plugin` to skip package installation; if the package is missing, Mach generates a local `./plugins/withMachDeepLinks` fallback. See [Deep Links](docs/deep-links.md).
232
+ `setup` writes compact `deepLinks` settings into `mach.config.json`, generates Apple AASA and Android `assetlinks.json` files, wires an Expo config plugin for Expo projects, and safely patches Flutter's Android manifest plus iOS entitlements/URL scheme. Re-running it is idempotent. The interactive flow can also enable iOS Web Credentials for HTTPS OAuth callbacks and Android Login Credentials for website password and passkey sharing; both remain disabled by default. For Expo apps, Mach prefers `@radhya/mach/expo-plugin`; interactive setup offers to install `@radhya/mach` locally, and `--yes` or `--install-plugin` installs it automatically. Use `--no-install-plugin` to skip package installation; if the package is missing, Mach generates a local `./plugins/withMachDeepLinks` fallback. See [Deep Links](docs/deep-links.md).
220
233
 
221
234
  ## Local Device Agent
222
235
 
@@ -457,13 +470,15 @@ Create a `mach.config.json` in your project root:
457
470
  | `framework` | Required build framework: `react-native`, `expo`, or `flutter`. Builds never infer this from `package.json` |
458
471
  | `appDirectory` | Optional mobile app path relative to the repository root, for example `apps/mobile`. Defaults to `"."` for existing projects. |
459
472
  | `name` | App display name |
460
- | `nodeVersion` | Node.js version to use on the build machine (e.g. `"24"`, `"20.11.0"`). Switches via nvm before installing deps |
473
+ | `nodeVersion` | React Native/Expo Node.js version (e.g. `"24"`, `"20.11.0"`). Flutter builds do not use this field |
474
+ | `flutter.sdkVersion` | Exact Flutter stable SDK for managed builds. `mach init` and `mach link` pin the installed SDK automatically |
461
475
 
462
476
  ### Monorepo Layout
463
477
 
464
- Keep `mach.config.json`, the package-manager lockfile, and workspace
465
- configuration at the repository root. Set `appDirectory` only when the Expo or
466
- bare React Native app lives below that root:
478
+ Keep `mach.config.json` and the workspace configuration at the repository root.
479
+ For JavaScript workspaces, keep the package-manager lockfile there too. Set
480
+ `appDirectory` when an Expo, bare React Native, or Flutter application lives
481
+ below the root:
467
482
 
468
483
  ```json
469
484
  {
@@ -472,9 +487,10 @@ bare React Native app lives below that root:
472
487
  }
473
488
  ```
474
489
 
475
- Mach uploads and installs from the repository root, then runs Expo, Gradle,
476
- CocoaPods, and Xcode from `appDirectory`. Root-level projects need no config
477
- change and continue to use `"."`.
490
+ Mach uploads from the repository root, resolves npm/Yarn/pnpm or Dart pub
491
+ workspace dependencies as appropriate, and runs the selected framework's
492
+ native toolchain from `appDirectory`. Root-level projects need no config change
493
+ and continue to use `"."`.
478
494
 
479
495
  ### Platform Properties
480
496
 
@@ -497,6 +513,12 @@ change and continue to use `"."`.
497
513
  | `distribution` | `development` for iOS development signing, `internal` for ad-hoc/internal testing builds, or `store` for App Store / Play Store release builds |
498
514
  | `android.emulator` | `true` to include Android emulator ABI support (`x86_64`) for Maestro/e2e APK builds |
499
515
  | `android.architectures` | Override Android release ABIs, for example `armeabi-v7a,arm64-v8a,x86_64` |
516
+ | `flutter.flavor` | Flutter Android product flavor and shared iOS scheme. Omit for unflavored apps |
517
+ | `flutter.target` | Dart entrypoint relative to `appDirectory`; defaults to `lib/main.dart` |
518
+ | `flutter.mode` | Flutter compilation mode: `debug`, `profile`, or `release` |
519
+ | `flutter.dartDefinesFromEnv` | Environment-key names passed with `--dart-define`. Values are compiled into the app and must not be secrets |
520
+ | `flutter.obfuscate` | Obfuscate Dart code for Android/iOS release builds and retain downloadable split debug symbols |
521
+ | `flutter.splitDebugInfo` | Retain downloadable split debug symbols without otherwise changing app behavior |
500
522
  | `preBuild` / `postBuild` | Shell command or script reference to run around the build |
501
523
  | `envGroups` | Array of environment group names to merge into this profile |
502
524
 
@@ -648,9 +670,9 @@ press `i` for iOS, `a` for Android, `w` for web, and `r` to reload. Use the flag
648
670
 
649
671
  `mach audit` combines doctor-style environment checks, delivery-impact advice,
650
672
  and security scanning in one offline-friendly command. It validates Mach config,
651
- framework and profile consistency, app identifiers, Node/lockfile health, Expo
652
- dependency alignment, permissions, secrets, dependencies, storage, networking,
653
- and native manifests when available.
673
+ framework and profile consistency, app identifiers, Node or Flutter/Dart
674
+ toolchains, lockfiles, dependency alignment, permissions, secrets, storage,
675
+ networking, and native manifests when available.
654
676
 
655
677
  ```bash
656
678
  mach audit
@@ -706,6 +728,7 @@ mach submit --latest --platform android --profile production
706
728
  ## Requirements
707
729
 
708
730
  - Node.js 18+
731
+ - For local Flutter builds: the Flutter SDK version pinned in `mach.config.json`
709
732
  - For iOS builds: Xcode (local builds) or cloud build subscription
710
733
  - For Android builds: Android SDK (local builds) or cloud build subscription
711
734
 
@@ -0,0 +1,4 @@
1
+ var o={name:"Mach",nameLower:"mach",nameUpper:"MACH",company:"Radhya Softlabs",npmScope:"@radhya",configFileName:"mach.config.json",configDir:".mach",workspacesDir:".mach-workspaces",remoteWorkspacesDir:"mach_workspaces",envPrefix:"MACH_",bucketPrefix:"skye-builds-385229479322",buildPrefix:"mach-build",credentialsKeychain:"mach-credentials.keychain",keychainPassword:"mach",cliTag:e=>` Mach CLI - ${e} `,fastlanePrefix:"mach",jsonStartMarker:"___MACH_JSON_START___",jsonEndMarker:"___MACH_JSON_END___",prewarmedMarker:"/opt/mach-prewarmed"};import s from"fs";import f from"path";import y from"os";import v from"axios";var m=f.join(y.homedir(),o.configDir),d=f.join(m,"credentials.json");function h(){return process.env[`${o.envPrefix}TOKEN`]?.trim()||void 0}function T(e){s.existsSync(m)||s.mkdirSync(m,{recursive:!0}),s.writeFileSync(d,JSON.stringify(e,null,2))}function l(){let e=h();if(e)return{token:e,email:"token-auth"};if(!s.existsSync(d))return null;try{return JSON.parse(s.readFileSync(d,"utf-8"))}catch{return null}}function I(){return s.existsSync(d)?(s.unlinkSync(d),!0):!1}function $(){let e=h();if(e)return{token:e,email:"token-auth"};let r=l();if(r?.token)return r;console.error(`\x1B[31mAuthentication required.\x1B[0m Either:
2
+ 1. Run \x1B[33m${o.nameLower} login\x1B[0m for interactive login
3
+ 2. Set \x1B[33m${o.envPrefix}TOKEN\x1B[0m environment variable for CI/CD`),process.exit(1)}async function k(e){let r=l();if(!r||!r.refreshToken)return null;try{let t=await v.post(`${e}/refresh-token`,{username:r.email,refreshToken:r.refreshToken});if(t.data.status==="success"&&t.data.user){let n={...r,id:t.data.user.id||r.id,token:t.data.user.token,refreshToken:t.data.user.refreshToken||r.refreshToken};return T(n),n.token}}catch{}return null}import A from"axios";import g from"dotenv";import x from"path";import{fileURLToPath as C}from"url";var b=x.dirname(C(import.meta.url));g.config({path:x.join(b,"../.env"),quiet:!0});var c=process.env[`${o.envPrefix}API_URL`]||"https://mach-api.securejs.in",_="https://getmach.dev",D=c.includes("localhost:3001")||c.includes("127.0.0.1:3001")?c.replace("3001","8081"):_,B=process.env[`${o.envPrefix}DASHBOARD_URL`]||D;var u=A.create({baseURL:c,headers:{Connection:"close"}});u.interceptors.request.use(e=>{let t=l()?.token||process.env[`${o.envPrefix}TOKEN`];return t&&(e.headers.Authorization=`Bearer ${t}`),e},e=>Promise.reject(e));u.interceptors.response.use(e=>e,async e=>{let{config:r}=e;if((!e.response||e.code==="ECONNRESET"||e.code==="ETIMEDOUT")&&(!r._retryCount||r._retryCount<3)){r._retryCount=(r._retryCount||0)+1;let n=Math.pow(2,r._retryCount-1)*1e3;return console.warn(`\x1B[33m[NETWORK] Transient error (${e.code||"UNKNOWN"}). Retrying in ${n/1e3}s... (Attempt ${r._retryCount}/3)\x1B[0m`),await new Promise(a=>setTimeout(a,n)),u(r)}return Promise.reject(e)});u.interceptors.response.use(e=>e,async e=>{let r=e.config;if(e.response?.status===401&&!r._retry){if(r._retry=!0,process.env[`${o.envPrefix}TOKEN`])return console.error(`\x1B[31mAuthentication failed.\x1B[0m Your ${o.envPrefix}TOKEN may be invalid or expired.
4
+ Generate a new token at the ${o.name} Dashboard \u2192 Settings \u2192 Access Tokens.`),Promise.reject(e);try{let n=await k(c);if(n)return r.headers.Authorization=`Bearer ${n}`,u(r)}catch(n){return Promise.reject(n)}}return Promise.reject(e)});var p=e=>String(e||"").trim().toLowerCase().replace(/_/g,"-");function S(e){let r=p(e);if(["development","dev"].includes(r))return"development";if(["adhoc","ad-hoc","internal","staging","stage","preview"].includes(r))return"adhoc";if(["store","app-store","appstore","production","prod"].includes(r))return"store"}function X(e){if(e.storedDistributionType!==e.requestedDistributionType)throw new Error(`Dashboard stored ${e.credentialType} as ${String(e.storedDistributionType||"unknown")} instead of ${e.requestedDistributionType}.`)}function Z(e){let r=S(e.requestedCredentialType);if(r)return r;let t=e.profile||{};if(t.developmentClient||t.simulator||t.ios?.simulator)return"development";let n=p(t.distribution),a=p(t.iosExportMethod||t.ios?.exportMethod),i=p(t.environment);return a==="app-store"||n==="store"?"store":a==="development"||n==="development"?"development":a==="ad-hoc"||a==="adhoc"||n==="ad-hoc"||n==="adhoc"||n==="internal"?"adhoc":i==="production"||i==="store"?"store":i==="development"||i==="dev"?"development":"adhoc"}export{o as a,T as b,l as c,I as d,$ as e,c as f,B as g,u as h,S as i,X as j,Z as k};