@webority/mobile-tools 0.19.1 → 0.88.0
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 +0 -20
- package/lib/cli.js +1 -1
- package/lib/otaNative.js +1 -1
- package/package.json +11 -3
- package/lib/ota.js +0 -1
package/README.md
CHANGED
|
@@ -122,26 +122,6 @@ Renders every environment's launcher icons from one square source: an SVG, or a
|
|
|
122
122
|
- Production is clean. Staging carries an orange `STAGING` ribbon across the bottom-right corner, and Development a green `DEV` one. The ribbon is SVG text in a generic sans-serif, with no font file.
|
|
123
123
|
- The same source gives the same bytes on the same machine.
|
|
124
124
|
|
|
125
|
-
### `ota deploy`
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
npx mobile-tools ota deploy --env <Staging|Production> [--platform android|ios] [--rollout <0-100>] [--notes <text>] [--critical]
|
|
129
|
-
npx mobile-tools ota deploy --complete
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
Publishes a JavaScript-only change over the air to the phones already on the current store build. A native change, a permission or an SDK bump goes through the stores instead.
|
|
133
|
-
|
|
134
|
-
1. Reads every id from the app's settings for the environment, never from the command line:
|
|
135
|
-
- `OTA_ANDROID_COMPONENT_ID` and `OTA_IOS_COMPONENT_ID`, the update server's component per platform;
|
|
136
|
-
- `APP_ID`, the identity the server and the app check the update against;
|
|
137
|
-
- `VERSION_INTERNAL`, the runtime version. An update reaches only phones whose build is exactly this number.
|
|
138
|
-
2. **Production only:** asks the stores for the live build, through `webority-play release tracks get` and `webority-appstore status`. A platform whose live build is not `VERSION_INTERNAL` is skipped, because the update would reach nobody (for example, while the new build is still in review). `--force` publishes anyway. A lookup that fails is reported and the platform kept. `--no-store-check` skips the lookup, and `--store-profile <name>` picks the store CLIs' credential profile.
|
|
139
|
-
3. Per platform: runs `prebuild` (which also writes the expo-updates settings, above), then `expo export --platform <platform>` into `node_modules/.cache/mobile-tools/ota/`, then `weborityos ota deploy` with `--app-identifier` and `--yes`.
|
|
140
|
-
4. The first deploy uploads nothing. It raises an approval card per platform and the ids are recorded in `node_modules/.cache/mobile-tools/ota/approvals.json`. Once a person approves, `ota deploy --complete` uploads every recorded bundle. `--approval-id <id>` with `--platform` does the same for one platform in one run.
|
|
141
|
-
5. Prebuilds the environment that was compiled before, so a Development checkout is left as it was.
|
|
142
|
-
|
|
143
|
-
`--dry-run` prints the commands after the store check, and changes nothing. `weborityos` must be on `PATH` and signed in (`weborityos login`). Development is refused: it runs from Metro.
|
|
144
|
-
|
|
145
125
|
### `codemod theme-keys`
|
|
146
126
|
|
|
147
127
|
```bash
|
package/lib/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{parseArgs}from"node:util";import{codemodThemeKeys}from"./codemod.js";import{parseEnvironment}from"./config.js";import{installHooks}from"./hooks.js";import{icons}from"./icons.js";import{
|
|
2
|
+
import{parseArgs}from"node:util";import{codemodThemeKeys}from"./codemod.js";import{parseEnvironment}from"./config.js";import{installHooks}from"./hooks.js";import{icons}from"./icons.js";import{CliError,fail,say,warn}from"./output.js";import{prebuild}from"./prebuild.js";import{run}from"./run.js";import{checkSync,sync,writeStamp}from"./sync.js";const USAGE="mobile-tools <command> [options] (run from the app's root folder)\n\n prebuild --env <Development|Staging|Production> [--platform android|ios]\n prebuild --same-env [--platform android|ios]\n Compile appsettings into appsettings.Compiled.json and stamp the native projects.\n\n sync [--check | --stamp]\n --check: exit 1 with the fix command when node_modules (or ios/Pods) is stale.\n --stamp: record that node_modules matches the lockfile now (run it from postinstall).\n Without either: install from the lockfile, stamp, and reset native codegen when needed.\n\n hooks install\n Add git post-merge and post-checkout hooks that run sync --check.\n\n run --env <Environment> [--platform android|ios] [--device <serial|name>]\n Build for one device's ABI, install it there, reuse or start Metro, and launch.\n\n icons [--source <png|svg>]\n Render per-environment launcher icons for Android flavors and iOS AppIcon-<Env> sets.\n\n codemod theme-keys [--dry-run]\n Rewrite theme overrides onto the installed mobile-ui's key names; list what needs a person.",parse=(e,o,n=!1)=>{try{const{values:s,positionals:t}=parseArgs({args:e,options:o,allowPositionals:n,strict:!0});return{values:s,positionals:t}}catch(e){return fail(`${e.message}\n\n${USAGE}`,2)}},envOption=e=>"string"==typeof e?parseEnvironment(e):void 0,platformOption=e=>{if(void 0!==e)return"android"!==e&&"ios"!==e?fail(`--platform must be android or ios, not ${String(e)}`,2):e},main=async e=>{const[o,...n]=e,s=process.cwd();switch(o){case"prebuild":{const{values:e}=parse(n,{env:{type:"string"},"same-env":{type:"boolean"},platform:{type:"string"}});return void prebuild({appRoot:s,env:envOption(e.env),sameEnv:!0===e["same-env"],platform:platformOption(e.platform)})}case"sync":{const{values:e}=parse(n,{check:{type:"boolean"},stamp:{type:"boolean"}});if(e.check&&e.stamp&&fail("use --check or --stamp, not both",2),e.stamp)return writeStamp(s),void say("sync --stamp: recorded node_modules as matching the lockfile");if(e.check){const e=checkSync(s,process.platform);return e.length>0&&fail(e.join("\n")),void say("sync --check: node_modules matches the lockfile")}return void sync(s,process.platform)}case"hooks":{const{positionals:e}=parse(n,{},!0);return"install"===e[0]&&1===e.length||fail(`usage: mobile-tools hooks install\n\n${USAGE}`,2),void installHooks(s)}case"run":{const{values:e}=parse(n,{env:{type:"string"},platform:{type:"string"},device:{type:"string"}}),o=envOption(e.env)??fail("run needs --env <Development|Staging|Production>",2);return void await run({appRoot:s,env:o,platform:platformOption(e.platform)??"android",device:"string"==typeof e.device?e.device:void 0})}case"codemod":{const{values:e,positionals:o}=parse(n,{"dry-run":{type:"boolean"}},!0);1===o.length&&"theme-keys"===o[0]||fail(`usage: mobile-tools codemod theme-keys [--dry-run]\n\n${USAGE}`,2);const t=!0===e["dry-run"],r=codemodThemeKeys({appRoot:s,dryRun:t});for(const e of r.changedFiles)say(`${t?"would update":"updated"} ${e}`);for(const e of r.findings)say(`${e.file}:${e.line} ${e.path}: ${e.message}`);return r.findings.length>0&&fail(`${r.findings.length} item(s) above need a person; the app refuses retired keys at startup until they are fixed`),void say("codemod theme-keys: done")}case"icons":{const{values:e}=parse(n,{source:{type:"string"}});return void await icons({appRoot:s,source:"string"==typeof e.source?e.source:void 0})}case void 0:case"help":case"--help":case"-h":return void say(USAGE);default:fail(`unknown command "${o}"\n\n${USAGE}`,2)}};main(process.argv.slice(2)).catch(e=>{e instanceof CliError&&(warn(`mobile-tools: ${e.message}`),process.exit(e.exitCode)),warn(e instanceof Error?e.stack??e.message:String(e)),process.exit(1)});
|
package/lib/otaNative.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{X509Certificate}from"node:crypto";import{existsSync,readFileSync}from"node:fs";import path from"node:path";import{fail}from"./output.js";export const COMPONENT_KEY={android:"OTA_ANDROID_COMPONENT_ID",ios:"OTA_IOS_COMPONENT_ID"};const CODE_SIGNING_ALG="rsa-v1_5-sha256",KEY_ID=/^[a-z][a-z0-9]*$/,WHOLE_NUMBER=/^[1-9][0-9]*$/,isSet=e=>null!=e&&""!==e;export const componentIdFor=(e,t)=>{const i=COMPONENT_KEY[t],n=e[i];return isSet(n)?WHOLE_NUMBER.test(String(n))?String(n):fail(`${i} must be a positive whole number, not ${JSON.stringify(n)}`):fail(`appsettings has no ${i}: the ${t} app has no OTA component to publish to`)};export const codeSigningKeyIdOf=(e,t)=>{let i;try{i=new X509Certificate(e).subject}catch{return fail(`${t} is not a readable X.509 certificate`)}const n=i.split("\n").filter(e=>e.startsWith("CN=")),s=1===n.length?n[0].slice(3):"";return"FoodMe OTA Updates"===s?fail(`${t} is the shared OTA certificate (CN=FoodMe OTA Updates), which is retired for this app: get the product's own certificate with \`bash scripts/ota-signing-key.sh cert <keyid> --out certs/ota-code-signing-cert.pem\` in webority-os`):KEY_ID.test(s)?s:fail(`${t} has common name ${JSON.stringify(s)}, which is not a signing key id (a lowercase name, for example
|
|
1
|
+
import{X509Certificate}from"node:crypto";import{existsSync,readFileSync}from"node:fs";import path from"node:path";import{fail}from"./output.js";export const COMPONENT_KEY={android:"OTA_ANDROID_COMPONENT_ID",ios:"OTA_IOS_COMPONENT_ID"};const CODE_SIGNING_ALG="rsa-v1_5-sha256",KEY_ID=/^[a-z][a-z0-9]*$/,WHOLE_NUMBER=/^[1-9][0-9]*$/,isSet=e=>null!=e&&""!==e;export const componentIdFor=(e,t)=>{const i=COMPONENT_KEY[t],n=e[i];return isSet(n)?WHOLE_NUMBER.test(String(n))?String(n):fail(`${i} must be a positive whole number, not ${JSON.stringify(n)}`):fail(`appsettings has no ${i}: the ${t} app has no OTA component to publish to`)};export const codeSigningKeyIdOf=(e,t)=>{let i;try{i=new X509Certificate(e).subject}catch{return fail(`${t} is not a readable X.509 certificate`)}const n=i.split("\n").filter(e=>e.startsWith("CN=")),s=1===n.length?n[0].slice(3):"";return"FoodMe OTA Updates"===s?fail(`${t} is the shared OTA certificate (CN=FoodMe OTA Updates), which is retired for this app: get the product's own certificate with \`bash scripts/ota-signing-key.sh cert <keyid> --out certs/ota-code-signing-cert.pem\` in webority-os`):KEY_ID.test(s)?s:fail(`${t} has common name ${JSON.stringify(s)}, which is not a signing key id (a lowercase name, for example acme)`)};export const otaNativeSettings=(e,t,i,n)=>{const s=t.OTA_UPDATE_BASE_URL;if(!isSet(s)||!isSet(t[COMPONENT_KEY[n]]))return;const r=componentIdFor(t,n),o=t.OTA_PROJECT_ID;if(!isSet(o)||!WHOLE_NUMBER.test(String(o)))return fail(`OTA_PROJECT_ID must be the update server's project id (a positive whole number), not ${JSON.stringify(o)}`);const a=t.OTA_CODE_SIGNING_CERT;if("string"!=typeof a||""===a.trim())return fail("OTA_CODE_SIGNING_CERT must name the PEM certificate file, relative to the app folder");const c=path.join(e,a);if(!existsSync(c))return fail(`OTA_CODE_SIGNING_CERT ${a} not found in ${e}`);const p=readFileSync(c,"utf8").replace(/\r\n/g,"\n").trim();return p.startsWith("-----BEGIN CERTIFICATE-----")?{updateUrl:`${String(s).replace(/\/+$/,"")}/ota/manifest/${o}/${r}/${i}`,runtimeVersion:String(t.VERSION_INTERNAL),certificate:p,keyId:codeSigningKeyIdOf(p,a)}:fail(`${a} is not a PEM certificate`)};const escapeXml=e=>e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""),escapeRegExp=e=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),eolOf=e=>e.includes("\r\n")?"\r\n":"\n";export const androidOtaMetaData=e=>[["expo.modules.updates.ENABLED","true"],["expo.modules.updates.EXPO_UPDATE_URL",e.updateUrl],["expo.modules.updates.EXPO_RUNTIME_VERSION",e.runtimeVersion],["expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH","ALWAYS"],["expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS","0"],["expo.modules.updates.CODE_SIGNING_CERTIFICATE",e.certificate],["expo.modules.updates.CODE_SIGNING_METADATA",JSON.stringify({keyid:e.keyId,alg:CODE_SIGNING_ALG})]];const childIndent=(e,t)=>{const i=e.split(/\r?\n/).filter(e=>""!==e.trim()),n=/^[ \t]*/.exec(i[i.length-1]??"")?.[0]??"";return n.length>t.length?n:`${t} `};export const rewriteAndroidManifestOta=(e,t)=>{const i=eolOf(e);let n=e;const s=[];for(const[e,i]of androidOtaMetaData(t)){const t=escapeXml(i).replace(/\n/g," "),r=new RegExp(`(<meta-data\\s+android:name="${escapeRegExp(e)}"\\s+android:value=")[^"]*(")`);r.test(n)?n=n.replace(r,(e,i,n)=>`${i}${t}${n}`):s.push([e,t])}if(0===s.length)return n;const r=/^([ \t]*)<\/application>/m.exec(n);if(!r)return fail("AndroidManifest.xml has no </application> to add the expo-updates settings to");const o=childIndent(n.slice(0,r.index),r[1]),a=s.map(([e,t])=>`${o}<meta-data android:name="${e}" android:value="${t}" />`).join(i);return`${n.slice(0,r.index)}${a}${i}${n.slice(r.index)}`};export const iosOtaPlistKeys=e=>[["EXUpdatesEnabled",!0],["EXUpdatesURL",e.updateUrl],["EXUpdatesRuntimeVersion",e.runtimeVersion],["EXUpdatesCheckOnLaunch","ALWAYS"],["EXUpdatesLaunchWaitMs",0],["EXUpdatesCodeSigningCertificate",e.certificate],["EXUpdatesCodeSigningMetadata",{keyid:e.keyId,alg:CODE_SIGNING_ALG}]];const plistValue=(e,t,i)=>{if("boolean"==typeof e)return e?"<true/>":"<false/>";if("number"==typeof e)return`<integer>${e}</integer>`;if("string"==typeof e)return`<string>${escapeXml(e).replace(/\n/g,i)}</string>`;const n=`${t}${t.includes("\t")?"\t":" "}`,s=Object.entries(e).map(([e,t])=>`${n}<key>${escapeXml(e)}</key>${i}${n}<string>${escapeXml(t)}</string>`).join(i);return`<dict>${i}${s}${i}${t}</dict>`},PLIST_VALUE=String.raw`(?:<(?:true|false)\/>|<string>[\s\S]*?<\/string>|<integer>[^<]*<\/integer>|<dict>[\s\S]*?<\/dict>|<dict\/>)`;export const rewriteExpoPlistOta=(e,t)=>{const i=eolOf(e);let n=e;const s=[];for(const[e,r]of iosOtaPlistKeys(t)){const t=new RegExp(`([ \\t]*)(<key>${escapeRegExp(e)}<\\/key>\\s*)${PLIST_VALUE}`).exec(n);if(t){const e=t[1];n=`${n.slice(0,t.index)}${e}${t[2]}${plistValue(r,e,i)}${n.slice(t.index+t[0].length)}`}else s.push([e,r])}if(0===s.length)return n;const r=/^([ \t]*)<\/dict>\s*<\/plist>/m.exec(n);if(!r)return fail("Expo.plist has no root </dict> to add the expo-updates settings to");const o=childIndent(n.slice(0,r.index),r[1]),a=s.map(([e,t])=>`${o}<key>${escapeXml(e)}</key>${i}${o}${plistValue(t,o,i)}`).join(i);return`${n.slice(0,r.index)}${a}${i}${n.slice(r.index)}`};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/mobile-tools",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Dev-time CLI for Webority React Native apps: one prebuild, a dependency guard, a device-aware run
|
|
3
|
+
"version": "0.88.0",
|
|
4
|
+
"description": "Dev-time CLI for Webority React Native apps: one prebuild, a dependency guard, a device-aware run and per-environment launcher icons, the same on Windows and macOS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
7
7
|
"cli",
|
|
@@ -42,8 +42,16 @@
|
|
|
42
42
|
"access": "public",
|
|
43
43
|
"registry": "https://registry.npmjs.org/"
|
|
44
44
|
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@babel/parser": "7.29.8",
|
|
47
|
+
"@types/node": "25.6.0",
|
|
48
|
+
"jest": "29.7.0",
|
|
49
|
+
"rimraf": "6.1.3",
|
|
50
|
+
"typescript": "5.9.3",
|
|
51
|
+
"@webority/mobile-dev": "0.0.0"
|
|
52
|
+
},
|
|
45
53
|
"scripts": {
|
|
46
|
-
"build": "tsc -p tsconfig.build.json && node
|
|
54
|
+
"build": "rimraf lib && tsc -p tsconfig.build.json && node ../mobile-dev/scripts/minify-lib.mjs",
|
|
47
55
|
"clean": "rimraf lib",
|
|
48
56
|
"lint": "biome lint ./src",
|
|
49
57
|
"lint:fix": "biome lint --write ./src",
|
package/lib/ota.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{existsSync,mkdirSync,readFileSync,rmSync,writeFileSync}from"node:fs";import{createRequire}from"node:module";import path from"node:path";import{COMPILED_FILE,lastCompiledEnvironment,loadAppSettings,loadToolsConfig,resolveIosProjectName}from"./config.js";import{capture,runInherit}from"./exec.js";import{componentIdFor}from"./otaNative.js";import{fail,say,warn}from"./output.js";import{findOnPath}from"./platform.js";import{prebuild}from"./prebuild.js";import{logDir}from"./run.js";export const otaDir=o=>path.join(logDir(o),"ota");const manifestFile=o=>path.join(otaDir(o),"approvals.json"),readManifest=o=>{const t=manifestFile(o);return existsSync(t)?JSON.parse(readFileSync(t,"utf8")):{entries:[]}},writeManifest=(o,t)=>{mkdirSync(otaDir(o),{recursive:!0}),writeFileSync(manifestFile(o),`${JSON.stringify(t,null,2)}\n`,"utf8")};export const upsertEntry=(o,t)=>{const e=o.entries.findIndex(o=>o.componentId===t.componentId&&o.environment===t.environment);e>=0?o.entries[e]=t:o.entries.push(t)};export const parseApprovalId=o=>/approvalId\W+(\d+)/i.exec(o)?.[1]??null;export const deployArgs=(o,t,e)=>{const r=["ota","deploy",o.outDir,"--component",o.componentId,"--environment",o.environment,"--runtime-version",o.runtimeVersion,"--app-identifier",o.appIdentifier];return void 0!==t.rollout&&r.push("--rollout",t.rollout),t.notes&&r.push("--notes",t.notes),t.critical&&r.push("--critical"),e&&r.push("--approval-id",e),r.push("--yes"),r};export const liveAndroidBuild=o=>{const t=o?.releases,e=(t??[]).filter(o=>"completed"===o.status||"inProgress"===o.status).flatMap(o=>o.versionCodes??[]).map(Number).filter(Number.isFinite);return e.length>0?Math.max(...e):null};export const liveIosBuild=o=>{const t=o?.platforms?.IOS?.live?.build?.buildNumber;return void 0===t?null:Number(t)};const storeCli=o=>findOnPath(o,process.env,process.platform)??fail(`${o} not found on PATH: install the Webority store CLIs, or pass --no-store-check`),runJson=(o,t,e)=>{const r=capture(o,t);if(0!==r.status)return fail(`${e} failed: ${(r.stderr||r.stdout).trim().split("\n")[0]}`);try{return JSON.parse(r.stdout)}catch{return fail(`${e} printed no JSON`)}},liveStoreBuild=(o,t,e)=>{const r=e.storeProfile?["--profile",e.storeProfile]:[];return"android"===o?liveAndroidBuild(runJson({file:storeCli("webority-play"),args:[...r,"release","tracks","get","--package-name",t,"--track","production"]},e.appRoot,"webority-play release tracks get")):liveIosBuild(runJson({file:storeCli("webority-appstore"),args:[...r,"status","--app",t,"--platform","IOS","--builds","1"]},e.appRoot,"webority-appstore status"))},reachablePlatforms=(o,t,e,r)=>{const n=[];for(const i of o){let o;try{o=liveStoreBuild(i,t.APP_ID,r)}catch(o){warn(`ota: ${i}: could not read the live store build (${o.message}); keeping it`),n.push(i);continue}if(o===Number(e)){say(`ota: ${i}: live store build ${o} matches runtime ${e}`),n.push(i);continue}const a=`live store build ${o??"none"} is not runtime ${e}, so the update would reach nobody`;r.force?(say(`ota: ${i}: ${a}; publishing anyway (--force)`),n.push(i)):say(`ota: ${i}: skipped, ${a}`)}return n},weborityos=()=>findOnPath("weborityos",process.env,process.platform)??fail("weborityos not found on PATH: install the Webority OS CLI and run weborityos login"),expoCli=o=>{try{return createRequire(path.join(o,"package.json")).resolve("expo/bin/cli")}catch{return fail("expo is not installed in this app; run npx mobile-tools sync")}},runDeploy=(o,t,e)=>{const r=capture({file:weborityos(),args:deployArgs(o,t,e)},t.appRoot),n=`${r.stdout}${r.stderr}`;""!==n.trim()&&say(n.trimEnd());const i=e?null:parseApprovalId(n);return i?{...o,status:"pending-approval",approvalId:i}:0!==r.status?fail(`weborityos ota deploy failed for ${o.platform} (exit ${r.status})`):{...o,status:"published",approvalId:e}},completeFromManifest=o=>{const t=readManifest(o.appRoot),e=t.entries.filter(o=>"pending-approval"===o.status);if(0===e.length)return void say("ota: nothing awaits upload; no bundle is pending approval");const r=[];for(const n of e)if(existsSync(n.outDir)){say(`ota: ${n.platform} ${n.environment}: uploading under approval ${n.approvalId}`);try{upsertEntry(t,runDeploy(n,o,n.approvalId))}catch(o){r.push(`${n.platform} ${n.environment}: ${o.message}`)}}else r.push(`${n.platform} ${n.environment}: the bundle is gone, deploy again`);writeManifest(o.appRoot,t),r.length>0&&fail(`ota: still pending:\n ${r.join("\n ")}`),say("ota: every approved bundle is published")},platformsOf=(o,t)=>{if(t)return[t];const e=[];return existsSync(path.join(o,"android"))&&e.push("android"),resolveIosProjectName(o,loadToolsConfig(o))&&e.push("ios"),e.length>0?e:fail(`no android/ or ios/ folder in ${o}`)};export const otaDeploy=o=>{const{appRoot:t}=o;if(o.complete)return void completeFromManifest(o);const e=o.env??fail("ota deploy needs --env <Staging|Production>",2);"Development"===e&&fail("ota deploy publishes to Staging or Production; a Development build runs from Metro",2),void 0===o.rollout||/^(100|[1-9]?[0-9])$/.test(o.rollout)||fail(`--rollout must be a whole number from 0 to 100, not ${o.rollout}`,2),void 0!==o.approvalId&&void 0===o.platform&&fail("--approval-id belongs to one platform: pass --platform with it",2);const r=loadAppSettings(t,e),n=String(r.VERSION_INTERNAL);let i=platformsOf(t,o.platform);const a=new Map(i.map(o=>[o,componentIdFor(r,o)]));if(o.storeCheck&&"Production"===e?i=reachablePlatforms(i,r,n,o):"Production"===e&&say("ota: live store check skipped (--no-store-check)"),0===i.length)return void say("ota: nothing left to publish");const s=i.map(o=>({platform:o,componentId:a.get(o),appIdentifier:r.APP_ID,runtimeVersion:n,environment:e,outDir:path.join(otaDir(t),`${o}-${e.toLowerCase()}`)}));if(o.dryRun){for(const t of s)say(`ota (dry run): ${t.platform}`),say(` mobile-tools prebuild --env ${e} --platform ${t.platform}`),say(` expo export --platform ${t.platform} --output-dir ${t.outDir}`),say(` weborityos ${deployArgs(t,o,o.approvalId??null).join(" ")}`);return}weborityos();const p=expoCli(t),l=existsSync(path.join(t,COMPILED_FILE))?lastCompiledEnvironment(t):void 0,u=readManifest(t),d=[];try{for(const r of s){say(`ota: ${r.platform} to ${e}`);try{prebuild({appRoot:t,env:e,platform:r.platform}),rmSync(r.outDir,{recursive:!0,force:!0}),runInherit({file:process.execPath,args:[p,"export","--platform",r.platform,"--output-dir",r.outDir]},t,"expo export");const n=runDeploy(r,o,o.approvalId??null);upsertEntry(u,n),writeManifest(t,u)}catch(o){d.push(`${r.platform}: ${o.message}`)}}}finally{l&&l!==e&&prebuild({appRoot:t,env:l})}const f=u.entries.filter(o=>"pending-approval"===o.status&&o.environment===e);for(const o of f)say(`ota: ${o.platform} ${e}: exported, awaiting approval ${o.approvalId}; nothing uploaded`);f.length>0&&say("ota: once approved, run: npx mobile-tools ota deploy --complete"),d.length>0&&fail(`ota: failed:\n ${d.join("\n ")}`)};
|