@webority/mobile-tools 0.104.1 → 0.107.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.
Files changed (2) hide show
  1. package/lib/otaNative.js +1 -1
  2. package/package.json +1 -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("appConfigInvalid",`${i} must be a positive whole number, not ${JSON.stringify(n)}`):fail("appConfigInvalid",`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("appConfigInvalid",`${t} is not a readable X.509 certificate`)}const n=i.split("\n").filter(e=>e.startsWith("CN=")),a=1===n.length?n[0].slice(3):"";return"FoodMe OTA Updates"===a?fail("appConfigInvalid",`${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(a)?a:fail("appConfigInvalid",`${t} has common name ${JSON.stringify(a)}, which is not a signing key id (a lowercase name, for example acme)`)};export const otaNativeSettings=(e,t,i,n)=>{const a=t.OTA_UPDATE_BASE_URL;if(!isSet(a)||!isSet(t[COMPONENT_KEY[n]]))return;const s=componentIdFor(t,n),o=t.OTA_PROJECT_ID;if(!isSet(o)||!WHOLE_NUMBER.test(String(o)))return fail("appConfigInvalid",`OTA_PROJECT_ID must be the update server's project id (a positive whole number), not ${JSON.stringify(o)}`);const r=t.OTA_CODE_SIGNING_CERT;if("string"!=typeof r||""===r.trim())return fail("appConfigInvalid","OTA_CODE_SIGNING_CERT must name the PEM certificate file, relative to the app folder");const p=path.join(e,r);if(!existsSync(p))return fail("appConfigInvalid",`OTA_CODE_SIGNING_CERT ${r} not found in ${e}`);const c=readFileSync(p,"utf8").replace(/\r\n/g,"\n").trim();return c.startsWith("-----BEGIN CERTIFICATE-----")?{updateUrl:`${String(a).replace(/\/+$/,"")}/ota/manifest/${o}/${s}/${i}`,runtimeVersion:String(t.VERSION_INTERNAL),certificate:c,keyId:codeSigningKeyIdOf(c,r)}:fail("appConfigInvalid",`${r} is not a PEM certificate`)};const escapeXml=e=>e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"),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 a=[];for(const[e,i]of androidOtaMetaData(t)){const t=escapeXml(i).replace(/\n/g,"&#10;"),s=new RegExp(`(<meta-data\\s+android:name="${escapeRegExp(e)}"\\s+android:value=")[^"]*(")`);s.test(n)?n=n.replace(s,(e,i,n)=>`${i}${t}${n}`):a.push([e,t])}if(0===a.length)return n;const s=/^([ \t]*)<\/application>/m.exec(n);if(!s)return fail("appConfigInvalid","AndroidManifest.xml has no </application> to add the expo-updates settings to");const o=childIndent(n.slice(0,s.index),s[1]),r=a.map(([e,t])=>`${o}<meta-data android:name="${e}" android:value="${t}" />`).join(i);return`${n.slice(0,s.index)}${r}${i}${n.slice(s.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":" "}`,a=Object.entries(e).map(([e,t])=>`${n}<key>${escapeXml(e)}</key>${i}${n}<string>${escapeXml(t)}</string>`).join(i);return`<dict>${i}${a}${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 a=[];for(const[e,s]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(s,e,i)}${n.slice(t.index+t[0].length)}`}else a.push([e,s])}if(0===a.length)return n;const s=/^([ \t]*)<\/dict>\s*<\/plist>/m.exec(n);if(!s)return fail("appConfigInvalid","Expo.plist has no root </dict> to add the expo-updates settings to");const o=childIndent(n.slice(0,s.index),s[1]),r=a.map(([e,t])=>`${o}<key>${escapeXml(e)}</key>${i}${o}${plistValue(t,o,i)}`).join(i);return`${n.slice(0,s.index)}${r}${i}${n.slice(s.index)}`};
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("appConfigInvalid",`${i} must be a positive whole number, not ${JSON.stringify(n)}`):fail("appConfigInvalid",`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("appConfigInvalid",`${t} is not a readable X.509 certificate`)}const n=i.split("\n").filter(e=>e.startsWith("CN=")),a=1===n.length?n[0].slice(3):"";return"FoodMe OTA Updates"===a?fail("appConfigInvalid",`${t} is the shared OTA certificate, 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(a)?a:fail("appConfigInvalid",`${t} has common name ${JSON.stringify(a)}, which is not a signing key id (a lowercase name, for example acme)`)};export const otaNativeSettings=(e,t,i,n)=>{const a=t.OTA_UPDATE_BASE_URL;if(!isSet(a)||!isSet(t[COMPONENT_KEY[n]]))return;const s=componentIdFor(t,n),o=t.OTA_PROJECT_ID;if(!isSet(o)||!WHOLE_NUMBER.test(String(o)))return fail("appConfigInvalid",`OTA_PROJECT_ID must be the update server's project id (a positive whole number), not ${JSON.stringify(o)}`);const r=t.OTA_CODE_SIGNING_CERT;if("string"!=typeof r||""===r.trim())return fail("appConfigInvalid","OTA_CODE_SIGNING_CERT must name the PEM certificate file, relative to the app folder");const p=path.join(e,r);if(!existsSync(p))return fail("appConfigInvalid",`OTA_CODE_SIGNING_CERT ${r} not found in ${e}`);const c=readFileSync(p,"utf8").replace(/\r\n/g,"\n").trim();return c.startsWith("-----BEGIN CERTIFICATE-----")?{updateUrl:`${String(a).replace(/\/+$/,"")}/ota/manifest/${o}/${s}/${i}`,runtimeVersion:String(t.VERSION_INTERNAL),certificate:c,keyId:codeSigningKeyIdOf(c,r)}:fail("appConfigInvalid",`${r} is not a PEM certificate`)};const escapeXml=e=>e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"),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 a=[];for(const[e,i]of androidOtaMetaData(t)){const t=escapeXml(i).replace(/\n/g,"&#10;"),s=new RegExp(`(<meta-data\\s+android:name="${escapeRegExp(e)}"\\s+android:value=")[^"]*(")`);s.test(n)?n=n.replace(s,(e,i,n)=>`${i}${t}${n}`):a.push([e,t])}if(0===a.length)return n;const s=/^([ \t]*)<\/application>/m.exec(n);if(!s)return fail("appConfigInvalid","AndroidManifest.xml has no </application> to add the expo-updates settings to");const o=childIndent(n.slice(0,s.index),s[1]),r=a.map(([e,t])=>`${o}<meta-data android:name="${e}" android:value="${t}" />`).join(i);return`${n.slice(0,s.index)}${r}${i}${n.slice(s.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":" "}`,a=Object.entries(e).map(([e,t])=>`${n}<key>${escapeXml(e)}</key>${i}${n}<string>${escapeXml(t)}</string>`).join(i);return`<dict>${i}${a}${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 a=[];for(const[e,s]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(s,e,i)}${n.slice(t.index+t[0].length)}`}else a.push([e,s])}if(0===a.length)return n;const s=/^([ \t]*)<\/dict>\s*<\/plist>/m.exec(n);if(!s)return fail("appConfigInvalid","Expo.plist has no root </dict> to add the expo-updates settings to");const o=childIndent(n.slice(0,s.index),s[1]),r=a.map(([e,t])=>`${o}<key>${escapeXml(e)}</key>${i}${o}${plistValue(t,o,i)}`).join(i);return`${n.slice(0,s.index)}${r}${i}${n.slice(s.index)}`};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/mobile-tools",
3
- "version": "0.104.1",
3
+ "version": "0.107.0",
4
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",