@mcesystems/apple-kit 1.0.31 → 1.0.33

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