expo-modules-autolinking 55.0.22 → 55.0.24
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 +12 -0
- package/build/platforms/devtools.js +11 -3
- package/build/platforms/devtools.js.map +1 -1
- package/build/utils.d.ts +1 -0
- package/build/utils.js +5 -0
- package/build/utils.js.map +1 -1
- package/package.json +2 -2
- package/scripts/ios/precompiled_modules.rb +166 -54
- package/scripts/ios/replace-xcframework.js +90 -106
- package/src/platforms/devtools.ts +15 -3
- package/src/utils.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,18 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 55.0.24 — 2026-05-21
|
|
14
|
+
|
|
15
|
+
### 🎉 New features
|
|
16
|
+
|
|
17
|
+
- [iOS] Include and consume shared SPM dependencies in the precompiled pod / npm publish pipeline. ([#46069](https://github.com/expo/expo/pull/46069) by [@chrfalch](https://github.com/chrfalch))
|
|
18
|
+
|
|
19
|
+
## 55.0.23 — 2026-05-19
|
|
20
|
+
|
|
21
|
+
### 🐛 Bug fixes
|
|
22
|
+
|
|
23
|
+
- 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))
|
|
24
|
+
|
|
13
25
|
## 55.0.22 — 2026-05-13
|
|
14
26
|
|
|
15
27
|
### 🐛 Bug fixes
|
|
@@ -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":";;;;;
|
|
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');
|
package/build/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;;;;AAMA,0CAaC;AAGD,wDAoBC;AAGD,oDA+BC;
|
|
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 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.
|
|
3
|
+
"version": "55.0.24",
|
|
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": "
|
|
46
|
+
"gitHead": "bb1d4bd298e5bcaff86b04aabca7c56659e57138"
|
|
47
47
|
}
|
|
@@ -54,6 +54,11 @@ module Expo
|
|
|
54
54
|
# Centralized build output directory under packages/precompile/
|
|
55
55
|
PRECOMPILE_BUILD_DIR = '.build'.freeze
|
|
56
56
|
|
|
57
|
+
# Subdir under packages/precompile/.build/ holding <Name>/<flavor>/<Name>.xcframework (monorepo source).
|
|
58
|
+
SHARED_SPM_DEPS_SOURCE_DIR = '.spm-deps'.freeze
|
|
59
|
+
# Subdir inside an npm package that bundles shared SPM xcframeworks for standalone consumers.
|
|
60
|
+
BUNDLED_SHARED_SPM_DEPS_SUBPATH = File.join('prebuilds', 'spm-deps').freeze
|
|
61
|
+
|
|
57
62
|
# Apple platforms supported by CocoaPods podspecs
|
|
58
63
|
APPLE_PLATFORMS = %w[ios osx tvos watchos visionos].freeze
|
|
59
64
|
|
|
@@ -185,10 +190,14 @@ module Expo
|
|
|
185
190
|
# - Pods that vendor xcframeworks (already precompiled)
|
|
186
191
|
# - Source-built pods that depend on React-Core (non-modular includes)
|
|
187
192
|
#
|
|
193
|
+
# Also stages shared SPM dep xcframework symlinks inside their owner pod's
|
|
194
|
+
# directory — must run before `generate_pods_project` reads each xcframework's
|
|
195
|
+
# Info.plist to slice it.
|
|
196
|
+
#
|
|
188
197
|
# @param installer [Pod::Installer] The CocoaPods installer instance
|
|
189
198
|
def perform_pre_install(installer)
|
|
190
199
|
return unless enabled?
|
|
191
|
-
|
|
200
|
+
ensure_shared_spm_deps(installer)
|
|
192
201
|
return if linkage(installer).nil?
|
|
193
202
|
|
|
194
203
|
pods_to_downgrade = Set.new(installer.podfile.framework_modules_to_patch)
|
|
@@ -212,6 +221,63 @@ module Expo
|
|
|
212
221
|
end
|
|
213
222
|
end
|
|
214
223
|
|
|
224
|
+
# Symlinks each shared SPM dependency xcframework (e.g. SDWebImage) into the
|
|
225
|
+
# pod directory of its owner. Ownership is whatever `build_vendored_paths` set
|
|
226
|
+
# in `@framework_owner_map` during `store_podspec` (resolution-first);
|
|
227
|
+
# falls back to alphabetical-first only if the map has no entry. Must run
|
|
228
|
+
# before `generate_pods_project` so CocoaPods sees the symlinks when reading
|
|
229
|
+
# each xcframework's Info.plist.
|
|
230
|
+
def ensure_shared_spm_deps(installer)
|
|
231
|
+
return unless enabled?
|
|
232
|
+
|
|
233
|
+
consumers_by_dep = collect_shared_spm_deps(installer)
|
|
234
|
+
return if consumers_by_dep.empty?
|
|
235
|
+
|
|
236
|
+
@framework_owner_map ||= {}
|
|
237
|
+
@claimed_vendored_frameworks ||= Set.new
|
|
238
|
+
|
|
239
|
+
unresolved = []
|
|
240
|
+
staged = 0
|
|
241
|
+
|
|
242
|
+
consumers_by_dep.each do |dep_name, consumers|
|
|
243
|
+
existing = @framework_owner_map[dep_name]
|
|
244
|
+
owner_name = (existing && consumers.key?(existing)) ? existing : consumers.keys.sort.first
|
|
245
|
+
owner_info = consumers[owner_name]
|
|
246
|
+
@framework_owner_map[dep_name] ||= owner_name
|
|
247
|
+
@claimed_vendored_frameworks.add(dep_name)
|
|
248
|
+
|
|
249
|
+
source_path = shared_spm_dep_xcframework_path(dep_name, owner_info, build_flavor)
|
|
250
|
+
owner_pod_dir = File.join(installer.sandbox.root, owner_name)
|
|
251
|
+
unless source_path && File.directory?(owner_pod_dir)
|
|
252
|
+
unresolved << dep_name
|
|
253
|
+
next
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
FileUtils.rm_rf(File.join(owner_pod_dir, "#{dep_name}.xcframework"))
|
|
257
|
+
File.symlink(source_path, File.join(owner_pod_dir, "#{dep_name}.xcframework"))
|
|
258
|
+
staged += 1
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
if unresolved.any?
|
|
262
|
+
Pod::UI.warn "[Expo-precompiled] Shared SPM xcframeworks not found for: #{unresolved.join(', ')} (flavor: #{build_flavor}). The Expo modules that depend on them will fail at runtime with dyld 'Library not loaded: @rpath/<Name>.framework/<Name>'. Run the precompile prebuild pipeline, or ensure each consuming npm package ships prebuilds/spm-deps/<Name>/<flavor>/<Name>.xcframework."
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
Pod::UI.puts "[Expo] ".blue + "Staged #{staged}/#{consumers_by_dep.size} shared SPM xcframework(s) (#{build_flavor})" if staged > 0
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# dep_name => { pod_name => pod_info } for shared SPM deps consumed by enabled prebuilt pods in this install.
|
|
269
|
+
def collect_shared_spm_deps(installer)
|
|
270
|
+
by_dep = {}
|
|
271
|
+
installer.pod_targets.each do |pod_target|
|
|
272
|
+
info = pod_lookup_map[pod_target.name]
|
|
273
|
+
next unless info && has_prebuilt_xcframework?(pod_target.name)
|
|
274
|
+
(info[:spm_dependency_frameworks] || []).each do |dep_name|
|
|
275
|
+
(by_dep[dep_name] ||= {})[pod_target.name] = info
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
by_dep
|
|
279
|
+
end
|
|
280
|
+
|
|
215
281
|
# ──────────────────────────────────────────────────────────────────────
|
|
216
282
|
# Cache management
|
|
217
283
|
# ──────────────────────────────────────────────────────────────────────
|
|
@@ -1074,15 +1140,10 @@ module Expo
|
|
|
1074
1140
|
end
|
|
1075
1141
|
end
|
|
1076
1142
|
|
|
1077
|
-
#
|
|
1078
|
-
#
|
|
1079
|
-
#
|
|
1080
|
-
#
|
|
1081
|
-
#
|
|
1082
|
-
# @param product_name [String] The product/module name
|
|
1083
|
-
# @param pod_info [Hash] Package info from spm.config.json lookup
|
|
1084
|
-
# @param pod_name [String] The pod name (for summary tracking)
|
|
1085
|
-
# @return [Array<String>] vendored framework paths
|
|
1143
|
+
# Returns vendored_frameworks paths for a prebuilt pod: the product's own
|
|
1144
|
+
# xcframework plus any shared SPM deps this pod owns (first to claim wins;
|
|
1145
|
+
# non-owners get FRAMEWORK_SEARCH_PATHS instead). Shared dep entries are
|
|
1146
|
+
# symlinks staged by `ensure_shared_spm_deps` inside the owner's pod dir.
|
|
1086
1147
|
def build_vendored_paths(product_name, pod_info, pod_name)
|
|
1087
1148
|
@claimed_vendored_frameworks ||= Set.new
|
|
1088
1149
|
@framework_owner_map ||= {}
|
|
@@ -1092,38 +1153,27 @@ module Expo
|
|
|
1092
1153
|
@framework_owner_map[product_name] = pod_name
|
|
1093
1154
|
|
|
1094
1155
|
(pod_info[:spm_dependency_frameworks] || []).each do |dep_name|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
Pod::UI.puts "#{'[Expo-precompiled] '.blue}Skipping #{dep_name}.xcframework from #{pod_name} — already vendored by #{owner}"
|
|
1098
|
-
else
|
|
1156
|
+
owner = (@framework_owner_map[dep_name] ||= pod_name)
|
|
1157
|
+
if owner == pod_name
|
|
1099
1158
|
paths << "#{dep_name}.xcframework"
|
|
1100
|
-
|
|
1101
|
-
|
|
1159
|
+
else
|
|
1160
|
+
Pod::UI.puts "#{'[Expo-precompiled] '.blue}Skipping #{dep_name}.xcframework from #{pod_name} — already vendored by #{owner}"
|
|
1102
1161
|
end
|
|
1103
1162
|
log_spm_dependency(pod_name, dep_name)
|
|
1104
1163
|
end
|
|
1105
1164
|
paths
|
|
1106
1165
|
end
|
|
1107
1166
|
|
|
1108
|
-
#
|
|
1109
|
-
#
|
|
1110
|
-
#
|
|
1111
|
-
#
|
|
1112
|
-
# @param pod_name [String] The pod name
|
|
1113
|
-
# @param pod_info [Hash] Package info from spm.config.json lookup
|
|
1114
|
-
# @return [Array<String>] framework search path entries
|
|
1167
|
+
# FRAMEWORK_SEARCH_PATHS entries for shared SPM deps claimed by another pod.
|
|
1168
|
+
# CocoaPods slices each xcframework into `${PODS_XCFRAMEWORKS_BUILD_DIR}/<owner>/`,
|
|
1169
|
+
# which is where the linker resolves the framework.
|
|
1115
1170
|
def framework_search_paths_for_skipped_deps(pod_name, pod_info)
|
|
1116
|
-
@claimed_vendored_frameworks ||= Set.new
|
|
1117
1171
|
@framework_owner_map ||= {}
|
|
1118
|
-
|
|
1119
|
-
paths = []
|
|
1120
|
-
(pod_info[:spm_dependency_frameworks] || []).each do |dep_name|
|
|
1172
|
+
owners = (pod_info[:spm_dependency_frameworks] || []).filter_map do |dep_name|
|
|
1121
1173
|
owner = @framework_owner_map[dep_name]
|
|
1122
|
-
if owner && owner != pod_name
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
end
|
|
1126
|
-
paths.uniq
|
|
1174
|
+
owner if owner && owner != pod_name
|
|
1175
|
+
end.uniq
|
|
1176
|
+
owners.flat_map { |owner| [%("${PODS_XCFRAMEWORKS_BUILD_DIR}/#{owner}"), %("${PODS_ROOT}/#{owner}")] }
|
|
1127
1177
|
end
|
|
1128
1178
|
|
|
1129
1179
|
# ──────────────────────────────────────────────────────────────────────
|
|
@@ -1150,11 +1200,13 @@ module Expo
|
|
|
1150
1200
|
package_root_var = "#{pods_parent}/#{package_root_rel}"
|
|
1151
1201
|
dsym_stamp = "$(DERIVED_FILE_DIR)/expo-dsym-resolve-#{product_name}-$(CONFIGURATION).stamp"
|
|
1152
1202
|
|
|
1203
|
+
shared_deps = shared_dep_switch_args(spec_name, pod_info)
|
|
1204
|
+
|
|
1153
1205
|
switch_phase = {
|
|
1154
1206
|
'name' => "[Expo] Switch #{spec_name} XCFramework for build configuration",
|
|
1155
1207
|
'execution_position' => 'before_compile',
|
|
1156
1208
|
'input_files' => ["#{pods_parent}/#{switch_script_rel}"],
|
|
1157
|
-
'script' => xcframework_switch_script(product_name, xcframeworks_dir_var, switch_script_path),
|
|
1209
|
+
'script' => xcframework_switch_script(product_name, xcframeworks_dir_var, switch_script_path, shared_deps),
|
|
1158
1210
|
}
|
|
1159
1211
|
|
|
1160
1212
|
if Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.13.0')
|
|
@@ -1197,32 +1249,55 @@ module Expo
|
|
|
1197
1249
|
SH
|
|
1198
1250
|
end
|
|
1199
1251
|
|
|
1200
|
-
#
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1252
|
+
# Shell script for the xcframework switch phase. With no shared deps the
|
|
1253
|
+
# script short-circuits in shell when the per-pod state file matches; with
|
|
1254
|
+
# shared deps Node is always invoked so each dep's symlink can be repointed
|
|
1255
|
+
# (it has its own per-dep state file inside replace-xcframework.js).
|
|
1256
|
+
def xcframework_switch_script(product_name, xcframeworks_dir, script_path, shared_deps = [])
|
|
1257
|
+
config_detect = <<~SH.chomp
|
|
1206
1258
|
CONFIG="release"
|
|
1207
1259
|
if echo "$GCC_PREPROCESSOR_DEFINITIONS" | grep -q "DEBUG=1"; then
|
|
1208
1260
|
CONFIG="debug"
|
|
1209
1261
|
fi
|
|
1210
|
-
|
|
1211
|
-
# Early exit: Skip Node.js invocation if configuration hasn't changed
|
|
1212
|
-
# This optimization avoids ~100-200ms overhead per module on incremental builds
|
|
1213
|
-
LAST_CONFIG_FILE="#{xcframeworks_dir}/artifacts/.last_build_configuration"
|
|
1214
|
-
if [ -f "$LAST_CONFIG_FILE" ] && [ "$(cat "$LAST_CONFIG_FILE")" = "$CONFIG" ]; then
|
|
1215
|
-
exit 0
|
|
1216
|
-
fi
|
|
1217
|
-
|
|
1218
|
-
# Configuration changed or first build - invoke Node.js to extract tarball
|
|
1219
|
-
. "$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh"
|
|
1220
|
-
|
|
1221
|
-
"$NODE_BINARY" "#{script_path}" \\
|
|
1222
|
-
-c "$CONFIG" \\
|
|
1223
|
-
-m "#{product_name}" \\
|
|
1224
|
-
-x "#{xcframeworks_dir}"
|
|
1225
1262
|
SH
|
|
1263
|
+
|
|
1264
|
+
if shared_deps.empty?
|
|
1265
|
+
<<~SH
|
|
1266
|
+
# Auto-generated by expo-modules-autolinking
|
|
1267
|
+
#{config_detect}
|
|
1268
|
+
LAST_CONFIG_FILE="#{xcframeworks_dir}/artifacts/.last_build_configuration"
|
|
1269
|
+
if [ -f "$LAST_CONFIG_FILE" ] && [ "$(cat "$LAST_CONFIG_FILE")" = "$CONFIG" ]; then
|
|
1270
|
+
exit 0
|
|
1271
|
+
fi
|
|
1272
|
+
. "$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh"
|
|
1273
|
+
"$NODE_BINARY" "#{script_path}" -c "$CONFIG" -m "#{product_name}" -x "#{xcframeworks_dir}"
|
|
1274
|
+
SH
|
|
1275
|
+
else
|
|
1276
|
+
shared_args = shared_deps.map { |arg| " #{arg}" }.join(" \\\n")
|
|
1277
|
+
<<~SH
|
|
1278
|
+
# Auto-generated by expo-modules-autolinking
|
|
1279
|
+
#{config_detect}
|
|
1280
|
+
. "$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh"
|
|
1281
|
+
"$NODE_BINARY" "#{script_path}" \\
|
|
1282
|
+
-c "$CONFIG" \\
|
|
1283
|
+
-m "#{product_name}" \\
|
|
1284
|
+
-x "#{xcframeworks_dir}" \\
|
|
1285
|
+
#{shared_args}
|
|
1286
|
+
SH
|
|
1287
|
+
end
|
|
1288
|
+
end
|
|
1289
|
+
|
|
1290
|
+
# '--shared "<Name>:<source_base>"' tokens for shared SPM deps this pod owns.
|
|
1291
|
+
# Non-owners reach the framework via FRAMEWORK_SEARCH_PATHS at link time.
|
|
1292
|
+
def shared_dep_switch_args(pod_name, pod_info)
|
|
1293
|
+
return [] unless pod_info && pod_info[:spm_dependency_frameworks]
|
|
1294
|
+
@framework_owner_map ||= {}
|
|
1295
|
+
pod_info[:spm_dependency_frameworks].filter_map do |dep_name|
|
|
1296
|
+
next nil unless @framework_owner_map[dep_name] == pod_name
|
|
1297
|
+
source_base = shared_spm_dep_source_base(dep_name, pod_info)
|
|
1298
|
+
next nil unless source_base
|
|
1299
|
+
%(--shared "#{dep_name}:#{source_base.gsub(/[\\"$`]/) { |c| "\\#{c}" }}")
|
|
1300
|
+
end
|
|
1226
1301
|
end
|
|
1227
1302
|
|
|
1228
1303
|
# Returns the shell script for the dSYM source map resolution phase.
|
|
@@ -1811,6 +1886,43 @@ module Expo
|
|
|
1811
1886
|
own_resolution
|
|
1812
1887
|
end
|
|
1813
1888
|
|
|
1889
|
+
# Candidate parent dirs (each holds <flavor>/<Name>.xcframework subtrees) for
|
|
1890
|
+
# a shared SPM dep. Ordered: EXPO_PRECOMPILED_MODULES_PATH override, monorepo
|
|
1891
|
+
# .spm-deps, then the consumer-side npm-bundled location.
|
|
1892
|
+
def shared_spm_dep_source_base_candidates(dep_name, pod_info)
|
|
1893
|
+
candidates = []
|
|
1894
|
+
candidates << File.join(custom_modules_path, SHARED_SPM_DEPS_SOURCE_DIR, dep_name) if custom_modules_path
|
|
1895
|
+
candidates << File.join(memoized_repo_root, 'packages', 'precompile', PRECOMPILE_BUILD_DIR, SHARED_SPM_DEPS_SOURCE_DIR, dep_name) if memoized_repo_root
|
|
1896
|
+
candidates << File.join(pod_info[:package_root], BUNDLED_SHARED_SPM_DEPS_SUBPATH, dep_name) if pod_info && pod_info[:package_root]
|
|
1897
|
+
candidates
|
|
1898
|
+
end
|
|
1899
|
+
|
|
1900
|
+
# First candidate base that has at least one flavor on disk (used to build switch-script source_base args).
|
|
1901
|
+
def shared_spm_dep_source_base(dep_name, pod_info)
|
|
1902
|
+
shared_spm_dep_source_base_candidates(dep_name, pod_info).find do |base|
|
|
1903
|
+
%w[debug release].any? { |f| File.directory?(File.join(base, f, "#{dep_name}.xcframework")) }
|
|
1904
|
+
end
|
|
1905
|
+
end
|
|
1906
|
+
|
|
1907
|
+
# First candidate that has the requested flavor on disk (walks all candidates so a partial monorepo doesn't shadow a complete npm bundle).
|
|
1908
|
+
def shared_spm_dep_xcframework_path(dep_name, pod_info, flavor)
|
|
1909
|
+
shared_spm_dep_source_base_candidates(dep_name, pod_info).each do |base|
|
|
1910
|
+
path = File.join(base, flavor, "#{dep_name}.xcframework")
|
|
1911
|
+
return path if File.directory?(path)
|
|
1912
|
+
end
|
|
1913
|
+
nil
|
|
1914
|
+
end
|
|
1915
|
+
|
|
1916
|
+
def memoized_repo_root
|
|
1917
|
+
return @repo_root if @memoized_repo_root_set
|
|
1918
|
+
begin
|
|
1919
|
+
@repo_root = find_repo_root
|
|
1920
|
+
ensure
|
|
1921
|
+
@memoized_repo_root_set = true
|
|
1922
|
+
end
|
|
1923
|
+
@repo_root
|
|
1924
|
+
end
|
|
1925
|
+
|
|
1814
1926
|
def resolve_prebuilt_tarball(pod_info, product_name, flavor, pod_name = nil)
|
|
1815
1927
|
tarball = File.join(pod_info[:build_output_dir], flavor, 'xcframeworks', "#{product_name}.tar.gz")
|
|
1816
1928
|
return tarball if File.exist?(tarball)
|
|
@@ -2,52 +2,32 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Replace XCFramework for Debug/Release Configuration
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Per-pod product swap: extracts <xcframeworksDir>/artifacts/<module>-<config>.tar.gz
|
|
6
|
+
* over <Product>.xcframework, gated on <xcframeworksDir>/artifacts/.last_build_configuration.
|
|
7
|
+
* Sibling shared-dep symlinks under <xcframeworksDir>/ are preserved (only the product
|
|
8
|
+
* xcframework is wiped before re-extracting).
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* <Product>-release.tar.gz (tarball, source of truth)
|
|
14
|
-
* .last_build_configuration
|
|
15
|
-
* <Product>.xcframework/ (real dir, extracted from tarball)
|
|
16
|
-
* <Dependency>.xcframework/ (real dir, if any, extracted from same tarball)
|
|
10
|
+
* Shared-dep repoint (each --shared entry): atomically replaces
|
|
11
|
+
* <xcframeworksDir>/<Name>.xcframework with a symlink to <source_base>/<config>/<Name>.xcframework
|
|
12
|
+
* and writes <xcframeworksDir>/artifacts/<Name>.last_config. The owner pod (decided at pod
|
|
13
|
+
* install time by ensure_shared_spm_deps) receives --shared args for each dep it owns.
|
|
17
14
|
*
|
|
18
15
|
* Usage:
|
|
19
|
-
* node replace-xcframework.js -c <CONFIG> -m <
|
|
20
|
-
*
|
|
21
|
-
* Arguments:
|
|
22
|
-
* -c, --config Build configuration: "debug" or "release"
|
|
23
|
-
* -m, --module Module/product name (used for tarball lookup and logging)
|
|
24
|
-
* -x, --xcframeworks Path to the pod directory (Pods/<PodName>/)
|
|
25
|
-
*
|
|
26
|
-
* The script:
|
|
27
|
-
* 1. Finds the tarball: <xcframeworksDir>/artifacts/<module>-<config>.tar.gz
|
|
28
|
-
* 2. Checks artifacts/.last_build_configuration — skips if unchanged
|
|
29
|
-
* 3. Removes all *.xcframework directories in xcframeworksDir
|
|
30
|
-
* 4. Extracts the tarball: tar -xzf ... -C <xcframeworksDir>
|
|
31
|
-
* 5. Writes the new config to artifacts/.last_build_configuration
|
|
16
|
+
* node replace-xcframework.js -c <CONFIG> -m <MODULE> -x <XCFRAMEWORKS_DIR>
|
|
17
|
+
* [--shared <Name>:<source_base>]...
|
|
32
18
|
*
|
|
33
19
|
* Based on React Native's replace-rncore-version.js pattern.
|
|
34
20
|
*/
|
|
35
21
|
|
|
22
|
+
const { spawnSync } = require('child_process');
|
|
36
23
|
const fs = require('fs');
|
|
37
24
|
const path = require('path');
|
|
38
|
-
const { spawnSync } = require('child_process');
|
|
39
25
|
|
|
40
26
|
const LOG_PREFIX = '[Expo XCFramework]';
|
|
41
27
|
|
|
42
|
-
// Parse command line arguments
|
|
43
28
|
function parseArgs() {
|
|
44
29
|
const args = process.argv.slice(2);
|
|
45
|
-
const result = {
|
|
46
|
-
config: null,
|
|
47
|
-
module: null,
|
|
48
|
-
xcframeworksDir: null,
|
|
49
|
-
};
|
|
50
|
-
|
|
30
|
+
const result = { config: null, module: null, xcframeworksDir: null, sharedDeps: [] };
|
|
51
31
|
for (let i = 0; i < args.length; i++) {
|
|
52
32
|
switch (args[i]) {
|
|
53
33
|
case '-c':
|
|
@@ -62,116 +42,120 @@ function parseArgs() {
|
|
|
62
42
|
case '--xcframeworks':
|
|
63
43
|
result.xcframeworksDir = args[++i];
|
|
64
44
|
break;
|
|
45
|
+
case '-s':
|
|
46
|
+
case '--shared': {
|
|
47
|
+
const spec = args[++i] || '';
|
|
48
|
+
const colon = spec.indexOf(':');
|
|
49
|
+
if (colon === -1) {
|
|
50
|
+
console.error(`${LOG_PREFIX} Invalid --shared (expected "<Name>:<source_base>"): ${spec}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
result.sharedDeps.push({ name: spec.slice(0, colon), sourceBase: spec.slice(colon + 1) });
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
65
56
|
}
|
|
66
57
|
}
|
|
67
|
-
|
|
68
58
|
return result;
|
|
69
59
|
}
|
|
70
60
|
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
console.error(
|
|
77
|
-
'Usage: replace-xcframework.js -c <CONFIG> -m <MODULE_NAME> -x <XCFRAMEWORKS_DIR>'
|
|
78
|
-
);
|
|
79
|
-
console.error(' -c, --config Build configuration: "debug" or "release"');
|
|
80
|
-
console.error(' -m, --module Module/product name');
|
|
81
|
-
console.error(' -x, --xcframeworks Path to the xcframeworks directory');
|
|
82
|
-
process.exit(1);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Normalize config to lowercase
|
|
86
|
-
const configLower = args.config.toLowerCase();
|
|
87
|
-
if (configLower !== 'debug' && configLower !== 'release') {
|
|
88
|
-
console.error(
|
|
89
|
-
`${LOG_PREFIX} Invalid configuration: ${args.config}. Must be "debug" or "release".`
|
|
90
|
-
);
|
|
91
|
-
process.exit(1);
|
|
61
|
+
function readState(file) {
|
|
62
|
+
try {
|
|
63
|
+
return fs.existsSync(file) ? fs.readFileSync(file, 'utf8').trim() : null;
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
92
66
|
}
|
|
67
|
+
}
|
|
93
68
|
|
|
94
|
-
|
|
95
|
-
const moduleName = args
|
|
96
|
-
|
|
97
|
-
// Validate xcframeworksDir exists
|
|
69
|
+
function processPerPodSwap(args, configLower) {
|
|
70
|
+
const { xcframeworksDir, module: moduleName } = args;
|
|
98
71
|
if (!fs.existsSync(xcframeworksDir) || !fs.statSync(xcframeworksDir).isDirectory()) {
|
|
99
72
|
console.error(`${LOG_PREFIX} ${moduleName}: Directory not found: ${xcframeworksDir}`);
|
|
100
73
|
process.exit(1);
|
|
101
74
|
}
|
|
102
75
|
|
|
103
|
-
// Ensure artifacts directory exists
|
|
104
76
|
const artifactsDir = path.join(xcframeworksDir, 'artifacts');
|
|
105
77
|
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
106
|
-
|
|
107
|
-
// Find the tarball for the requested configuration (stored in artifacts/)
|
|
108
78
|
const tarballPath = path.join(artifactsDir, `${moduleName}-${configLower}.tar.gz`);
|
|
109
79
|
const lastConfigFile = path.join(artifactsDir, '.last_build_configuration');
|
|
110
80
|
|
|
111
|
-
// Check if tarball exists
|
|
112
81
|
if (!fs.existsSync(tarballPath)) {
|
|
113
|
-
console.error(
|
|
114
|
-
`${LOG_PREFIX} ${moduleName}: Tarball not found at ${tarballPath}, skipping.`
|
|
115
|
-
);
|
|
82
|
+
console.error(`${LOG_PREFIX} ${moduleName}: Tarball not found at ${tarballPath}, skipping.`);
|
|
116
83
|
return;
|
|
117
84
|
}
|
|
118
85
|
|
|
119
|
-
|
|
120
|
-
let lastConfig = null;
|
|
121
|
-
if (fs.existsSync(lastConfigFile)) {
|
|
122
|
-
try {
|
|
123
|
-
lastConfig = fs.readFileSync(lastConfigFile, 'utf8').trim();
|
|
124
|
-
} catch (e) {
|
|
125
|
-
// Ignore read errors — will proceed with extraction
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Check if configuration has changed
|
|
86
|
+
const lastConfig = readState(lastConfigFile);
|
|
130
87
|
if (lastConfig === configLower) {
|
|
131
88
|
console.log(`${LOG_PREFIX} ${moduleName}: Already extracted ${configLower}, skipping.`);
|
|
132
89
|
return;
|
|
133
90
|
}
|
|
134
91
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (stat.isDirectory() || stat.isSymbolicLink()) {
|
|
144
|
-
fs.rmSync(entryPath, { recursive: true, force: true });
|
|
145
|
-
}
|
|
146
|
-
} catch (e) {
|
|
147
|
-
console.error(`${LOG_PREFIX} ${moduleName}: Warning: failed to remove ${entry}: ${e.message}`);
|
|
92
|
+
// Only remove the product xcframework — shared-dep symlinks staged by
|
|
93
|
+
// ensure_shared_spm_deps are repointed separately below via --shared.
|
|
94
|
+
const productXcfw = path.join(xcframeworksDir, `${moduleName}.xcframework`);
|
|
95
|
+
try {
|
|
96
|
+
fs.rmSync(productXcfw, { recursive: true, force: true });
|
|
97
|
+
} catch (e) {
|
|
98
|
+
if (e.code !== 'ENOENT') {
|
|
99
|
+
console.error(`${LOG_PREFIX} ${moduleName}: failed to remove product xcframework: ${e.message}`);
|
|
148
100
|
}
|
|
149
101
|
}
|
|
150
102
|
|
|
151
|
-
|
|
152
|
-
const result = spawnSync('tar', ['-xzf', tarballPath, '-C', xcframeworksDir], {
|
|
153
|
-
stdio: 'pipe',
|
|
154
|
-
});
|
|
155
|
-
|
|
103
|
+
const result = spawnSync('tar', ['-xzf', tarballPath, '-C', xcframeworksDir], { stdio: 'pipe' });
|
|
156
104
|
if (result.status !== 0) {
|
|
157
|
-
|
|
158
|
-
console.error(`${LOG_PREFIX} ${moduleName}: Failed to extract tarball: ${stderr}`);
|
|
105
|
+
console.error(`${LOG_PREFIX} ${moduleName}: tar failed: ${result.stderr?.toString().trim()}`);
|
|
159
106
|
process.exit(1);
|
|
160
107
|
}
|
|
161
108
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
109
|
+
fs.writeFileSync(lastConfigFile, configLower);
|
|
110
|
+
console.log(
|
|
111
|
+
lastConfig
|
|
112
|
+
? `${LOG_PREFIX} ${moduleName}: Switched from ${lastConfig} to ${configLower}.`
|
|
113
|
+
: `${LOG_PREFIX} ${moduleName}: Extracted ${configLower} tarball.`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function repointSharedDep(xcframeworksDir, name, sourceBase, configLower) {
|
|
118
|
+
const artifactsDir = path.join(xcframeworksDir, 'artifacts');
|
|
119
|
+
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
120
|
+
const stateFile = path.join(artifactsDir, `${name}.last_config`);
|
|
121
|
+
const linkPath = path.join(xcframeworksDir, `${name}.xcframework`);
|
|
122
|
+
|
|
123
|
+
// Trust the state file only when the symlink is still present — an externally
|
|
124
|
+
// deleted symlink (e.g. clear_cocoapods_cache wiping the pod dir) must trigger
|
|
125
|
+
// a re-link even if the state file claims the right config.
|
|
126
|
+
if (readState(stateFile) === configLower && fs.existsSync(linkPath)) return;
|
|
127
|
+
|
|
128
|
+
const target = path.join(sourceBase, configLower, `${name}.xcframework`);
|
|
129
|
+
if (!fs.existsSync(target)) {
|
|
130
|
+
console.error(
|
|
131
|
+
`${LOG_PREFIX} Shared dep ${name}: target not found at ${target}. Run the precompile prebuild pipeline for the ${configLower} flavor, or ensure prebuilds/spm-deps/${name}/${configLower}/${name}.xcframework ships with the consuming package.`
|
|
132
|
+
);
|
|
133
|
+
process.exit(1);
|
|
167
134
|
}
|
|
168
135
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
136
|
+
fs.rmSync(linkPath, { recursive: true, force: true });
|
|
137
|
+
fs.symlinkSync(target, linkPath);
|
|
138
|
+
fs.writeFileSync(stateFile, configLower);
|
|
139
|
+
console.log(`${LOG_PREFIX} Shared dep ${name}: repointed to ${configLower} (${target}).`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function main() {
|
|
143
|
+
const args = parseArgs();
|
|
144
|
+
if (!args.config || !args.module || !args.xcframeworksDir) {
|
|
145
|
+
console.error(
|
|
146
|
+
'Usage: replace-xcframework.js -c <CONFIG> -m <MODULE> -x <XCFRAMEWORKS_DIR> [--shared <Name>:<source_base>]...'
|
|
172
147
|
);
|
|
173
|
-
|
|
174
|
-
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
const configLower = args.config.toLowerCase();
|
|
151
|
+
if (configLower !== 'debug' && configLower !== 'release') {
|
|
152
|
+
console.error(`${LOG_PREFIX} Invalid configuration: ${args.config}. Must be "debug" or "release".`);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
processPerPodSwap(args, configLower);
|
|
157
|
+
for (const dep of args.sharedDeps) {
|
|
158
|
+
repointSharedDep(args.xcframeworksDir, dep.name, dep.sourceBase, configLower);
|
|
175
159
|
}
|
|
176
160
|
}
|
|
177
161
|
|
|
@@ -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;
|