@mcesystems/apple-kit 1.0.27 → 1.0.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcesystems/apple-kit",
3
- "version": "1.0.27",
3
+ "version": "1.0.28",
4
4
  "description": "iOS device management toolkit using libimobiledevice command-line tools",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -17,15 +17,16 @@
17
17
  * - Windows: Builds from source using MSYS2/MinGW (requires manual setup)
18
18
  */
19
19
 
20
- import { exec } from "node:child_process";
20
+ import { exec, spawn } from "node:child_process";
21
21
  import {
22
- chmodSync,
23
- copyFileSync,
24
- existsSync,
25
- mkdirSync,
26
- readFileSync,
27
- readlinkSync,
28
- writeFileSync,
22
+ chmodSync,
23
+ copyFileSync,
24
+ existsSync,
25
+ mkdirSync,
26
+ readFileSync,
27
+ readdirSync,
28
+ readlinkSync,
29
+ writeFileSync,
29
30
  } from "node:fs";
30
31
  import path from "node:path";
31
32
  import { fileURLToPath } from "node:url";
@@ -43,21 +44,21 @@ const execAsync = promisify(exec);
43
44
 
44
45
  // Tools we need to bundle
45
46
  const REQUIRED_TOOLS = [
46
- "idevice_id",
47
- "ideviceinfo",
48
- "idevicepair",
49
- "idevicename",
50
- "idevicedebug",
51
- "ideviceinstaller",
52
- "iproxy",
47
+ "idevice_id",
48
+ "ideviceinfo",
49
+ "idevicepair",
50
+ "idevicename",
51
+ "idevicedebug",
52
+ "ideviceinstaller",
53
+ "iproxy",
53
54
  ];
54
55
 
55
56
  // Optional tools (won't fail if not found)
56
57
  const OPTIONAL_TOOLS = [
57
- "ideviceactivation",
58
- "idevicesyslog",
59
- "idevicescreenshot",
60
- "idevicediagnostics",
58
+ "ideviceactivation",
59
+ "idevicesyslog",
60
+ "idevicescreenshot",
61
+ "idevicediagnostics",
61
62
  ];
62
63
 
63
64
  // Homebrew paths on macOS
@@ -67,17 +68,27 @@ const HOMEBREW_INTEL_PATH = "/usr/local";
67
68
  // MSYS2 paths on Windows
68
69
  const MSYS2_DEFAULT_PATH = "C:\\msys64";
69
70
 
71
+ // System library prefixes that should NOT be copied (they exist on all macOS)
72
+ const SYSTEM_LIB_PREFIXES = [
73
+ "/System/Library/",
74
+ "/usr/lib/",
75
+ "/Library/Apple/",
76
+ ];
77
+
70
78
  // ============================================================================
71
79
  // Utility Functions
72
80
  // ============================================================================
73
81
 
74
82
  function printUsage(): void {
75
- console.log(`
83
+ console.log(`
76
84
  Usage: npx tsx export-resources.ts <target-path>
77
85
 
78
86
  Arguments:
79
87
  target-path Directory where resources will be exported
80
88
 
89
+ Options:
90
+ --skip-verify Skip verification step
91
+
81
92
  Examples:
82
93
  npx tsx export-resources.ts ./my-app/resources/apple-kit
83
94
  npx tsx export-resources.ts /absolute/path/to/resources
@@ -93,113 +104,277 @@ The script will create the following structure:
93
104
  }
94
105
 
95
106
  function resolveSymlink(filePath: string): string {
96
- try {
97
- const linkTarget = readlinkSync(filePath);
98
- if (path.isAbsolute(linkTarget)) {
99
- return linkTarget;
100
- }
101
- return path.resolve(path.dirname(filePath), linkTarget);
102
- } catch {
103
- return filePath; // Not a symlink
104
- }
107
+ try {
108
+ const linkTarget = readlinkSync(filePath);
109
+ if (path.isAbsolute(linkTarget)) {
110
+ return linkTarget;
111
+ }
112
+ return path.resolve(path.dirname(filePath), linkTarget);
113
+ } catch {
114
+ return filePath; // Not a symlink
115
+ }
105
116
  }
106
117
 
107
- interface DylibInfo {
108
- path: string;
109
- name: string;
118
+ /**
119
+ * Check if a library path is a system library that doesn't need to be copied
120
+ */
121
+ function isSystemLibrary(libPath: string): boolean {
122
+ return SYSTEM_LIB_PREFIXES.some((prefix) => libPath.startsWith(prefix));
110
123
  }
111
124
 
112
- async function getDylibDependencies(binaryPath: string): Promise<DylibInfo[]> {
113
- const dylibs: DylibInfo[] = [];
114
-
115
- try {
116
- const { stdout } = await execAsync(`otool -L "${binaryPath}"`);
117
- const lines = stdout.split("\n");
118
-
119
- for (const line of lines) {
120
- const match = line.match(/^\s+(\S+\.dylib)/);
121
- if (match) {
122
- const dylibPath = match[1];
123
- if (
124
- dylibPath.includes("/opt/homebrew") ||
125
- dylibPath.includes("/usr/local/opt")
126
- ) {
127
- dylibs.push({
128
- path: dylibPath,
129
- name: path.basename(dylibPath),
130
- });
131
- }
132
- }
133
- }
134
- } catch {
135
- console.warn(`Warning: Could not get dependencies for ${binaryPath}`);
136
- }
137
-
138
- return dylibs;
125
+ /**
126
+ * Check if a library path needs to be bundled (is from homebrew or similar)
127
+ */
128
+ function needsBundling(libPath: string): boolean {
129
+ if (isSystemLibrary(libPath)) {
130
+ return false;
131
+ }
132
+ if (libPath.startsWith("@")) {
133
+ // Already a relocatable path
134
+ return false;
135
+ }
136
+ // Bundle anything from /opt/homebrew, /usr/local/opt, or /usr/local/Cellar
137
+ return (
138
+ libPath.includes("/opt/homebrew") ||
139
+ libPath.includes("/usr/local/opt") ||
140
+ libPath.includes("/usr/local/Cellar") ||
141
+ libPath.includes("/usr/local/lib")
142
+ );
139
143
  }
140
144
 
141
- async function getAllDylibs(binaries: string[]): Promise<Map<string, string>> {
142
- const allDylibs = new Map<string, string>();
143
- const processed = new Set<string>();
145
+ interface DylibDependency {
146
+ originalPath: string;
147
+ name: string;
148
+ realPath: string;
149
+ }
144
150
 
145
- async function processBinary(binaryPath: string): Promise<void> {
146
- if (processed.has(binaryPath)) return;
147
- processed.add(binaryPath);
151
+ /**
152
+ * Get all dylib dependencies for a binary, including the original path as referenced
153
+ */
154
+ async function getDylibDependencies(binaryPath: string): Promise<DylibDependency[]> {
155
+ const dylibs: DylibDependency[] = [];
156
+
157
+ try {
158
+ const { stdout } = await execAsync(`otool -L "${binaryPath}"`);
159
+ const lines = stdout.split("\n");
160
+
161
+ for (const line of lines) {
162
+ // Match library paths (with or without version info in parentheses)
163
+ const match = line.match(/^\s+(.+?)\s+\(/);
164
+ if (match) {
165
+ const libPath = match[1].trim();
166
+ if (needsBundling(libPath)) {
167
+ const realPath = resolveSymlink(libPath);
168
+ dylibs.push({
169
+ originalPath: libPath,
170
+ name: path.basename(libPath),
171
+ realPath: existsSync(realPath) ? realPath : libPath,
172
+ });
173
+ }
174
+ }
175
+ }
176
+ } catch (error) {
177
+ console.warn(`Warning: Could not get dependencies for ${binaryPath}: ${error}`);
178
+ }
179
+
180
+ return dylibs;
181
+ }
148
182
 
149
- const deps = await getDylibDependencies(binaryPath);
150
- for (const dep of deps) {
151
- if (!allDylibs.has(dep.name)) {
152
- allDylibs.set(dep.name, dep.path);
153
- await processBinary(dep.path);
154
- }
155
- }
156
- }
183
+ /**
184
+ * Recursively collect ALL dylib dependencies
185
+ * Continues until no new dependencies are found
186
+ */
187
+ async function collectAllDependencies(
188
+ initialBinaries: string[]
189
+ ): Promise<Map<string, DylibDependency>> {
190
+ const allDylibs = new Map<string, DylibDependency>();
191
+ const processedPaths = new Set<string>();
192
+ const toProcess: string[] = [...initialBinaries];
193
+
194
+ console.log(" Scanning for dependencies...");
195
+
196
+ let iteration = 0;
197
+ while (toProcess.length > 0) {
198
+ iteration++;
199
+ const currentPath = toProcess.pop()!;
200
+
201
+ if (processedPaths.has(currentPath)) {
202
+ continue;
203
+ }
204
+ processedPaths.add(currentPath);
205
+
206
+ const deps = await getDylibDependencies(currentPath);
207
+
208
+ for (const dep of deps) {
209
+ if (!allDylibs.has(dep.name)) {
210
+ allDylibs.set(dep.name, dep);
211
+ // Add this dylib to be processed for its own dependencies
212
+ if (existsSync(dep.realPath)) {
213
+ toProcess.push(dep.realPath);
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ console.log(` Found ${allDylibs.size} unique dylib dependencies (${iteration} files scanned)`);
220
+ return allDylibs;
221
+ }
157
222
 
158
- for (const binary of binaries) {
159
- await processBinary(binary);
160
- }
223
+ /**
224
+ * Fix all library paths in a binary to use @loader_path
225
+ * This rewrites ALL non-system library references
226
+ */
227
+ async function fixAllLibraryPaths(
228
+ binaryPath: string,
229
+ allDylibNames: Set<string>
230
+ ): Promise<string[]> {
231
+ const unfixedPaths: string[] = [];
232
+
233
+ try {
234
+ const { stdout } = await execAsync(`otool -L "${binaryPath}"`);
235
+ const lines = stdout.split("\n");
236
+
237
+ for (const line of lines) {
238
+ const match = line.match(/^\s+(.+?)\s+\(/);
239
+ if (match) {
240
+ const libPath = match[1].trim();
241
+
242
+ // Skip system libraries and already-fixed paths
243
+ if (isSystemLibrary(libPath) || libPath.startsWith("@")) {
244
+ continue;
245
+ }
246
+
247
+ const libName = path.basename(libPath);
248
+
249
+ // Check if we have this dylib in our collection
250
+ if (allDylibNames.has(libName)) {
251
+ try {
252
+ await execAsync(
253
+ `install_name_tool -change "${libPath}" "@loader_path/${libName}" "${binaryPath}"`
254
+ );
255
+ } catch (error) {
256
+ console.warn(` Warning: Could not fix path ${libPath} in ${path.basename(binaryPath)}`);
257
+ unfixedPaths.push(libPath);
258
+ }
259
+ } else {
260
+ // This is a non-system library we don't have - this is a problem
261
+ unfixedPaths.push(libPath);
262
+ }
263
+ }
264
+ }
265
+ } catch (error) {
266
+ console.warn(`Warning: Could not process ${binaryPath}: ${error}`);
267
+ }
268
+
269
+ return unfixedPaths;
270
+ }
161
271
 
162
- return allDylibs;
272
+ /**
273
+ * Fix the install name (ID) of a dylib to use @loader_path
274
+ */
275
+ async function fixDylibId(dylibPath: string): Promise<void> {
276
+ const dylibName = path.basename(dylibPath);
277
+ try {
278
+ await execAsync(`install_name_tool -id "@loader_path/${dylibName}" "${dylibPath}"`);
279
+ } catch {
280
+ console.warn(` Warning: Could not fix dylib ID for ${dylibName}`);
281
+ }
163
282
  }
164
283
 
165
- async function fixDylibPaths(
166
- binaryPath: string,
167
- dylibs: Map<string, string>
168
- ): Promise<void> {
169
- const { stdout } = await execAsync(`otool -L "${binaryPath}"`);
170
- const lines = stdout.split("\n");
171
-
172
- for (const [dylibName] of dylibs) {
173
- for (const line of lines) {
174
- const match = line.match(/^\s+(\S+)/);
175
- if (match) {
176
- const depPath = match[1];
177
- if (
178
- depPath.includes(dylibName) &&
179
- !depPath.startsWith("@loader_path")
180
- ) {
181
- try {
182
- await execAsync(
183
- `install_name_tool -change "${depPath}" "@loader_path/${dylibName}" "${binaryPath}"`
184
- );
185
- } catch {
186
- // Ignore errors
187
- }
188
- }
189
- }
190
- }
191
- }
284
+ /**
285
+ * Ad-hoc code sign a binary (required after modifying with install_name_tool on modern macOS)
286
+ */
287
+ async function codesignBinary(binaryPath: string): Promise<boolean> {
288
+ try {
289
+ await execAsync(`codesign --force --sign - "${binaryPath}"`);
290
+ return true;
291
+ } catch (error) {
292
+ console.warn(` Warning: Could not code sign ${path.basename(binaryPath)}: ${error}`);
293
+ return false;
294
+ }
192
295
  }
193
296
 
194
- async function fixDylibId(dylibPath: string): Promise<void> {
195
- const dylibName = path.basename(dylibPath);
196
- try {
197
- await execAsync(
198
- `install_name_tool -id "@loader_path/${dylibName}" "${dylibPath}"`
199
- );
200
- } catch {
201
- console.warn(`Warning: Could not fix dylib id for ${dylibPath}`);
202
- }
297
+ /**
298
+ * Verify that a binary can be loaded and executed
299
+ */
300
+ async function verifyBinary(binaryPath: string, binDir: string): Promise<{ success: boolean; error?: string }> {
301
+ const binaryName = path.basename(binaryPath);
302
+
303
+ // Check that otool shows no broken paths
304
+ try {
305
+ const { stdout } = await execAsync(`otool -L "${binaryPath}"`);
306
+ const lines = stdout.split("\n");
307
+
308
+ for (const line of lines) {
309
+ const match = line.match(/^\s+(.+?)\s+\(/);
310
+ if (match) {
311
+ const libPath = match[1].trim();
312
+
313
+ // Skip system libraries
314
+ if (isSystemLibrary(libPath)) {
315
+ continue;
316
+ }
317
+
318
+ // If it's an @loader_path reference, check the file exists
319
+ if (libPath.startsWith("@loader_path/")) {
320
+ const libName = libPath.replace("@loader_path/", "");
321
+ const fullPath = path.join(binDir, libName);
322
+ if (!existsSync(fullPath)) {
323
+ return { success: false, error: `Missing library: ${libName}` };
324
+ }
325
+ } else if (!libPath.startsWith("@")) {
326
+ // Absolute path that's not a system library - this is a problem
327
+ return { success: false, error: `Unfixed absolute path: ${libPath}` };
328
+ }
329
+ }
330
+ }
331
+ } catch (error) {
332
+ return { success: false, error: `otool failed: ${error}` };
333
+ }
334
+
335
+ // For executable binaries (not dylibs), try to run them with --help or -h
336
+ if (!binaryName.endsWith(".dylib")) {
337
+ try {
338
+ // Use spawn with a timeout to test if the binary loads
339
+ const result = await new Promise<{ success: boolean; error?: string }>((resolve) => {
340
+ const child = spawn(binaryPath, ["--help"], {
341
+ timeout: 5000,
342
+ env: { ...process.env, DYLD_LIBRARY_PATH: binDir },
343
+ });
344
+
345
+ let stderr = "";
346
+ child.stderr?.on("data", (data) => {
347
+ stderr += data.toString();
348
+ });
349
+
350
+ child.on("error", (err) => {
351
+ resolve({ success: false, error: `Spawn error: ${err.message}` });
352
+ });
353
+
354
+ child.on("close", (code) => {
355
+ // Exit codes 0, 1, or 2 are acceptable (0=success, 1=error, 2=usage)
356
+ // What we're checking is that the binary LOADS, not that --help works
357
+ if (code !== null && code <= 2) {
358
+ resolve({ success: true });
359
+ } else if (stderr.includes("Library not loaded") || stderr.includes("image not found")) {
360
+ resolve({ success: false, error: stderr.split("\n")[0] });
361
+ } else {
362
+ // Other exit codes might be fine too
363
+ resolve({ success: true });
364
+ }
365
+ });
366
+ });
367
+
368
+ if (!result.success) {
369
+ return result;
370
+ }
371
+ } catch (error) {
372
+ // If spawn fails entirely, that's a problem
373
+ return { success: false, error: `Execution test failed: ${error}` };
374
+ }
375
+ }
376
+
377
+ return { success: true };
203
378
  }
204
379
 
205
380
  // ============================================================================
@@ -207,111 +382,186 @@ async function fixDylibId(dylibPath: string): Promise<void> {
207
382
  // ============================================================================
208
383
 
209
384
  function getHomebrewPath(): string | null {
210
- if (existsSync(path.join(HOMEBREW_ARM_PATH, "bin", "idevice_id"))) {
211
- return HOMEBREW_ARM_PATH;
212
- }
213
- if (existsSync(path.join(HOMEBREW_INTEL_PATH, "bin", "idevice_id"))) {
214
- return HOMEBREW_INTEL_PATH;
215
- }
216
- return null;
385
+ if (existsSync(path.join(HOMEBREW_ARM_PATH, "bin", "idevice_id"))) {
386
+ return HOMEBREW_ARM_PATH;
387
+ }
388
+ if (existsSync(path.join(HOMEBREW_INTEL_PATH, "bin", "idevice_id"))) {
389
+ return HOMEBREW_INTEL_PATH;
390
+ }
391
+ return null;
217
392
  }
218
393
 
219
- async function exportDarwinResources(targetDir: string): Promise<void> {
220
- console.log("\n=== Exporting macOS (Darwin) Resources ===\n");
221
-
222
- const homebrewPath = getHomebrewPath();
223
- if (!homebrewPath) {
224
- console.error("Error: Homebrew libimobiledevice not found.");
225
- console.error(
226
- "Please install it first: brew install libimobiledevice ideviceinstaller"
227
- );
228
- process.exit(1);
229
- }
230
-
231
- console.log(`Found Homebrew at: ${homebrewPath}`);
232
-
233
- const darwinBinDir = path.join(targetDir, "bin", "darwin");
234
- mkdirSync(darwinBinDir, { recursive: true });
235
-
236
- // Collect all binaries that exist
237
- const binaryPaths: string[] = [];
238
- const copiedBinaries: string[] = [];
239
-
240
- // Copy required tools
241
- for (const tool of REQUIRED_TOOLS) {
242
- const toolPath = path.join(homebrewPath, "bin", tool);
243
- if (existsSync(toolPath)) {
244
- const realPath = resolveSymlink(toolPath);
245
- binaryPaths.push(realPath);
246
-
247
- const destPath = path.join(darwinBinDir, tool);
248
- copyFileSync(realPath, destPath);
249
- chmodSync(destPath, 0o755);
250
- copiedBinaries.push(destPath);
251
- console.log(`✓ Copied ${tool}`);
252
- } else {
253
- console.error(`✗ Required tool not found: ${tool}`);
254
- console.error(
255
- ` Please install: brew install libimobiledevice ideviceinstaller`
256
- );
257
- process.exit(1);
258
- }
259
- }
260
-
261
- // Copy optional tools
262
- for (const tool of OPTIONAL_TOOLS) {
263
- const toolPath = path.join(homebrewPath, "bin", tool);
264
- if (existsSync(toolPath)) {
265
- const realPath = resolveSymlink(toolPath);
266
- binaryPaths.push(realPath);
267
-
268
- const destPath = path.join(darwinBinDir, tool);
269
- copyFileSync(realPath, destPath);
270
- chmodSync(destPath, 0o755);
271
- copiedBinaries.push(destPath);
272
- console.log(`✓ Copied ${tool} (optional)`);
273
- } else {
274
- console.log(`- Skipping ${tool} (optional, not installed)`);
275
- }
276
- }
277
-
278
- // Get all dylib dependencies
279
- console.log("\nCollecting dylib dependencies...");
280
- const allDylibs = await getAllDylibs(binaryPaths);
281
-
282
- // Copy dylibs
283
- console.log(`\nCopying ${allDylibs.size} dylibs...`);
284
- const copiedDylibs: string[] = [];
285
-
286
- for (const [dylibName, dylibPath] of allDylibs) {
287
- const realPath = resolveSymlink(dylibPath);
288
- const destPath = path.join(darwinBinDir, dylibName);
289
-
290
- if (existsSync(realPath)) {
291
- copyFileSync(realPath, destPath);
292
- chmodSync(destPath, 0o755);
293
- copiedDylibs.push(destPath);
294
- console.log(`✓ Copied ${dylibName}`);
295
- } else {
296
- console.warn(`Warning: Dylib not found: ${realPath}`);
297
- }
298
- }
299
-
300
- // Fix dylib paths in all binaries and dylibs
301
- console.log("\nFixing dylib paths...");
302
-
303
- for (const binaryPath of copiedBinaries) {
304
- await fixDylibPaths(binaryPath, allDylibs);
305
- console.log(`✓ Fixed paths in ${path.basename(binaryPath)}`);
306
- }
307
-
308
- for (const dylibPath of copiedDylibs) {
309
- await fixDylibId(dylibPath);
310
- await fixDylibPaths(dylibPath, allDylibs);
311
- console.log(`✓ Fixed paths in ${path.basename(dylibPath)}`);
312
- }
313
-
314
- console.log(`\n✓ macOS resources exported to: ${darwinBinDir}`);
394
+ async function exportDarwinResources(targetDir: string, skipVerify: boolean): Promise<void> {
395
+ console.log("\n=== Exporting macOS (Darwin) Resources ===\n");
396
+
397
+ const homebrewPath = getHomebrewPath();
398
+ if (!homebrewPath) {
399
+ console.error("Error: Homebrew libimobiledevice not found.");
400
+ console.error("Please install it first: brew install libimobiledevice ideviceinstaller");
401
+ process.exit(1);
402
+ }
403
+
404
+ console.log(`Found Homebrew at: ${homebrewPath}`);
405
+
406
+ const darwinBinDir = path.join(targetDir, "bin", "darwin");
407
+ mkdirSync(darwinBinDir, { recursive: true });
408
+
409
+ // =========================================================================
410
+ // Step 1: Copy all required and optional tools
411
+ // =========================================================================
412
+ console.log("\n--- Step 1: Copying binaries ---");
413
+
414
+ const binaryPaths: string[] = [];
415
+ const copiedBinaries: string[] = [];
416
+
417
+ // Copy required tools
418
+ for (const tool of REQUIRED_TOOLS) {
419
+ const toolPath = path.join(homebrewPath, "bin", tool);
420
+ if (existsSync(toolPath)) {
421
+ const realPath = resolveSymlink(toolPath);
422
+ binaryPaths.push(realPath);
423
+
424
+ const destPath = path.join(darwinBinDir, tool);
425
+ copyFileSync(realPath, destPath);
426
+ chmodSync(destPath, 0o755);
427
+ copiedBinaries.push(destPath);
428
+ console.log(` ✓ Copied ${tool}`);
429
+ } else {
430
+ console.error(` Required tool not found: ${tool}`);
431
+ console.error(` Please install: brew install libimobiledevice ideviceinstaller`);
432
+ process.exit(1);
433
+ }
434
+ }
435
+
436
+ // Copy optional tools
437
+ for (const tool of OPTIONAL_TOOLS) {
438
+ const toolPath = path.join(homebrewPath, "bin", tool);
439
+ if (existsSync(toolPath)) {
440
+ const realPath = resolveSymlink(toolPath);
441
+ binaryPaths.push(realPath);
442
+
443
+ const destPath = path.join(darwinBinDir, tool);
444
+ copyFileSync(realPath, destPath);
445
+ chmodSync(destPath, 0o755);
446
+ copiedBinaries.push(destPath);
447
+ console.log(` ✓ Copied ${tool} (optional)`);
448
+ } else {
449
+ console.log(` - Skipping ${tool} (optional, not installed)`);
450
+ }
451
+ }
452
+
453
+ // =========================================================================
454
+ // Step 2: Recursively collect ALL dylib dependencies
455
+ // =========================================================================
456
+ console.log("\n--- Step 2: Collecting dependencies ---");
457
+
458
+ const allDylibs = await collectAllDependencies(binaryPaths);
459
+
460
+ // =========================================================================
461
+ // Step 3: Copy all dylibs
462
+ // =========================================================================
463
+ console.log("\n--- Step 3: Copying dylibs ---");
464
+
465
+ const copiedDylibs: string[] = [];
466
+ const allDylibNames = new Set<string>();
467
+
468
+ for (const [dylibName, dep] of allDylibs) {
469
+ allDylibNames.add(dylibName);
470
+ const destPath = path.join(darwinBinDir, dylibName);
471
+
472
+ if (existsSync(dep.realPath)) {
473
+ copyFileSync(dep.realPath, destPath);
474
+ chmodSync(destPath, 0o755);
475
+ copiedDylibs.push(destPath);
476
+ console.log(` ✓ Copied ${dylibName}`);
477
+ } else {
478
+ console.warn(` ✗ Warning: Dylib not found: ${dep.realPath}`);
479
+ }
480
+ }
481
+
482
+ // =========================================================================
483
+ // Step 4: Fix library paths in all copied files
484
+ // =========================================================================
485
+ console.log("\n--- Step 4: Fixing library paths ---");
486
+
487
+ const allCopiedFiles = [...copiedBinaries, ...copiedDylibs];
488
+ const unfixedPaths: Map<string, string[]> = new Map();
489
+
490
+ // First, fix dylib IDs
491
+ for (const dylibPath of copiedDylibs) {
492
+ await fixDylibId(dylibPath);
493
+ }
494
+
495
+ // Then, fix all library references
496
+ for (const filePath of allCopiedFiles) {
497
+ const unfixed = await fixAllLibraryPaths(filePath, allDylibNames);
498
+ if (unfixed.length > 0) {
499
+ unfixedPaths.set(path.basename(filePath), unfixed);
500
+ }
501
+ console.log(` ✓ Fixed paths in ${path.basename(filePath)}`);
502
+ }
503
+
504
+ // Report any unfixed paths
505
+ if (unfixedPaths.size > 0) {
506
+ console.log("\n ⚠ Warning: Some library paths could not be fixed:");
507
+ for (const [file, paths] of unfixedPaths) {
508
+ for (const p of paths) {
509
+ console.log(` ${file}: ${p}`);
510
+ }
511
+ }
512
+ }
513
+
514
+ // =========================================================================
515
+ // Step 5: Code sign all binaries (required on modern macOS)
516
+ // =========================================================================
517
+ console.log("\n--- Step 5: Code signing ---");
518
+
519
+ let signedCount = 0;
520
+ for (const filePath of allCopiedFiles) {
521
+ const success = await codesignBinary(filePath);
522
+ if (success) {
523
+ signedCount++;
524
+ }
525
+ }
526
+ console.log(` ✓ Signed ${signedCount}/${allCopiedFiles.length} files`);
527
+
528
+ // =========================================================================
529
+ // Step 6: Verify binaries work
530
+ // =========================================================================
531
+ if (!skipVerify) {
532
+ console.log("\n--- Step 6: Verifying binaries ---");
533
+
534
+ let verifyErrors = 0;
535
+ for (const binaryPath of copiedBinaries) {
536
+ const result = await verifyBinary(binaryPath, darwinBinDir);
537
+ if (result.success) {
538
+ console.log(` ✓ ${path.basename(binaryPath)} OK`);
539
+ } else {
540
+ console.log(` ✗ ${path.basename(binaryPath)} FAILED: ${result.error}`);
541
+ verifyErrors++;
542
+ }
543
+ }
544
+
545
+ // Also verify dylibs have valid paths
546
+ for (const dylibPath of copiedDylibs) {
547
+ const result = await verifyBinary(dylibPath, darwinBinDir);
548
+ if (!result.success) {
549
+ console.log(` ✗ ${path.basename(dylibPath)} FAILED: ${result.error}`);
550
+ verifyErrors++;
551
+ }
552
+ }
553
+
554
+ if (verifyErrors > 0) {
555
+ console.log(`\n ⚠ ${verifyErrors} verification errors found`);
556
+ console.log(" Run with DYLD_PRINT_LIBRARIES=1 to debug library loading issues");
557
+ } else {
558
+ console.log(`\n ✓ All binaries verified successfully`);
559
+ }
560
+ } else {
561
+ console.log("\n--- Step 6: Verification skipped ---");
562
+ }
563
+
564
+ console.log(`\n✓ macOS resources exported to: ${darwinBinDir}`);
315
565
  }
316
566
 
317
567
  // ============================================================================
@@ -319,53 +569,48 @@ async function exportDarwinResources(targetDir: string): Promise<void> {
319
569
  // ============================================================================
320
570
 
321
571
  async function exportWindowsResources(targetDir: string): Promise<void> {
322
- console.log("\n=== Exporting Windows Resources ===\n");
323
-
324
- // Check if we're on Windows
325
- if (process.platform !== "win32") {
326
- console.log("Note: Windows resources can only be built on Windows.");
327
- console.log("Generating build script for later use...\n");
328
- generateWindowsBuildScript(targetDir);
329
- return;
330
- }
331
-
332
- // Check for MSYS2
333
- const msys2Path = process.env.MSYS2_PATH || MSYS2_DEFAULT_PATH;
334
- if (!existsSync(msys2Path)) {
335
- console.error(`Error: MSYS2 not found at ${msys2Path}`);
336
- console.error("Please install MSYS2 from https://www.msys2.org/");
337
- console.error(
338
- "Or set MSYS2_PATH environment variable to your MSYS2 installation."
339
- );
340
- generateWindowsBuildScript(targetDir);
341
- return;
342
- }
343
-
344
- // Generate the build script
345
- generateWindowsBuildScript(targetDir);
346
-
347
- console.log("\nTo build Windows resources:");
348
- console.log("1. Open MSYS2 MinGW 64-bit shell");
349
- console.log(`2. Run: bash "${path.join(targetDir, "build-windows.sh")}"`);
572
+ console.log("\n=== Exporting Windows Resources ===\n");
573
+
574
+ // Check if we're on Windows
575
+ if (process.platform !== "win32") {
576
+ console.log("Note: Windows resources can only be built on Windows.");
577
+ console.log("Generating build script for later use...\n");
578
+ generateWindowsBuildScript(targetDir);
579
+ return;
580
+ }
581
+
582
+ // Check for MSYS2
583
+ const msys2Path = process.env.MSYS2_PATH || MSYS2_DEFAULT_PATH;
584
+ if (!existsSync(msys2Path)) {
585
+ console.error(`Error: MSYS2 not found at ${msys2Path}`);
586
+ console.error("Please install MSYS2 from https://www.msys2.org/");
587
+ console.error("Or set MSYS2_PATH environment variable to your MSYS2 installation.");
588
+ generateWindowsBuildScript(targetDir);
589
+ return;
590
+ }
591
+
592
+ // Generate the build script
593
+ generateWindowsBuildScript(targetDir);
594
+
595
+ console.log("\nTo build Windows resources:");
596
+ console.log("1. Open MSYS2 MinGW 64-bit shell");
597
+ console.log(`2. Run: bash "${path.join(targetDir, "build-windows.sh")}"`);
350
598
  }
351
599
 
352
600
  function generateWindowsBuildScript(targetDir: string): void {
353
- const windowsBinDir = path.join(targetDir, "bin", "windows");
354
- mkdirSync(windowsBinDir, { recursive: true });
355
-
356
- // Read the template file
357
- const templatePath = path.join(__dirname, "build-windows.sh.template");
358
- let buildScript = readFileSync(templatePath, "utf-8");
359
-
360
- // Replace the placeholder with the actual target directory
361
- buildScript = buildScript.replace(
362
- "{{TARGET_DIR}}",
363
- windowsBinDir.replace(/\\/g, "/")
364
- );
365
-
366
- const scriptPath = path.join(targetDir, "build-windows.sh");
367
- writeFileSync(scriptPath, buildScript);
368
- console.log(`✓ Generated Windows build script: ${scriptPath}`);
601
+ const windowsBinDir = path.join(targetDir, "bin", "windows");
602
+ mkdirSync(windowsBinDir, { recursive: true });
603
+
604
+ // Read the template file
605
+ const templatePath = path.join(__dirname, "build-windows.sh.template");
606
+ let buildScript = readFileSync(templatePath, "utf-8");
607
+
608
+ // Replace the placeholder with the actual target directory
609
+ buildScript = buildScript.replace("{{TARGET_DIR}}", windowsBinDir.replace(/\\/g, "/"));
610
+
611
+ const scriptPath = path.join(targetDir, "build-windows.sh");
612
+ writeFileSync(scriptPath, buildScript);
613
+ console.log(`✓ Generated Windows build script: ${scriptPath}`);
369
614
  }
370
615
 
371
616
  // ============================================================================
@@ -373,10 +618,10 @@ function generateWindowsBuildScript(targetDir: string): void {
373
618
  // ============================================================================
374
619
 
375
620
  function createLicenseFile(targetDir: string): void {
376
- const licensesDir = path.join(targetDir, "licenses");
377
- mkdirSync(licensesDir, { recursive: true });
621
+ const licensesDir = path.join(targetDir, "licenses");
622
+ mkdirSync(licensesDir, { recursive: true });
378
623
 
379
- const lgplText = `GNU LESSER GENERAL PUBLIC LICENSE
624
+ const lgplText = `GNU LESSER GENERAL PUBLIC LICENSE
380
625
  Version 2.1, February 1999
381
626
 
382
627
  libimobiledevice and related tools are licensed under the
@@ -393,9 +638,9 @@ These binaries are bundled for convenience. The source code is available at:
393
638
  - https://github.com/libimobiledevice/ideviceinstaller
394
639
  `;
395
640
 
396
- const licensePath = path.join(licensesDir, "LGPL-2.1.txt");
397
- writeFileSync(licensePath, lgplText);
398
- console.log(`✓ Created license file: ${licensePath}`);
641
+ const licensePath = path.join(licensesDir, "LGPL-2.1.txt");
642
+ writeFileSync(licensePath, lgplText);
643
+ console.log(`✓ Created license file: ${licensePath}`);
399
644
  }
400
645
 
401
646
  // ============================================================================
@@ -403,42 +648,42 @@ These binaries are bundled for convenience. The source code is available at:
403
648
  // ============================================================================
404
649
 
405
650
  async function main(): Promise<void> {
406
- const args = process.argv.slice(2);
407
-
408
- if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
409
- printUsage();
410
- process.exit(args.length === 0 ? 1 : 0);
411
- }
412
-
413
- const targetPath = path.resolve(args[0]);
414
-
415
- console.log("=== Apple-kit Resources Export ===");
416
- console.log(`Platform: ${process.platform}`);
417
- console.log(`Target: ${targetPath}`);
418
-
419
- // Create target directory
420
- mkdirSync(targetPath, { recursive: true });
421
-
422
- // Export based on platform
423
- if (process.platform === "darwin") {
424
- await exportDarwinResources(targetPath);
425
- } else if (process.platform === "win32") {
426
- await exportWindowsResources(targetPath);
427
- } else {
428
- console.log("\nNote: This script supports macOS and Windows.");
429
- console.log("For Linux, install libimobiledevice-utils via your package manager.");
430
- }
431
-
432
- // Always create license file
433
- console.log("");
434
- createLicenseFile(targetPath);
435
-
436
- console.log("\n=== Export Complete ===");
437
- console.log(`Resources exported to: ${targetPath}`);
651
+ const args = process.argv.slice(2);
652
+
653
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
654
+ printUsage();
655
+ process.exit(args.length === 0 ? 1 : 0);
656
+ }
657
+
658
+ const skipVerify = args.includes("--skip-verify");
659
+ const targetPath = path.resolve(args.find((a) => !a.startsWith("--")) || ".");
660
+
661
+ console.log("=== Apple-kit Resources Export ===");
662
+ console.log(`Platform: ${process.platform}`);
663
+ console.log(`Target: ${targetPath}`);
664
+
665
+ // Create target directory
666
+ mkdirSync(targetPath, { recursive: true });
667
+
668
+ // Export based on platform
669
+ if (process.platform === "darwin") {
670
+ await exportDarwinResources(targetPath, skipVerify);
671
+ } else if (process.platform === "win32") {
672
+ await exportWindowsResources(targetPath);
673
+ } else {
674
+ console.log("\nNote: This script supports macOS and Windows.");
675
+ console.log("For Linux, install libimobiledevice-utils via your package manager.");
676
+ }
677
+
678
+ // Always create license file
679
+ console.log("");
680
+ createLicenseFile(targetPath);
681
+
682
+ console.log("\n=== Export Complete ===");
683
+ console.log(`Resources exported to: ${targetPath}`);
438
684
  }
439
685
 
440
686
  main().catch((error) => {
441
- console.error("Export failed:", error);
442
- process.exit(1);
687
+ console.error("Export failed:", error);
688
+ process.exit(1);
443
689
  });
444
-