expo-modules-autolinking 55.0.21 → 55.0.23

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/CHANGELOG.md CHANGED
@@ -10,6 +10,18 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 55.0.23 — 2026-05-19
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - Disallow devtools plugins to point to `webpageRoot` outside of their own bounds ([#45841](https://github.com/expo/expo/pull/45841) by [@kitten](https://github.com/kitten))
18
+
19
+ ## 55.0.22 — 2026-05-13
20
+
21
+ ### 🐛 Bug fixes
22
+
23
+ [iOS] Added fallback to source for missing framework slice. ([#45664](https://github.com/expo/expo/pull/45664) by [@chrfalch](https://github.com/chrfalch))
24
+
13
25
  ## 55.0.21 — 2026-05-05
14
26
 
15
27
  _This version does not introduce any user-facing changes._
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.resolveModuleAsync = resolveModuleAsync;
7
7
  exports.resolveExtraBuildDependenciesAsync = resolveExtraBuildDependenciesAsync;
8
8
  const path_1 = __importDefault(require("path"));
9
+ const utils_1 = require("../utils");
9
10
  async function resolveModuleAsync(packageName, revision) {
10
11
  const devtoolsConfig = revision.config?.toJSON().devtools;
11
12
  if (devtoolsConfig == null) {
@@ -14,12 +15,19 @@ async function resolveModuleAsync(packageName, revision) {
14
15
  return {
15
16
  packageName,
16
17
  packageRoot: revision.path,
17
- webpageRoot: devtoolsConfig.webpageRoot
18
- ? path_1.default.join(revision.path, devtoolsConfig.webpageRoot)
19
- : undefined,
18
+ webpageRoot: await resolveWebpageRoot(revision.path, devtoolsConfig.webpageRoot),
20
19
  cliExtensions: devtoolsConfig.cliExtensions,
21
20
  };
22
21
  }
22
+ async function resolveWebpageRoot(packageRoot, configuredWebpageRoot) {
23
+ if (!configuredWebpageRoot) {
24
+ return undefined;
25
+ }
26
+ const resolvedWebpageRoot = path_1.default.resolve(packageRoot, configuredWebpageRoot);
27
+ // NOTE(@kitten): Failing realpath-ing, typically due to ENOENT, results in the original value
28
+ const webpageRoot = (await (0, utils_1.maybeRealpath)(resolvedWebpageRoot)) ?? resolvedWebpageRoot;
29
+ return (0, utils_1.isPathInside)(webpageRoot, packageRoot) ? webpageRoot : undefined;
30
+ }
23
31
  async function resolveExtraBuildDependenciesAsync(_projectNativeRoot) {
24
32
  return null;
25
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"devtools.js","sourceRoot":"","sources":["../../src/platforms/devtools.ts"],"names":[],"mappings":";;;;;AAIA,gDAiBC;AAED,gFAIC;AA3BD,gDAAwB;AAIjB,KAAK,UAAU,kBAAkB,CACtC,WAAmB,EACnB,QAAyB;IAEzB,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,QAAQ,CAAC;IAC1D,IAAI,cAAc,IAAI,IAAI,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,WAAW;QACX,WAAW,EAAE,QAAQ,CAAC,IAAI;QAC1B,WAAW,EAAE,cAAc,CAAC,WAAW;YACrC,CAAC,CAAC,cAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,WAAW,CAAC;YACtD,CAAC,CAAC,SAAS;QACb,aAAa,EAAE,cAAc,CAAC,aAAa;KAC5C,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,kCAAkC,CACtD,kBAA0B;IAE1B,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import path from 'path';\n\nimport type { ExtraDependencies, ModuleDescriptorDevTools, PackageRevision } from '../types';\n\nexport async function resolveModuleAsync(\n packageName: string,\n revision: PackageRevision\n): Promise<ModuleDescriptorDevTools | null> {\n const devtoolsConfig = revision.config?.toJSON().devtools;\n if (devtoolsConfig == null) {\n return null;\n }\n\n return {\n packageName,\n packageRoot: revision.path,\n webpageRoot: devtoolsConfig.webpageRoot\n ? path.join(revision.path, devtoolsConfig.webpageRoot)\n : undefined,\n cliExtensions: devtoolsConfig.cliExtensions,\n };\n}\n\nexport async function resolveExtraBuildDependenciesAsync(\n _projectNativeRoot: string\n): Promise<ExtraDependencies | null> {\n return null;\n}\n"]}
1
+ {"version":3,"file":"devtools.js","sourceRoot":"","sources":["../../src/platforms/devtools.ts"],"names":[],"mappings":";;;;;AAKA,gDAeC;AAeD,gFAIC;AAvCD,gDAAwB;AAGxB,oCAAuD;AAEhD,KAAK,UAAU,kBAAkB,CACtC,WAAmB,EACnB,QAAyB;IAEzB,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,QAAQ,CAAC;IAC1D,IAAI,cAAc,IAAI,IAAI,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,WAAW;QACX,WAAW,EAAE,QAAQ,CAAC,IAAI;QAC1B,WAAW,EAAE,MAAM,kBAAkB,CAAC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,WAAW,CAAC;QAChF,aAAa,EAAE,cAAc,CAAC,aAAa;KAC5C,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,kBAAkB,CAC/B,WAAmB,EACnB,qBAAyC;IAEzC,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAC3B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,mBAAmB,GAAG,cAAI,CAAC,OAAO,CAAC,WAAW,EAAE,qBAAqB,CAAC,CAAC;IAC7E,8FAA8F;IAC9F,MAAM,WAAW,GAAG,CAAC,MAAM,IAAA,qBAAa,EAAC,mBAAmB,CAAC,CAAC,IAAI,mBAAmB,CAAC;IACtF,OAAO,IAAA,oBAAY,EAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1E,CAAC;AAEM,KAAK,UAAU,kCAAkC,CACtD,kBAA0B;IAE1B,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import path from 'path';\n\nimport type { ExtraDependencies, ModuleDescriptorDevTools, PackageRevision } from '../types';\nimport { isPathInside, maybeRealpath } from '../utils';\n\nexport async function resolveModuleAsync(\n packageName: string,\n revision: PackageRevision\n): Promise<ModuleDescriptorDevTools | null> {\n const devtoolsConfig = revision.config?.toJSON().devtools;\n if (devtoolsConfig == null) {\n return null;\n }\n\n return {\n packageName,\n packageRoot: revision.path,\n webpageRoot: await resolveWebpageRoot(revision.path, devtoolsConfig.webpageRoot),\n cliExtensions: devtoolsConfig.cliExtensions,\n };\n}\n\nasync function resolveWebpageRoot(\n packageRoot: string,\n configuredWebpageRoot: string | undefined\n): Promise<string | undefined> {\n if (!configuredWebpageRoot) {\n return undefined;\n }\n const resolvedWebpageRoot = path.resolve(packageRoot, configuredWebpageRoot);\n // NOTE(@kitten): Failing realpath-ing, typically due to ENOENT, results in the original value\n const webpageRoot = (await maybeRealpath(resolvedWebpageRoot)) ?? resolvedWebpageRoot;\n return isPathInside(webpageRoot, packageRoot) ? webpageRoot : undefined;\n}\n\nexport async function resolveExtraBuildDependenciesAsync(\n _projectNativeRoot: string\n): Promise<ExtraDependencies | null> {\n return null;\n}\n"]}
package/build/utils.d.ts CHANGED
@@ -11,6 +11,7 @@ export declare function scanFilesRecursively(parentPath: string, includeDirector
11
11
  export declare const fileExistsAsync: (file: string) => Promise<string | null>;
12
12
  export declare const fastJoin: (from: string, append: string) => string;
13
13
  export declare const maybeRealpath: (target: string) => Promise<string | null>;
14
+ export declare function isPathInside(child: string, parent: string): boolean;
14
15
  export type PackageJson = Record<string, unknown> & {
15
16
  name?: string;
16
17
  version?: string;
package/build/utils.js CHANGED
@@ -7,6 +7,7 @@ exports.loadPackageJson = exports.maybeRealpath = exports.fastJoin = exports.fil
7
7
  exports.listFilesSorted = listFilesSorted;
8
8
  exports.listFilesInDirectories = listFilesInDirectories;
9
9
  exports.scanFilesRecursively = scanFilesRecursively;
10
+ exports.isPathInside = isPathInside;
10
11
  const fs_1 = __importDefault(require("fs"));
11
12
  const path_1 = __importDefault(require("path"));
12
13
  const memoize_1 = require("./memoize");
@@ -84,6 +85,10 @@ const maybeRealpath = async (target) => {
84
85
  }
85
86
  };
86
87
  exports.maybeRealpath = maybeRealpath;
88
+ function isPathInside(child, parent) {
89
+ const relative = path_1.default.relative(parent, child);
90
+ return !!relative && !relative.startsWith('..') && !path_1.default.isAbsolute(relative);
91
+ }
87
92
  exports.loadPackageJson = (0, memoize_1.memoize)(async function loadPackageJson(jsonPath) {
88
93
  try {
89
94
  const packageJsonText = await fs_1.default.promises.readFile(jsonPath, 'utf8');
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nimport { memoize } from './memoize';\n\n/** List filtered top-level files in `targetPath` (returns absolute paths) */\nexport async function listFilesSorted(\n targetPath: string,\n filter: (basename: string) => boolean\n): Promise<string[]> {\n try {\n // `readdir` isn't guaranteed to be sorted on Windows\n return (await fs.promises.readdir(targetPath, { withFileTypes: true }))\n .filter((entry) => entry.isFile() && filter(entry.name))\n .sort((a, b) => a.name.localeCompare(b.name))\n .map((entry) => path.join(targetPath, entry.name));\n } catch {\n return [];\n }\n}\n\n/** List nested files in top-level directories in `targetPath` (returns relative paths) */\nexport async function listFilesInDirectories(\n targetPath: string,\n filter: (basename: string) => boolean\n): Promise<string[]> {\n return (\n await Promise.all(\n (await fs.promises.readdir(targetPath, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory() && entry.name !== 'node_modules')\n .sort((a, b) => a.name.localeCompare(b.name))\n .map(async (directory) => {\n const entries = await fs.promises.readdir(path.join(targetPath, directory.name), {\n withFileTypes: true,\n });\n return entries\n .filter((entry) => entry.isFile() && filter(entry.name))\n .sort((a, b) => a.name.localeCompare(b.name))\n .map((entry) => path.join(directory.name, entry.name));\n })\n )\n ).flat(1);\n}\n\n/** Iterate folders recursively for files, optionally sorting results and filtering directories */\nexport async function* scanFilesRecursively(\n parentPath: string,\n includeDirectory?: (parentPath: string, name: string) => boolean,\n sort = !fs.opendir\n) {\n const queue = [parentPath];\n let targetPath: string | undefined;\n while (queue.length > 0 && (targetPath = queue.shift()) != null) {\n try {\n const entries = sort\n ? (await fs.promises.readdir(targetPath, { withFileTypes: true })).sort((a, b) =>\n a.name.localeCompare(b.name)\n )\n : await fs.promises.opendir(targetPath);\n for await (const entry of entries) {\n if (entry.isDirectory() && entry.name !== 'node_modules') {\n if (!includeDirectory || includeDirectory(targetPath, entry.name)) {\n queue.push(path.join(targetPath, entry.name));\n }\n } else if (entry.isFile()) {\n yield {\n path: path.join(targetPath, entry.name),\n parentPath: targetPath,\n name: entry.name,\n } as const;\n }\n }\n } catch {\n continue;\n }\n }\n}\n\nexport const fileExistsAsync = async (file: string): Promise<string | null> => {\n const stat = await fs.promises.stat(file).catch(() => null);\n return stat?.isFile() ? file : null;\n};\n\nexport const fastJoin: (from: string, append: string) => string =\n path.sep === '/'\n ? (from, append) => `${from}${path.sep}${append}`\n : (from, append) =>\n `${from}${path.sep}${append[0] === '@' ? append.replace('/', path.sep) : append}`;\n\nexport const maybeRealpath = async (target: string): Promise<string | null> => {\n try {\n return await fs.promises.realpath(target);\n } catch {\n return null;\n }\n};\n\nexport type PackageJson = Record<string, unknown> & {\n name?: string;\n version?: string;\n peerDependencies?: Record<string, string>;\n codegenConfig?: Record<string, unknown>;\n};\n\nexport const loadPackageJson = memoize(async function loadPackageJson(\n jsonPath: string\n): Promise<PackageJson | null> {\n try {\n const packageJsonText = await fs.promises.readFile(jsonPath, 'utf8');\n const json = JSON.parse(packageJsonText);\n if (typeof json !== 'object' || json == null) {\n return null;\n }\n return json;\n } catch {\n return null;\n }\n});\n"]}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;;;;AAMA,0CAaC;AAGD,wDAoBC;AAGD,oDA+BC;AAqBD,oCAGC;AApGD,4CAAoB;AACpB,gDAAwB;AAExB,uCAAoC;AAEpC,6EAA6E;AACtE,KAAK,UAAU,eAAe,CACnC,UAAkB,EAClB,MAAqC;IAErC,IAAI,CAAC;QACH,qDAAqD;QACrD,OAAO,CAAC,MAAM,YAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;aACpE,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;aACvD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;aAC5C,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,0FAA0F;AACnF,KAAK,UAAU,sBAAsB,CAC1C,UAAkB,EAClB,MAAqC;IAErC,OAAO,CACL,MAAM,OAAO,CAAC,GAAG,CACf,CAAC,MAAM,YAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;SAC7D,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC;SACvE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;SAC5C,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE;QACvB,MAAM,OAAO,GAAG,MAAM,YAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE;YAC/E,aAAa,EAAE,IAAI;SACpB,CAAC,CAAC;QACH,OAAO,OAAO;aACX,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;aACvD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;aAC5C,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,cAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3D,CAAC,CAAC,CACL,CACF,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACZ,CAAC;AAED,kGAAkG;AAC3F,KAAK,SAAS,CAAC,CAAC,oBAAoB,CACzC,UAAkB,EAClB,gBAAgE,EAChE,IAAI,GAAG,CAAC,YAAE,CAAC,OAAO;IAElB,MAAM,KAAK,GAAG,CAAC,UAAU,CAAC,CAAC;IAC3B,IAAI,UAA8B,CAAC;IACnC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC;QAChE,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI;gBAClB,CAAC,CAAC,CAAC,MAAM,YAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAC7E,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAC7B;gBACH,CAAC,CAAC,MAAM,YAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YAC1C,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAClC,IAAI,KAAK,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;oBACzD,IAAI,CAAC,gBAAgB,IAAI,gBAAgB,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;wBAClE,KAAK,CAAC,IAAI,CAAC,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;oBAChD,CAAC;gBACH,CAAC;qBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;oBAC1B,MAAM;wBACJ,IAAI,EAAE,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC;wBACvC,UAAU,EAAE,UAAU;wBACtB,IAAI,EAAE,KAAK,CAAC,IAAI;qBACR,CAAC;gBACb,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;IACH,CAAC;AACH,CAAC;AAEM,MAAM,eAAe,GAAG,KAAK,EAAE,IAAY,EAA0B,EAAE;IAC5E,MAAM,IAAI,GAAG,MAAM,YAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAC5D,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACtC,CAAC,CAAC;AAHW,QAAA,eAAe,mBAG1B;AAEW,QAAA,QAAQ,GACnB,cAAI,CAAC,GAAG,KAAK,GAAG;IACd,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,cAAI,CAAC,GAAG,GAAG,MAAM,EAAE;IACjD,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CACf,GAAG,IAAI,GAAG,cAAI,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,cAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;AAEnF,MAAM,aAAa,GAAG,KAAK,EAAE,MAAc,EAA0B,EAAE;IAC5E,IAAI,CAAC;QACH,OAAO,MAAM,YAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AANW,QAAA,aAAa,iBAMxB;AAEF,SAAgB,YAAY,CAAC,KAAa,EAAE,MAAc;IACxD,MAAM,QAAQ,GAAG,cAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC9C,OAAO,CAAC,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,cAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;AAChF,CAAC;AASY,QAAA,eAAe,GAAG,IAAA,iBAAO,EAAC,KAAK,UAAU,eAAe,CACnE,QAAgB;IAEhB,IAAI,CAAC;QACH,MAAM,eAAe,GAAG,MAAM,YAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACrE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;QACzC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;YAC7C,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC,CAAC","sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nimport { memoize } from './memoize';\n\n/** List filtered top-level files in `targetPath` (returns absolute paths) */\nexport async function listFilesSorted(\n targetPath: string,\n filter: (basename: string) => boolean\n): Promise<string[]> {\n try {\n // `readdir` isn't guaranteed to be sorted on Windows\n return (await fs.promises.readdir(targetPath, { withFileTypes: true }))\n .filter((entry) => entry.isFile() && filter(entry.name))\n .sort((a, b) => a.name.localeCompare(b.name))\n .map((entry) => path.join(targetPath, entry.name));\n } catch {\n return [];\n }\n}\n\n/** List nested files in top-level directories in `targetPath` (returns relative paths) */\nexport async function listFilesInDirectories(\n targetPath: string,\n filter: (basename: string) => boolean\n): Promise<string[]> {\n return (\n await Promise.all(\n (await fs.promises.readdir(targetPath, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory() && entry.name !== 'node_modules')\n .sort((a, b) => a.name.localeCompare(b.name))\n .map(async (directory) => {\n const entries = await fs.promises.readdir(path.join(targetPath, directory.name), {\n withFileTypes: true,\n });\n return entries\n .filter((entry) => entry.isFile() && filter(entry.name))\n .sort((a, b) => a.name.localeCompare(b.name))\n .map((entry) => path.join(directory.name, entry.name));\n })\n )\n ).flat(1);\n}\n\n/** Iterate folders recursively for files, optionally sorting results and filtering directories */\nexport async function* scanFilesRecursively(\n parentPath: string,\n includeDirectory?: (parentPath: string, name: string) => boolean,\n sort = !fs.opendir\n) {\n const queue = [parentPath];\n let targetPath: string | undefined;\n while (queue.length > 0 && (targetPath = queue.shift()) != null) {\n try {\n const entries = sort\n ? (await fs.promises.readdir(targetPath, { withFileTypes: true })).sort((a, b) =>\n a.name.localeCompare(b.name)\n )\n : await fs.promises.opendir(targetPath);\n for await (const entry of entries) {\n if (entry.isDirectory() && entry.name !== 'node_modules') {\n if (!includeDirectory || includeDirectory(targetPath, entry.name)) {\n queue.push(path.join(targetPath, entry.name));\n }\n } else if (entry.isFile()) {\n yield {\n path: path.join(targetPath, entry.name),\n parentPath: targetPath,\n name: entry.name,\n } as const;\n }\n }\n } catch {\n continue;\n }\n }\n}\n\nexport const fileExistsAsync = async (file: string): Promise<string | null> => {\n const stat = await fs.promises.stat(file).catch(() => null);\n return stat?.isFile() ? file : null;\n};\n\nexport const fastJoin: (from: string, append: string) => string =\n path.sep === '/'\n ? (from, append) => `${from}${path.sep}${append}`\n : (from, append) =>\n `${from}${path.sep}${append[0] === '@' ? append.replace('/', path.sep) : append}`;\n\nexport const maybeRealpath = async (target: string): Promise<string | null> => {\n try {\n return await fs.promises.realpath(target);\n } catch {\n return null;\n }\n};\n\nexport function isPathInside(child: string, parent: string): boolean {\n const relative = path.relative(parent, child);\n return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);\n}\n\nexport type PackageJson = Record<string, unknown> & {\n name?: string;\n version?: string;\n peerDependencies?: Record<string, string>;\n codegenConfig?: Record<string, unknown>;\n};\n\nexport const loadPackageJson = memoize(async function loadPackageJson(\n jsonPath: string\n): Promise<PackageJson | null> {\n try {\n const packageJsonText = await fs.promises.readFile(jsonPath, 'utf8');\n const json = JSON.parse(packageJsonText);\n if (typeof json !== 'object' || json == null) {\n return null;\n }\n return json;\n } catch {\n return null;\n }\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-autolinking",
3
- "version": "55.0.21",
3
+ "version": "55.0.23",
4
4
  "description": "Scripts that autolink Expo modules.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -43,5 +43,5 @@
43
43
  "chalk": "^4.1.0",
44
44
  "commander": "^7.2.0"
45
45
  },
46
- "gitHead": "7c081282cf88968f81732feb67a71840e769a40f"
46
+ "gitHead": "fcb091766242d53248cd3c5949965961dbc5ec1d"
47
47
  }
@@ -21,16 +21,18 @@ module Expo
21
21
 
22
22
  validate_target_definition()
23
23
 
24
- # Clear stale CocoaPods download cache for precompiled pods.
25
- Expo::PrecompiledModules.clear_cocoapods_cache
26
-
27
24
  resolve_result = resolve()
28
25
 
29
26
  Expo::PackagesConfig.instance.coreFeatures = resolve_result['coreFeatures']
30
27
 
31
- # Pass buildFromSource configuration to PrecompiledModules
32
28
  configuration = resolve_result['configuration'] || {}
33
- Expo::PrecompiledModules.build_from_source = configuration['buildFromSource'] || []
29
+ Expo::PrecompiledModules.configure(
30
+ target_platform: @target_definition.platform,
31
+ build_from_source: configuration['buildFromSource'] || []
32
+ )
33
+
34
+ # Clear stale CocoaPods download cache for precompiled pods.
35
+ Expo::PrecompiledModules.clear_cocoapods_cache
34
36
 
35
37
  @packages = resolve_result['modules'].map { |json_package| Package.new(json_package) }
36
38
  @extraPods = resolve_result['extraDependencies']
@@ -29,7 +29,9 @@
29
29
  require 'fileutils'
30
30
  require 'json'
31
31
  require 'net/http'
32
+ require 'open3'
32
33
  require 'set'
34
+ require 'tempfile'
33
35
  require 'uri'
34
36
 
35
37
  module Expo
@@ -76,6 +78,8 @@ module Expo
76
78
  @framework_owner_map = nil # Hash: framework_name -> owning_pod_name
77
79
  @failed_remote_downloads = Set.new
78
80
  @warned_no_prebuilt_react = false
81
+ @target_platform = nil
82
+ @xcframework_slice_cache = nil
79
83
 
80
84
  class << self
81
85
  # Returns the build flavor (debug/release) for precompiled modules.
@@ -113,13 +117,26 @@ module Expo
113
117
  false
114
118
  end
115
119
 
116
- # Sets the list of package name patterns that should be built from source
117
- # instead of using precompiled xcframeworks. Patterns are treated as regexes
118
- # (e.g., ".*" for all, "expo-audio" for exact match, "expo-.*" for prefix).
120
+ def configure(target_platform: nil, build_from_source: nil)
121
+ self.target_platform = target_platform unless target_platform.nil?
122
+ self.build_from_source = build_from_source unless build_from_source.nil?
123
+ end
124
+
119
125
  def build_from_source=(patterns)
120
126
  @build_from_source_patterns = (patterns || []).map { |p| Regexp.new("^#{p}$") }
121
127
  end
122
128
 
129
+ def target_platform=(platform)
130
+ normalized = normalize_xcframework_platform(platform)
131
+ return if @target_platform == normalized
132
+
133
+ @target_platform = normalized
134
+ @all_bundled_frameworks = nil
135
+ @claimed_vendored_frameworks = nil
136
+ @framework_owner_map = nil
137
+ @xcframework_slice_cache = nil
138
+ end
139
+
123
140
  # Checks if a pod is configured to be built from source via buildFromSource.
124
141
  # Matches against both the pod name and the npm package name.
125
142
  def build_from_source?(pod_name)
@@ -1761,6 +1778,7 @@ module Expo
1761
1778
  product_name = pod_info[:product_name] || pod_name
1762
1779
  tarball = resolve_prebuilt_tarball(pod_info, product_name, build_flavor, pod_name)
1763
1780
  return { available: false, reason: :missing_tarball, path: tarball } unless File.exist?(tarball)
1781
+ return { available: false, reason: :missing_platform_slice, path: tarball } unless xcframework_supports_target_platform?(tarball)
1764
1782
 
1765
1783
  { available: true, resolved: [pod_info, product_name, tarball] }
1766
1784
  end
@@ -1818,6 +1836,74 @@ module Expo
1818
1836
  remote_tarball
1819
1837
  end
1820
1838
 
1839
+ def normalize_xcframework_platform(platform)
1840
+ name = platform.respond_to?(:name) ? platform.name : platform
1841
+ name = name.string_name if name.respond_to?(:string_name)
1842
+ normalized = name.to_s.downcase
1843
+ normalized == 'osx' ? 'macos' : normalized
1844
+ end
1845
+
1846
+ def xcframework_supports_target_platform?(path)
1847
+ return true unless @target_platform
1848
+
1849
+ @xcframework_slice_cache ||= {}
1850
+ cache_key = [path, @target_platform]
1851
+ return @xcframework_slice_cache[cache_key] if @xcframework_slice_cache.key?(cache_key)
1852
+
1853
+ @xcframework_slice_cache[cache_key] = begin
1854
+ info_plists = File.directory?(path) ? [read_plist(File.join(path, 'Info.plist'))] : read_xcframework_info_plists_from_tarball(path)
1855
+ info_plists.any? && info_plists.all? { |info_plist| info_plist_supports_target_platform?(info_plist) }
1856
+ end
1857
+ end
1858
+
1859
+ def info_plist_supports_target_platform?(info_plist)
1860
+ return false unless info_plist
1861
+
1862
+ available_libraries = info_plist['AvailableLibraries']
1863
+ return false unless available_libraries.is_a?(Array)
1864
+
1865
+ available_libraries.any? do |library|
1866
+ normalize_xcframework_platform(library['SupportedPlatform']) == @target_platform
1867
+ end
1868
+ end
1869
+
1870
+ def read_xcframework_info_plists_from_tarball(tarball)
1871
+ entries_output, status = Open3.capture2e('tar', 'tzf', tarball)
1872
+ unless status.success?
1873
+ Pod::UI.warn "[Expo-precompiled] Failed to inspect #{File.basename(tarball)}: #{entries_output.strip}"
1874
+ return []
1875
+ end
1876
+
1877
+ entries = entries_output.lines.map(&:strip)
1878
+ plist_entries = entries.select { |entry| entry.end_with?('.xcframework/Info.plist') }
1879
+ Pod::UI.warn "[Expo-precompiled] No XCFramework Info.plist found in #{File.basename(tarball)}" if plist_entries.empty?
1880
+
1881
+ plist_entries.filter_map do |entry|
1882
+ plist_data, plist_status = Open3.capture2e('tar', 'xOzf', tarball, entry)
1883
+ unless plist_status.success?
1884
+ Pod::UI.warn "[Expo-precompiled] Failed to extract #{entry} from #{File.basename(tarball)}: #{plist_data.strip}"
1885
+ next
1886
+ end
1887
+
1888
+ Tempfile.create(['expo-xcframework-info', '.plist']) do |file|
1889
+ file.binmode
1890
+ file.write(plist_data)
1891
+ file.flush
1892
+ read_plist(file.path)
1893
+ end
1894
+ end
1895
+ rescue StandardError => e
1896
+ Pod::UI.warn "[Expo-precompiled] Failed to inspect #{File.basename(tarball)}: #{e.message}"
1897
+ []
1898
+ end
1899
+
1900
+ def read_plist(path)
1901
+ Xcodeproj::Plist.read_from_path(path)
1902
+ rescue StandardError => e
1903
+ Pod::UI.warn "[Expo-precompiled] Failed to read #{File.basename(path)}: #{e.message}"
1904
+ nil
1905
+ end
1906
+
1821
1907
  def failed_remote_downloads
1822
1908
  @failed_remote_downloads ||= Set.new
1823
1909
  end
@@ -2096,6 +2182,8 @@ module Expo
2096
2182
  'prebuilt config not found'
2097
2183
  when :missing_tarball
2098
2184
  'prebuilt tarball not found'
2185
+ when :missing_platform_slice
2186
+ "prebuilt xcframework does not contain a slice for #{@target_platform}"
2099
2187
  when :dependency_unavailable
2100
2188
  reason = format_prebuilt_unavailable_reason(reason: info[:dependency_reason], path: info[:dependency_path])
2101
2189
  "dependency #{info[:dependency]} is not using prebuilt: #{reason}"
@@ -1,6 +1,7 @@
1
1
  import path from 'path';
2
2
 
3
3
  import type { ExtraDependencies, ModuleDescriptorDevTools, PackageRevision } from '../types';
4
+ import { isPathInside, maybeRealpath } from '../utils';
4
5
 
5
6
  export async function resolveModuleAsync(
6
7
  packageName: string,
@@ -14,13 +15,24 @@ export async function resolveModuleAsync(
14
15
  return {
15
16
  packageName,
16
17
  packageRoot: revision.path,
17
- webpageRoot: devtoolsConfig.webpageRoot
18
- ? path.join(revision.path, devtoolsConfig.webpageRoot)
19
- : undefined,
18
+ webpageRoot: await resolveWebpageRoot(revision.path, devtoolsConfig.webpageRoot),
20
19
  cliExtensions: devtoolsConfig.cliExtensions,
21
20
  };
22
21
  }
23
22
 
23
+ async function resolveWebpageRoot(
24
+ packageRoot: string,
25
+ configuredWebpageRoot: string | undefined
26
+ ): Promise<string | undefined> {
27
+ if (!configuredWebpageRoot) {
28
+ return undefined;
29
+ }
30
+ const resolvedWebpageRoot = path.resolve(packageRoot, configuredWebpageRoot);
31
+ // NOTE(@kitten): Failing realpath-ing, typically due to ENOENT, results in the original value
32
+ const webpageRoot = (await maybeRealpath(resolvedWebpageRoot)) ?? resolvedWebpageRoot;
33
+ return isPathInside(webpageRoot, packageRoot) ? webpageRoot : undefined;
34
+ }
35
+
24
36
  export async function resolveExtraBuildDependenciesAsync(
25
37
  _projectNativeRoot: string
26
38
  ): Promise<ExtraDependencies | null> {
package/src/utils.ts CHANGED
@@ -95,6 +95,11 @@ export const maybeRealpath = async (target: string): Promise<string | null> => {
95
95
  }
96
96
  };
97
97
 
98
+ export function isPathInside(child: string, parent: string): boolean {
99
+ const relative = path.relative(parent, child);
100
+ return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);
101
+ }
102
+
98
103
  export type PackageJson = Record<string, unknown> & {
99
104
  name?: string;
100
105
  version?: string;