@expo/build-tools 20.5.1 → 21.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/android/gradle.d.ts +5 -0
  2. package/dist/android/gradle.js +13 -33
  3. package/dist/common/installDependencies.js +21 -18
  4. package/dist/context.js +31 -1
  5. package/dist/steps/easFunctions.js +18 -0
  6. package/dist/steps/functions/capturePosthogEvent.d.ts +2 -0
  7. package/dist/steps/functions/capturePosthogEvent.js +79 -0
  8. package/dist/steps/functions/createPosthogAnnotation.d.ts +2 -0
  9. package/dist/steps/functions/createPosthogAnnotation.js +75 -0
  10. package/dist/steps/functions/finishIosSimulatorRecordings.d.ts +2 -0
  11. package/dist/steps/functions/finishIosSimulatorRecordings.js +24 -0
  12. package/dist/steps/functions/rolloutPosthogFlag.d.ts +2 -0
  13. package/dist/steps/functions/rolloutPosthogFlag.js +208 -0
  14. package/dist/steps/functions/startAgentDeviceRemoteSession.js +21 -8
  15. package/dist/steps/functions/startArgentRemoteSession.d.ts +14 -0
  16. package/dist/steps/functions/startArgentRemoteSession.js +105 -44
  17. package/dist/steps/functions/startIosSimulatorRecordings.d.ts +2 -0
  18. package/dist/steps/functions/startIosSimulatorRecordings.js +20 -0
  19. package/dist/steps/functions/startServeSimRemoteSession.js +7 -5
  20. package/dist/steps/functions/uploadDeviceRunSessionScreenRecordings.d.ts +3 -0
  21. package/dist/steps/functions/uploadDeviceRunSessionScreenRecordings.js +91 -0
  22. package/dist/steps/functions/uploadPosthogSourcemaps.d.ts +2 -0
  23. package/dist/steps/functions/uploadPosthogSourcemaps.js +83 -0
  24. package/dist/steps/functions/waitForPosthogMetric.d.ts +2 -0
  25. package/dist/steps/functions/waitForPosthogMetric.js +135 -0
  26. package/dist/steps/functions/waitForPosthogQuery.d.ts +2 -0
  27. package/dist/steps/functions/waitForPosthogQuery.js +95 -0
  28. package/dist/steps/utils/IosSimulatorRecordingUtils.d.ts +17 -0
  29. package/dist/steps/utils/IosSimulatorRecordingUtils.js +218 -0
  30. package/dist/steps/utils/PosthogClient.d.ts +46 -0
  31. package/dist/steps/utils/PosthogClient.js +156 -0
  32. package/dist/steps/utils/PosthogUtils.d.ts +41 -0
  33. package/dist/steps/utils/PosthogUtils.js +65 -0
  34. package/dist/steps/utils/agentDeviceArtifacts.d.ts +27 -0
  35. package/dist/steps/utils/agentDeviceArtifacts.js +133 -0
  36. package/dist/steps/utils/android/gradle.d.ts +5 -0
  37. package/dist/steps/utils/android/gradle.js +13 -62
  38. package/dist/steps/utils/argentArtifacts.d.ts +5 -3
  39. package/dist/steps/utils/argentArtifacts.js +95 -23
  40. package/dist/steps/utils/deviceRunSessionArtifacts.d.ts +3 -1
  41. package/dist/steps/utils/deviceRunSessionArtifacts.js +6 -2
  42. package/dist/steps/utils/remoteDeviceRunSession.d.ts +8 -0
  43. package/dist/steps/utils/remoteDeviceRunSession.js +62 -1
  44. package/dist/utils/AndroidEmulatorUtils.js +3 -0
  45. package/dist/utils/IosSimulatorUtils.d.ts +1 -0
  46. package/dist/utils/IosSimulatorUtils.js +41 -2
  47. package/dist/utils/expoUpdatesEmbedded.js +4 -0
  48. package/dist/utils/processes.d.ts +1 -0
  49. package/dist/utils/processes.js +23 -0
  50. package/package.json +9 -8
  51. package/resources/record-sim/Package.swift +28 -0
  52. package/resources/record-sim/README.md +79 -0
  53. package/resources/record-sim/Sources/RecordSim/FramebufferDisplaySource.swift +192 -0
  54. package/resources/record-sim/Sources/RecordSim/PrivateSimulatorServices.swift +96 -0
  55. package/resources/record-sim/Sources/RecordSim/RecordingModels.swift +70 -0
  56. package/resources/record-sim/Sources/RecordSim/RecordingOutputWriter.swift +136 -0
  57. package/resources/record-sim/Sources/RecordSim/SimulatorRecorder.swift +632 -0
  58. package/resources/record-sim/Sources/RecordSim/Utilities.swift +60 -0
  59. package/resources/record-sim/Sources/record-sim/main.swift +143 -0
@@ -0,0 +1,192 @@
1
+ import Foundation
2
+ import IOSurface
3
+ import ObjectiveC
4
+
5
+ final class FramebufferDisplaySource {
6
+ struct SurfaceSnapshot {
7
+ let surface: IOSurface
8
+ let width: Int
9
+ let height: Int
10
+ let seed: UInt32
11
+ }
12
+
13
+ private let deviceUDID: String
14
+ private let callbackQueue: DispatchQueue
15
+ private let onFrame: () -> Void
16
+ private let onSurfaceChange: () -> Void
17
+ private var descriptors: [NSObject] = []
18
+ private var callbackUUIDs: [ObjectIdentifier: NSUUID] = [:]
19
+ private var retainedBlocks: [AnyObject] = []
20
+ private var ioClient: NSObject?
21
+
22
+ init(
23
+ deviceUDID: String,
24
+ callbackQueue: DispatchQueue,
25
+ onFrame: @escaping () -> Void,
26
+ onSurfaceChange: @escaping () -> Void
27
+ ) {
28
+ self.deviceUDID = deviceUDID
29
+ self.callbackQueue = callbackQueue
30
+ self.onFrame = onFrame
31
+ self.onSurfaceChange = onSurfaceChange
32
+ }
33
+
34
+ func start() throws {
35
+ try PrivateSimulatorFrameworks.load()
36
+ guard let device = SimulatorDeviceLookup.find(udid: deviceUDID) else {
37
+ throw RecorderError.make(1, "Simulator \(deviceUDID) not found")
38
+ }
39
+ let state = device.value(forKey: "stateString") as? String ?? "unknown"
40
+ guard state == "Booted" else {
41
+ throw RecorderError.make(2, "Simulator \(deviceUDID) is not booted (state: \(state))")
42
+ }
43
+ guard
44
+ let io = device.perform(NSSelectorFromString("io"))?.takeUnretainedValue() as? NSObject
45
+ else {
46
+ throw RecorderError.make(3, "Failed to get simulator IO client")
47
+ }
48
+ ioClient = io
49
+ try wireUpFramebuffer()
50
+ }
51
+
52
+ func stop() {
53
+ unregisterCallbacks()
54
+ descriptors.removeAll()
55
+ retainedBlocks.removeAll()
56
+ ioClient = nil
57
+ }
58
+
59
+ func surfaceSnapshot() -> SurfaceSnapshot? {
60
+ let surfaceSelector = NSSelectorFromString("framebufferSurface")
61
+ var bestSnapshot: SurfaceSnapshot?
62
+ var bestArea = 0
63
+ for descriptor in descriptors {
64
+ guard let surfaceObject = descriptor.perform(surfaceSelector)?.takeUnretainedValue()
65
+ else {
66
+ continue
67
+ }
68
+ let surface = unsafeBitCast(surfaceObject, to: IOSurface.self)
69
+ let width = IOSurfaceGetWidth(surface)
70
+ let height = IOSurfaceGetHeight(surface)
71
+ let area = width * height
72
+ if area > bestArea {
73
+ bestSnapshot = SurfaceSnapshot(
74
+ surface: surface,
75
+ width: width,
76
+ height: height,
77
+ seed: IOSurfaceGetSeed(surface)
78
+ )
79
+ bestArea = area
80
+ }
81
+ }
82
+ return bestSnapshot
83
+ }
84
+
85
+ func rewireFramebuffer() throws {
86
+ try wireUpFramebuffer()
87
+ }
88
+
89
+ private func wireUpFramebuffer() throws {
90
+ guard let io = ioClient else {
91
+ throw RecorderError.make(3, "No simulator IO client")
92
+ }
93
+ io.perform(NSSelectorFromString("updateIOPorts"))
94
+ let nextDescriptors = try findFramebufferDescriptors(io: io)
95
+ unregisterCallbacks()
96
+ descriptors = nextDescriptors
97
+ retainedBlocks.removeAll()
98
+ do {
99
+ for descriptor in descriptors {
100
+ try registerCallbacks(descriptor: descriptor)
101
+ }
102
+ } catch {
103
+ unregisterCallbacks()
104
+ descriptors.removeAll()
105
+ retainedBlocks.removeAll()
106
+ throw error
107
+ }
108
+ }
109
+
110
+ private func findFramebufferDescriptors(io: NSObject) throws -> [NSObject] {
111
+ guard let ports = io.value(forKey: "deviceIOPorts") as? [NSObject] else {
112
+ throw RecorderError.make(4, "Failed to read simulator IO ports")
113
+ }
114
+ let portIdentifierSelector = NSSelectorFromString("portIdentifier")
115
+ let descriptorSelector = NSSelectorFromString("descriptor")
116
+ let surfaceSelector = NSSelectorFromString("framebufferSurface")
117
+
118
+ var candidates: [NSObject] = []
119
+ for port in ports {
120
+ guard port.responds(to: portIdentifierSelector),
121
+ let portIdentifier = port.perform(portIdentifierSelector)?.takeUnretainedValue(),
122
+ "\(portIdentifier)" == "com.apple.framebuffer.display",
123
+ port.responds(to: descriptorSelector),
124
+ let descriptor = port.perform(descriptorSelector)?.takeUnretainedValue()
125
+ as? NSObject,
126
+ descriptor.responds(to: surfaceSelector)
127
+ else {
128
+ continue
129
+ }
130
+ candidates.append(descriptor)
131
+ }
132
+ if candidates.isEmpty {
133
+ throw RecorderError.make(5, "No framebuffer display descriptor found")
134
+ }
135
+ return candidates
136
+ }
137
+
138
+ private func registerCallbacks(descriptor: NSObject) throws {
139
+ let selector = NSSelectorFromString(
140
+ "registerScreenCallbacksWithUUID:callbackQueue:frameCallback:surfacesChangedCallback:propertiesChangedCallback:"
141
+ )
142
+ guard descriptor.responds(to: selector) else {
143
+ throw RecorderError.make(6, "Framebuffer descriptor does not support screen callbacks")
144
+ }
145
+ guard let msgSendPointer = dlsym(UnsafeMutableRawPointer(bitPattern: -2), "objc_msgSend")
146
+ else {
147
+ throw RecorderError.make(7, "objc_msgSend not found")
148
+ }
149
+ typealias MsgSend =
150
+ @convention(c) (
151
+ AnyObject, Selector, AnyObject, AnyObject, AnyObject, AnyObject, AnyObject
152
+ ) -> Void
153
+ let msgSend = unsafeBitCast(msgSendPointer, to: MsgSend.self)
154
+
155
+ let uuid = NSUUID()
156
+ callbackUUIDs[ObjectIdentifier(descriptor)] = uuid
157
+
158
+ let frameCallback: @convention(block) () -> Void = { [weak self] in
159
+ self?.onFrame()
160
+ }
161
+ let surfacesCallback: @convention(block) (AnyObject?, AnyObject?) -> Void = {
162
+ [weak self] _, _ in
163
+ self?.onSurfaceChange()
164
+ }
165
+ let propertiesCallback: @convention(block) () -> Void = {}
166
+ retainedBlocks.append(frameCallback as AnyObject)
167
+ retainedBlocks.append(surfacesCallback as AnyObject)
168
+ retainedBlocks.append(propertiesCallback as AnyObject)
169
+
170
+ msgSend(
171
+ descriptor,
172
+ selector,
173
+ uuid,
174
+ callbackQueue as AnyObject,
175
+ frameCallback as AnyObject,
176
+ surfacesCallback as AnyObject,
177
+ propertiesCallback as AnyObject
178
+ )
179
+ }
180
+
181
+ private func unregisterCallbacks() {
182
+ let selector = NSSelectorFromString("unregisterScreenCallbacksWithUUID:")
183
+ for descriptor in descriptors {
184
+ if let uuid = callbackUUIDs[ObjectIdentifier(descriptor)],
185
+ descriptor.responds(to: selector)
186
+ {
187
+ descriptor.perform(selector, with: uuid)
188
+ }
189
+ }
190
+ callbackUUIDs.removeAll()
191
+ }
192
+ }
@@ -0,0 +1,96 @@
1
+ import Foundation
2
+ import ObjectiveC
3
+
4
+ enum XcodeDeveloperDirectory {
5
+ static let current: String = {
6
+ if let developerDir = ProcessInfo.processInfo.environment["DEVELOPER_DIR"],
7
+ !developerDir.isEmpty
8
+ {
9
+ return developerDir
10
+ }
11
+
12
+ let fallback = "/Applications/Xcode.app/Contents/Developer"
13
+ let pipe = Pipe()
14
+ let process = Process()
15
+ process.executableURL = URL(fileURLWithPath: "/usr/bin/xcode-select")
16
+ process.arguments = ["-p"]
17
+ process.standardOutput = pipe
18
+ do {
19
+ try process.run()
20
+ } catch {
21
+ return fallback
22
+ }
23
+ process.waitUntilExit()
24
+ return String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)?
25
+ .trimmingCharacters(in: .whitespacesAndNewlines) ?? fallback
26
+ }()
27
+ }
28
+
29
+ enum PrivateSimulatorFrameworks {
30
+ static func load() throws {
31
+ try loadResult.get()
32
+ }
33
+
34
+ private static let loadResult: Result<Void, Error> = {
35
+ let developerDir = XcodeDeveloperDirectory.current
36
+ let candidates = [
37
+ "/Library/Developer/PrivateFrameworks/CoreSimulator.framework/CoreSimulator",
38
+ "\(developerDir)/Library/PrivateFrameworks/CoreSimulator.framework/CoreSimulator",
39
+ "\(developerDir)/../SharedFrameworks/SimulatorKit.framework/SimulatorKit",
40
+ "\(developerDir)/Library/PrivateFrameworks/SimulatorKit.framework/SimulatorKit",
41
+ ]
42
+ var failures: [String] = []
43
+ for path in candidates {
44
+ if dlopen(path, RTLD_NOW) == nil {
45
+ let message = dlerror().map { String(cString: $0) } ?? "unknown error"
46
+ failures.append("\(path): \(message)")
47
+ }
48
+ }
49
+ guard NSClassFromString("SimServiceContext") != nil else {
50
+ let tried = candidates.joined(separator: ", ")
51
+ let errors = failures.isEmpty ? "no dlopen errors" : failures.joined(separator: "; ")
52
+ return .failure(
53
+ RecorderError.make(
54
+ 8,
55
+ "Failed to load CoreSimulator/SimulatorKit (tried: \(tried); errors: \(errors))"
56
+ )
57
+ )
58
+ }
59
+ return .success(())
60
+ }()
61
+ }
62
+
63
+ enum SimulatorDeviceLookup {
64
+ private static let context: NSObject? = {
65
+ guard let contextClass = NSClassFromString("SimServiceContext") as? NSObject.Type else {
66
+ return nil
67
+ }
68
+ let sharedSelector = NSSelectorFromString("sharedServiceContextForDeveloperDir:error:")
69
+ return contextClass.perform(
70
+ sharedSelector, with: XcodeDeveloperDirectory.current, with: nil)?
71
+ .takeUnretainedValue() as? NSObject
72
+ }()
73
+
74
+ static func find(udid: String) -> NSObject? {
75
+ guard let context else {
76
+ return nil
77
+ }
78
+ let deviceSetSelector = NSSelectorFromString("defaultDeviceSetWithError:")
79
+ guard
80
+ let deviceSet = context.perform(deviceSetSelector, with: nil)?
81
+ .takeUnretainedValue() as? NSObject
82
+ else {
83
+ return nil
84
+ }
85
+ guard let devices = deviceSet.value(forKey: "devices") as? [NSObject] else {
86
+ return nil
87
+ }
88
+ return devices.first {
89
+ ($0.value(forKey: "UDID") as? NSUUID)?.uuidString == udid
90
+ }
91
+ }
92
+
93
+ static func stateString(udid: String) -> String? {
94
+ find(udid: udid)?.value(forKey: "stateString") as? String
95
+ }
96
+ }
@@ -0,0 +1,70 @@
1
+ import Foundation
2
+
3
+ public enum RecorderCodec: String, Codable, Sendable {
4
+ case h264
5
+ case hevc
6
+ }
7
+
8
+ public struct SimulatorRecordingConfiguration: Sendable {
9
+ public var deviceUDID: String
10
+ public var outputDirectory: URL
11
+ public var fps: Int
12
+ public var bitrate: Int
13
+ public var codec: RecorderCodec
14
+ public var segmentDuration: TimeInterval
15
+ public var maxPendingFrames: Int
16
+ public var overwrite: Bool
17
+
18
+ public init(
19
+ deviceUDID: String,
20
+ outputDirectory: URL,
21
+ fps: Int = 30,
22
+ bitrate: Int = 30_000_000,
23
+ codec: RecorderCodec = .h264,
24
+ segmentDuration: TimeInterval = 120,
25
+ maxPendingFrames: Int = 8,
26
+ overwrite: Bool = true
27
+ ) {
28
+ self.deviceUDID = deviceUDID
29
+ self.outputDirectory = outputDirectory
30
+ self.fps = fps
31
+ self.bitrate = bitrate
32
+ self.codec = codec
33
+ self.segmentDuration = segmentDuration
34
+ self.maxPendingFrames = maxPendingFrames
35
+ self.overwrite = overwrite
36
+ }
37
+ }
38
+
39
+ public enum SegmentKind: String, Codable, Sendable {
40
+ case initialization
41
+ case media
42
+ }
43
+
44
+ public struct SegmentOutput: Sendable {
45
+ public let kind: SegmentKind
46
+ public let relativePath: String
47
+ public let url: URL
48
+ public let byteCount: Int
49
+ public let durationSeconds: Double?
50
+ }
51
+
52
+ public struct MediaSegmentRecord: Codable, Sendable {
53
+ public let file: String
54
+ public let durationSeconds: Double
55
+ }
56
+
57
+ public struct FirstFrameWallClock: Codable, Sendable {
58
+ public let unixMs: Int64
59
+ public let iso8601: String
60
+ }
61
+
62
+ public struct RecordingManifest: Codable, Sendable {
63
+ public let firstFrameWallClock: FirstFrameWallClock
64
+ public let hlsVersion: Int?
65
+ public let hlsTargetDurationSeconds: Int?
66
+ public let hlsMediaSequence: Int?
67
+ public let recording: String?
68
+ public let initSegment: String?
69
+ public let segments: [MediaSegmentRecord]
70
+ }
@@ -0,0 +1,136 @@
1
+ import AVFoundation
2
+ import Foundation
3
+
4
+ final class RecordingOutputWriter: NSObject, AVAssetWriterDelegate {
5
+ private let rootDirectory: URL
6
+ private let segmentsDirectory: URL
7
+ let singleRecordingURL: URL
8
+ private let stateQueue = DispatchQueue(label: "record-sim.segment-writer")
9
+ private let notificationQueue = DispatchQueue(label: "record-sim.segment-notifications")
10
+ private let onSegment: ((SegmentOutput) -> Void)?
11
+ private var initSegmentPath: String?
12
+ private var segments: [MediaSegmentRecord] = []
13
+ private var nextMediaSegmentIndex = 0
14
+ private var firstError: Error?
15
+
16
+ init(rootDirectory: URL, onSegment: ((SegmentOutput) -> Void)?) {
17
+ self.rootDirectory = rootDirectory
18
+ self.segmentsDirectory = rootDirectory.appendingPathComponent("segments", isDirectory: true)
19
+ self.singleRecordingURL = rootDirectory.appendingPathComponent("recording.mp4")
20
+ self.onSegment = onSegment
21
+ super.init()
22
+ }
23
+
24
+ func prepare(overwrite: Bool, segmented: Bool) throws {
25
+ if overwrite {
26
+ try? FileManager.default.removeItem(at: rootDirectory)
27
+ }
28
+ try FileManager.default.createDirectory(at: rootDirectory, withIntermediateDirectories: true)
29
+ if segmented {
30
+ try FileManager.default.createDirectory(at: segmentsDirectory, withIntermediateDirectories: true)
31
+ }
32
+ }
33
+
34
+ var error: Error? {
35
+ stateQueue.sync { firstError }
36
+ }
37
+
38
+ func assetWriter(
39
+ _ writer: AVAssetWriter,
40
+ didOutputSegmentData segmentData: Data,
41
+ segmentType: AVAssetSegmentType,
42
+ segmentReport: AVAssetSegmentReport?
43
+ ) {
44
+ var notification: SegmentOutput?
45
+
46
+ stateQueue.sync {
47
+ if firstError != nil {
48
+ return
49
+ }
50
+ do {
51
+ switch segmentType {
52
+ case .initialization:
53
+ let relativePath = "init.mp4"
54
+ let url = rootDirectory.appendingPathComponent(relativePath)
55
+ try segmentData.write(to: url, options: .atomic)
56
+ initSegmentPath = relativePath
57
+ notification = SegmentOutput(
58
+ kind: .initialization,
59
+ relativePath: relativePath,
60
+ url: url,
61
+ byteCount: segmentData.count,
62
+ durationSeconds: nil
63
+ )
64
+ case .separable:
65
+ let index = nextMediaSegmentIndex
66
+ let relativePath = String(format: "segments/segment-%06d.m4s", index)
67
+ let url = rootDirectory.appendingPathComponent(relativePath)
68
+ try segmentData.write(to: url, options: .atomic)
69
+
70
+ let videoReport = segmentReport?.trackReports.first(where: { $0.mediaType == .video })
71
+ let duration = videoReport.flatMap { timeSeconds($0.duration) } ?? 0
72
+ let record = MediaSegmentRecord(
73
+ file: relativePath,
74
+ durationSeconds: duration
75
+ )
76
+ nextMediaSegmentIndex += 1
77
+ segments.append(record)
78
+ notification = SegmentOutput(
79
+ kind: .media,
80
+ relativePath: relativePath,
81
+ url: url,
82
+ byteCount: segmentData.count,
83
+ durationSeconds: duration
84
+ )
85
+ default:
86
+ break
87
+ }
88
+ } catch {
89
+ firstError = error
90
+ }
91
+ }
92
+
93
+ if let notification, let onSegment {
94
+ notificationQueue.async {
95
+ onSegment(notification)
96
+ }
97
+ }
98
+ }
99
+
100
+ @discardableResult
101
+ func writeManifest(
102
+ configuration: SimulatorRecordingConfiguration,
103
+ firstFrameWallClock: Date
104
+ ) throws -> RecordingManifest {
105
+ try stateQueue.sync {
106
+ if let firstError {
107
+ throw firstError
108
+ }
109
+ let segmented = configuration.segmentDuration > 0
110
+ if segmented, initSegmentPath == nil {
111
+ throw RecorderError.make(50, "Missing initialization segment")
112
+ }
113
+ if segmented, segments.isEmpty {
114
+ throw RecorderError.make(51, "No media segments were written")
115
+ }
116
+ let targetDuration = segmented
117
+ ? max(1, Int(ceil(segments.map(\.durationSeconds).max() ?? configuration.segmentDuration)))
118
+ : nil
119
+ let manifest = RecordingManifest(
120
+ firstFrameWallClock: FirstFrameWallClock(
121
+ unixMs: unixMs(firstFrameWallClock),
122
+ iso8601: iso8601(firstFrameWallClock)
123
+ ),
124
+ hlsVersion: segmented ? 7 : nil,
125
+ hlsTargetDurationSeconds: targetDuration,
126
+ hlsMediaSequence: segmented ? 0 : nil,
127
+ recording: segmented ? nil : "recording.mp4",
128
+ initSegment: segmented ? initSegmentPath : nil,
129
+ segments: segments
130
+ )
131
+ let data = try JSONEncoder.prettySorted.encode(manifest)
132
+ try data.write(to: rootDirectory.appendingPathComponent("session.json"), options: .atomic)
133
+ return manifest
134
+ }
135
+ }
136
+ }