@webority/mobile-tools 0.15.0 → 0.16.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/lib/metro.js CHANGED
@@ -1 +1 @@
1
- import{createRequire}from"node:module";import{createServer}from"node:net";import path from"node:path";import{startDetached}from"./exec.js";import{fail,say}from"./output.js";export const DEFAULT_METRO_PORT=8081;const samePath=(t,e)=>{const norm=t=>{const e=path.resolve(t).replace(/[\\/]+$/,"");return"win32"===process.platform?e.toLowerCase():e};return norm(t)===norm(e)};export const classifyStatus=(t,e,r)=>{if(!t.includes("packager-status:running"))return"other";const o=e??/project-root:(.+)/.exec(t)?.[1]?.trim();return o&&samePath(o,r)?"ours":"other"};const portIsFree=t=>new Promise(e=>{const r=createServer();r.once("error",()=>e(!1)),r.once("listening",()=>r.close(()=>e(!0))),r.listen(t)});export const probeMetro=async(t,e)=>{try{const r=await fetch(`http://localhost:${t}/status`,{signal:AbortSignal.timeout(1e3)});return classifyStatus(await r.text(),r.headers.get("X-React-Native-Project-Root"),e)}catch{return await portIsFree(t)?"free":"other"}};const waitForMetro=async(t,e,r)=>{const o=Date.now()+12e4;for(;Date.now()<o;){if("ours"===await probeMetro(t,e))return;await new Promise(t=>setTimeout(t,500))}fail(`Metro did not answer on port ${t} within 2 minutes; see ${r}`)};export const ensureMetro=async(t,e,{fixedPort:r})=>{const o=r?8081:8100;for(let r=8081;r<=o;r++){const o=await probeMetro(r,t);if("ours"===o)return say(`metro: reusing the server on port ${r}`),r;if("free"===o){let o;try{o=createRequire(path.join(t,"package.json")).resolve("react-native/cli.js")}catch{return fail("react-native is not installed in this app; run npx mobile-tools sync")}return startDetached({file:process.execPath,args:[o,"start","--port",String(r)]},t,e),say(`metro: started on port ${r}, log ${e}`),await waitForMetro(r,t,e),r}}return fail(r?"port 8081 is held by another server (the iOS app always looks there); stop it and rerun":"ports 8081-8100 are all held by other servers; stop one and rerun")};
1
+ import{createRequire}from"node:module";import{createServer}from"node:net";import path from"node:path";import{startDetached}from"./exec.js";import{fail,say}from"./output.js";export const DEFAULT_METRO_PORT=8081;const samePath=(e,t)=>{const norm=e=>{const t=path.resolve(e).replace(/[\\/]+$/,"");return"win32"===process.platform?t.toLowerCase():t};return norm(e)===norm(t)};export const classifyStatus=(e,t,r)=>{if(!e.includes("packager-status:running"))return"other";const o=t??/project-root:(.+)/.exec(e)?.[1]?.trim();return o&&samePath(o,r)?"ours":"other"};const portIsFree=e=>new Promise(t=>{const r=createServer();r.once("error",()=>t(!1)),r.once("listening",()=>r.close(()=>t(!0))),r.listen(e)});export const probeMetro=async(e,t)=>{try{const r=await fetch(`http://localhost:${e}/status`,{signal:AbortSignal.timeout(1e3)});return classifyStatus(await r.text(),r.headers.get("X-React-Native-Project-Root"),t)}catch{return await portIsFree(e)?"free":"other"}};const waitForMetro=async(e,t,r)=>{const o=Date.now()+12e4;for(;Date.now()<o;){if("ours"===await probeMetro(e,t))return;await new Promise(e=>setTimeout(e,500))}fail(`Metro did not answer on port ${e} within 2 minutes; see ${r}`)};export const ensureMetro=async(e,t,{fixedPort:r})=>{const o=r?8081:8100;for(let r=8081;r<=o;r++){const o=await probeMetro(r,e);if("ours"===o)return say(`metro: reusing the server on port ${r}`),r;if("free"===o){let o;try{o=createRequire(path.join(e,"package.json")).resolve("expo/bin/cli")}catch{return fail("expo is not installed in this app; run npx mobile-tools sync")}return startDetached({file:process.execPath,args:[o,"start","--port",String(r)]},e,t),say(`metro: started on port ${r}, log ${t}`),await waitForMetro(r,e,t),r}}return fail(r?"port 8081 is held by another server (the iOS app always looks there); stop it and rerun":"ports 8081-8100 are all held by other servers; stop one and rerun")};
package/lib/sync.js CHANGED
@@ -1 +1 @@
1
- import{createHash}from"node:crypto";import{existsSync,readdirSync,readFileSync,rmSync,statSync,writeFileSync}from"node:fs";import path from"node:path";import{runInherit}from"./exec.js";import{fail,say}from"./output.js";import{findOnPath,gradleInvocation,npmInvocation,pnpmInvocation}from"./platform.js";export const STAMP_FILE=path.join("node_modules",".webority-install.json");const INSTALL_FIELDS=["dependencies","devDependencies","optionalDependencies","peerDependencies","bundleDependencies","overrides","resolutions","pnpm","workspaces"];export const lockfileOf=e=>{const n=path.join(e,"pnpm-lock.yaml"),t=path.join(e,"package-lock.json");return existsSync(n)&&existsSync(t)?fail("both pnpm-lock.yaml and package-lock.json exist: delete the one not in use"):existsSync(n)?{manager:"pnpm",file:n}:existsSync(t)?{manager:"npm",file:t}:fail(`no package-lock.json or pnpm-lock.yaml in ${e}`)};const filesUnder=(e,n="")=>readdirSync(e,{withFileTypes:!0}).flatMap(t=>{const o=""===n?t.name:`${n}/${t.name}`;return t.isDirectory()?filesUnder(path.join(e,t.name),o):[o]}).sort();export const installHash=e=>{const{file:n}=lockfileOf(e),t=JSON.parse(readFileSync(path.join(e,"package.json"),"utf8")),o=createHash("sha256");o.update(`lockfile:${path.basename(n)}\0`),o.update(readFileSync(n)),o.update("\0package.json\0"),o.update(JSON.stringify(INSTALL_FIELDS.map(e=>[e,t[e]??null])));const s=path.join(e,"patches");if(existsSync(s))for(const e of filesUnder(s))o.update(`\0patches/${e}\0`),o.update(readFileSync(path.join(s,...e.split("/"))));return o.digest("hex")};export const readStamp=e=>{const n=path.join(e,STAMP_FILE);if(existsSync(n))try{return JSON.parse(readFileSync(n,"utf8"))}catch{return}};const podFix=e=>existsSync(path.join(e,"Gemfile"))?"cd ios && bundle exec pod install":"cd ios && pod install";export const podsStale=e=>{const n=path.join(e,"ios","Podfile.lock"),t=path.join(e,"ios","Pods","Manifest.lock");return!(!existsSync(n)||!existsSync(t))&&!readFileSync(n).equals(readFileSync(t))};export const checkSync=(e,n)=>{const t=[],o=readStamp(e),{file:s}=lockfileOf(e);return o?o.hash!==installHash(e)&&t.push(`node_modules is stale: ${path.basename(s)}, package.json or patches/ changed since the last install. Fix: npx mobile-tools sync`):t.push("node_modules has no install stamp (never installed through mobile-tools). Fix: npx mobile-tools sync"),"darwin"===n&&podsStale(e)&&t.push(`ios/Pods is stale: ios/Podfile.lock differs from ios/Pods/Manifest.lock. Fix: ${podFix(e)}`),t};export const nativePackages=e=>{const n=path.join(e,"node_modules");if(!existsSync(n))return{};const t=[];for(const e of readdirSync(n,{withFileTypes:!0}))if(!e.name.startsWith(".")&&e.isDirectory())if(e.name.startsWith("@"))for(const o of readdirSync(path.join(n,e.name),{withFileTypes:!0}))o.isDirectory()&&t.push(path.join(n,e.name,o.name));else t.push(path.join(n,e.name));const o={};for(const e of t){const n=path.join(e,"android"),t=path.join(e,"package.json");if(existsSync(n)&&statSync(n).isDirectory()&&existsSync(t))try{const e=JSON.parse(readFileSync(t,"utf8"));e.name&&e.version&&(o[e.name]=e.version)}catch{}}return o};export const changedNativePackages=(e,n)=>[...new Set([...Object.keys(e),...Object.keys(n)])].filter(t=>e[t]!==n[t]).sort();export const writeStamp=e=>{const{manager:n}=lockfileOf(e),t={hash:installHash(e),manager:n,nativePackages:nativePackages(e)};return writeFileSync(path.join(e,STAMP_FILE),`${JSON.stringify(t,null,2)}\n`,"utf8"),t};export const installInvocation=(e,n)=>"pnpm"===e?pnpmInvocation(["install","--frozen-lockfile"],process.env,n,process.execPath):npmInvocation(["ci"],process.env,n,process.execPath);export const sync=(e,n)=>{const{manager:t}=lockfileOf(e),o=readStamp(e)?.nativePackages??nativePackages(e);say("sync: "+("pnpm"===t?"pnpm install --frozen-lockfile":"npm ci")),runInherit(installInvocation(t,n),e,`${t} install`);const s=writeStamp(e).nativePackages,a=changedNativePackages(o,s),i=path.join(e,"android"),c=existsSync(path.join(i,"app",".cxx"))||existsSync(path.join(i,"app","build"));if(a.length>0&&c){say(`sync: native packages changed (${a.join(", ")}): regenerating codegen`),runInherit(gradleInvocation(i,["generateCodegenArtifactsFromSchema"],n,process.env),i,"gradlew generateCodegenArtifactsFromSchema");for(const n of[path.join(i,"app",".cxx"),path.join(i,"app","build","generated","autolinking")])rmSync(n,{recursive:!0,force:!0}),say(`sync: removed ${path.relative(e,n)}`)}if("darwin"===n&&existsSync(path.join(e,"ios","Podfile"))){const t=path.join(e,"ios","Podfile.lock"),o=path.join(e,"ios","Pods","Manifest.lock");if(!existsSync(t)||!existsSync(o)||podsStale(e)){const t=existsSync(path.join(e,"Gemfile")),o=findOnPath(t?"bundle":"pod",process.env,n);o||fail(`${t?"bundle":"pod"} not found on PATH; install CocoaPods, then ${podFix(e)}`),runInherit({file:o,args:t?["exec","pod","install"]:["install"]},path.join(e,"ios"),"pod install");const s=path.join(e,"ios","build","ModuleCache.noindex");existsSync(s)&&(rmSync(s,{recursive:!0,force:!0}),say(`sync: removed ${path.relative(e,s)}`))}}say("sync: done")};
1
+ import{createHash}from"node:crypto";import{existsSync,readdirSync,readFileSync,rmSync,statSync,writeFileSync}from"node:fs";import path from"node:path";import{runInherit}from"./exec.js";import{fail,say}from"./output.js";import{findOnPath,gradleInvocation,npmInvocation,pnpmInvocation}from"./platform.js";export const STAMP_FILE=path.join("node_modules",".webority-install.json");const INSTALL_FIELDS=["dependencies","devDependencies","optionalDependencies","peerDependencies","bundleDependencies","overrides","resolutions","pnpm","workspaces"];export const lockfileOf=e=>{const n=path.join(e,"pnpm-lock.yaml"),t=path.join(e,"package-lock.json");return existsSync(n)&&existsSync(t)?fail("both pnpm-lock.yaml and package-lock.json exist: delete the one not in use"):existsSync(n)?{manager:"pnpm",file:n}:existsSync(t)?{manager:"npm",file:t}:fail(`no package-lock.json or pnpm-lock.yaml in ${e}`)};const filesUnder=(e,n="")=>readdirSync(e,{withFileTypes:!0}).flatMap(t=>{const o=""===n?t.name:`${n}/${t.name}`;return t.isDirectory()?filesUnder(path.join(e,t.name),o):[o]}).sort();export const installHash=e=>{const{file:n}=lockfileOf(e),t=JSON.parse(readFileSync(path.join(e,"package.json"),"utf8")),o=createHash("sha256");o.update(`lockfile:${path.basename(n)}\0`),o.update(readFileSync(n)),o.update("\0package.json\0"),o.update(JSON.stringify(INSTALL_FIELDS.map(e=>[e,t[e]??null])));const s=path.join(e,"patches");if(existsSync(s))for(const e of filesUnder(s))o.update(`\0patches/${e}\0`),o.update(readFileSync(path.join(s,...e.split("/"))));return o.digest("hex")};export const readStamp=e=>{const n=path.join(e,STAMP_FILE);if(existsSync(n))try{return JSON.parse(readFileSync(n,"utf8"))}catch{return}};const podFix=e=>existsSync(path.join(e,"Gemfile"))?"cd ios && bundle exec pod install":"cd ios && pod install";export const podsStale=e=>{const n=path.join(e,"ios","Podfile.lock"),t=path.join(e,"ios","Pods","Manifest.lock");return!(!existsSync(n)||!existsSync(t))&&!readFileSync(n).equals(readFileSync(t))};export const checkSync=(e,n)=>{const t=[],o=readStamp(e),{file:s}=lockfileOf(e);if(o?o.hash!==installHash(e)&&t.push(`node_modules is stale: ${path.basename(s)}, package.json or patches/ changed since the last install. Fix: npx mobile-tools sync`):t.push("node_modules has no install stamp (never installed through mobile-tools). Fix: npx mobile-tools sync"),o){const n=changedNativePackages(o.nativePackages??{},nativePackages(e));n.length>0&&t.push(`node_modules native packages changed since the last install: ${n.join(", ")}. Fix: npx mobile-tools sync`)}return"darwin"===n&&podsStale(e)&&t.push(`ios/Pods is stale: ios/Podfile.lock differs from ios/Pods/Manifest.lock. Fix: ${podFix(e)}`),t};export const nativePackages=e=>{const n=path.join(e,"node_modules");if(!existsSync(n))return{};const t=[];for(const e of readdirSync(n,{withFileTypes:!0}))if(!e.name.startsWith(".")&&e.isDirectory())if(e.name.startsWith("@"))for(const o of readdirSync(path.join(n,e.name),{withFileTypes:!0}))o.isDirectory()&&t.push(path.join(n,e.name,o.name));else t.push(path.join(n,e.name));const o={};for(const e of t){const n=path.join(e,"android"),t=path.join(e,"package.json");if(existsSync(n)&&statSync(n).isDirectory()&&existsSync(t))try{const e=JSON.parse(readFileSync(t,"utf8"));e.name&&e.version&&(o[e.name]=e.version)}catch{}}return o};export const changedNativePackages=(e,n)=>[...new Set([...Object.keys(e),...Object.keys(n)])].filter(t=>e[t]!==n[t]).sort();export const writeStamp=e=>{const{manager:n}=lockfileOf(e),t={hash:installHash(e),manager:n,nativePackages:nativePackages(e)};return writeFileSync(path.join(e,STAMP_FILE),`${JSON.stringify(t,null,2)}\n`,"utf8"),t};export const installInvocation=(e,n)=>"pnpm"===e?pnpmInvocation(["install","--frozen-lockfile"],process.env,n,process.execPath):npmInvocation(["ci"],process.env,n,process.execPath);export const sync=(e,n)=>{const{manager:t}=lockfileOf(e),o=readStamp(e)?.nativePackages??nativePackages(e);say("sync: "+("pnpm"===t?"pnpm install --frozen-lockfile":"npm ci")),runInherit(installInvocation(t,n),e,`${t} install`);const s=writeStamp(e).nativePackages,a=changedNativePackages(o,s),i=path.join(e,"android"),c=existsSync(path.join(i,"app",".cxx"))||existsSync(path.join(i,"app","build"));if(a.length>0&&c){say(`sync: native packages changed (${a.join(", ")}): regenerating codegen`),runInherit(gradleInvocation(i,["generateCodegenArtifactsFromSchema"],n,process.env),i,"gradlew generateCodegenArtifactsFromSchema");for(const n of[path.join(i,"app",".cxx"),path.join(i,"app","build","generated","autolinking")])rmSync(n,{recursive:!0,force:!0}),say(`sync: removed ${path.relative(e,n)}`)}if("darwin"===n&&existsSync(path.join(e,"ios","Podfile"))){const t=path.join(e,"ios","Podfile.lock"),o=path.join(e,"ios","Pods","Manifest.lock");if(!existsSync(t)||!existsSync(o)||podsStale(e)){const t=existsSync(path.join(e,"Gemfile")),o=findOnPath(t?"bundle":"pod",process.env,n);o||fail(`${t?"bundle":"pod"} not found on PATH; install CocoaPods, then ${podFix(e)}`),runInherit({file:o,args:t?["exec","pod","install"]:["install"]},path.join(e,"ios"),"pod install");const s=path.join(e,"ios","build","ModuleCache.noindex");existsSync(s)&&(rmSync(s,{recursive:!0,force:!0}),say(`sync: removed ${path.relative(e,s)}`))}}say("sync: done")};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/mobile-tools",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Dev-time CLI for Webority React Native apps: one prebuild, a dependency guard, a device-aware run, per-environment launcher icons and over-the-air deploys, the same on Windows and macOS.",
5
5
  "keywords": [
6
6
  "react-native",