@tokamakdev/platform-ios-simulator-arm64 0.1.1-beta.38

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 (23) hide show
  1. package/ios-simulator-arm64/build/entrypoint +219 -0
  2. package/ios-simulator-arm64/frameworks/TokamakRuntime.framework/Headers/TokamakRuntime.h +45 -0
  3. package/ios-simulator-arm64/frameworks/TokamakRuntime.framework/Modules/module.modulemap +4 -0
  4. package/ios-simulator-arm64/frameworks/TokamakRuntime.framework/TokamakRuntime +0 -0
  5. package/ios-simulator-arm64/native-shell/TokamakPlugins.swift +216 -0
  6. package/ios-simulator-arm64/native-shell/TokamakShell.swift +645 -0
  7. package/ios-simulator-arm64/platform-pack.json +21 -0
  8. package/ios-simulator-arm64/tools/runtime/node_modules/@esbuild/darwin-arm64/README.md +3 -0
  9. package/ios-simulator-arm64/tools/runtime/node_modules/@esbuild/darwin-arm64/bin/esbuild +0 -0
  10. package/ios-simulator-arm64/tools/runtime/node_modules/@esbuild/darwin-arm64/package.json +20 -0
  11. package/ios-simulator-arm64/tools/runtime/node_modules/@esbuild/darwin-x64/README.md +3 -0
  12. package/ios-simulator-arm64/tools/runtime/node_modules/@esbuild/darwin-x64/bin/esbuild +0 -0
  13. package/ios-simulator-arm64/tools/runtime/node_modules/@esbuild/darwin-x64/package.json +20 -0
  14. package/ios-simulator-arm64/tools/runtime/node_modules/@esbuild/linux-x64/README.md +3 -0
  15. package/ios-simulator-arm64/tools/runtime/node_modules/@esbuild/linux-x64/bin/esbuild +0 -0
  16. package/ios-simulator-arm64/tools/runtime/node_modules/@esbuild/linux-x64/package.json +20 -0
  17. package/ios-simulator-arm64/tools/runtime/node_modules/@esbuild/win32-x64/README.md +3 -0
  18. package/ios-simulator-arm64/tools/runtime/node_modules/@esbuild/win32-x64/esbuild.exe +0 -0
  19. package/ios-simulator-arm64/tools/runtime/node_modules/@esbuild/win32-x64/package.json +20 -0
  20. package/ios-simulator-arm64/tools/runtime/node_modules/esbuild/LICENSE.md +21 -0
  21. package/ios-simulator-arm64/tools/runtime/node_modules/esbuild/bin/esbuild +24 -0
  22. package/ios-simulator-arm64/tools/tokamak-apple-signing +0 -0
  23. package/package.json +23 -0
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [[ $# -ne 3 || $1 != build ]]; then
5
+ echo "usage: entrypoint build INPUT OUTPUT" >&2
6
+ exit 2
7
+ fi
8
+
9
+ input=$2
10
+ output=$3
11
+ metadata=$input/metadata
12
+ native_shell=$input/native-shell
13
+ framework_root=$input/runtime/frameworks
14
+ app_slug=$(cat "$metadata/app-slug")
15
+ identifier=$(cat "$metadata/identifier")
16
+ platform=$(cat "$metadata/platform")
17
+ target=$(cat "$metadata/target")
18
+ project_dir=$(cat "$metadata/project-dir")
19
+ output_parent=$(dirname "$output")
20
+ module_cache=$output_parent/.tokamak/module-cache
21
+ temporary=$(mktemp -d "${TMPDIR:-/tmp}/tokamak-apple.XXXXXX")
22
+ trap 'rm -rf "$temporary"' EXIT
23
+ apple_tool=$PWD/tools/tokamak-apple-signing
24
+ if [[ ! -x "$apple_tool" ]]; then
25
+ echo "Apple platform pack is missing its Apple helper: $apple_tool" >&2
26
+ exit 1
27
+ fi
28
+
29
+ case "$target" in
30
+ macos-arm64)
31
+ sdk=macosx
32
+ triple=arm64-apple-macos14.0
33
+ ;;
34
+ macos-x64)
35
+ sdk=macosx
36
+ triple=x86_64-apple-macos14.0
37
+ ;;
38
+ ios-arm64)
39
+ sdk=iphoneos
40
+ triple=arm64-apple-ios17.0
41
+ ;;
42
+ ios-simulator-arm64)
43
+ sdk=iphonesimulator
44
+ triple=arm64-apple-ios17.0-simulator
45
+ ;;
46
+ ios-simulator-x64)
47
+ sdk=iphonesimulator
48
+ triple=x86_64-apple-ios17.0-simulator
49
+ ;;
50
+ *)
51
+ echo "unsupported Apple target: $target" >&2
52
+ exit 1
53
+ ;;
54
+ esac
55
+
56
+ if [[ "$platform" == macos ]]; then
57
+ bundle=$output
58
+ contents=$bundle/Contents
59
+ executable_dir=$contents/MacOS
60
+ app_dir=$contents/Resources/app
61
+ else
62
+ bundle=$output
63
+ contents=$bundle
64
+ executable_dir=$bundle
65
+ app_dir=$bundle/app
66
+ fi
67
+ icons=$input/icons/$platform
68
+ icon_info_plist=$temporary/icon-info.plist
69
+
70
+ sign_bundle() {
71
+ if [[ "$platform" == ios ]]; then
72
+ signing_args=(
73
+ sign
74
+ --project "$project_dir"
75
+ --bundle "$bundle"
76
+ --bundle-id "$identifier"
77
+ )
78
+ if [[ -f "$metadata/device-id" ]]; then
79
+ signing_args+=(--device-id "$(cat "$metadata/device-id")")
80
+ fi
81
+ "$apple_tool" "${signing_args[@]}"
82
+ else
83
+ codesign --force --timestamp=none --sign - "$bundle"
84
+ fi
85
+ }
86
+
87
+ bundle_key_file=$output_parent/.tokamak/$(basename "$output").input-key
88
+ if [[ -f "$metadata/input-key" ]]; then
89
+ plist_path=
90
+ case "$platform" in
91
+ ios|ios-simulator) plist_path=${TOKAMAK_IOS_PLIST:-} ;;
92
+ macos) plist_path=${TOKAMAK_MACOS_PLIST:-} ;;
93
+ esac
94
+ if [[ -n "$plist_path" && "$plist_path" != /* ]]; then
95
+ plist_path=$project_dir/$plist_path
96
+ fi
97
+ bundle_key=$(
98
+ {
99
+ cat "$metadata/input-key"
100
+ shasum -a 256 "$0" "$apple_tool"
101
+ if [[ -n "$plist_path" ]]; then
102
+ shasum -a 256 "$plist_path"
103
+ fi
104
+ env | LC_ALL=C sort | sed -n '/^TOKAMAK_/p' |
105
+ sed '/^TOKAMAK_IOS_TEAM_ID=/d; /^TOKAMAK_IOS_SIGNING_IDENTITY=/d; /^TOKAMAK_IOS_PROVISIONING_PROFILE=/d'
106
+ printf '%s\n' "${DEVELOPER_DIR:-}" "$PATH"
107
+ xcodebuild -version
108
+ } | shasum -a 256 | cut -d ' ' -f 1
109
+ )
110
+ if [[ -d "$bundle" && -f "$bundle_key_file" && $(cat "$bundle_key_file") == "$bundle_key" ]]; then
111
+ cp "$input/app/worker-environment.json" "$app_dir/worker-environment.json"
112
+ sign_bundle
113
+ exit 0
114
+ fi
115
+ fi
116
+
117
+ rm -f "$bundle_key_file"
118
+ rm -rf "$bundle"
119
+ mkdir -p "$executable_dir" "$app_dir" "$module_cache"
120
+ cp -R "$input/app/." "$app_dir/"
121
+
122
+ if [[ ( "$platform" == macos || "$platform" == ios || "$platform" == ios-simulator ) && -d "$icons/AppIcon.icon" ]]; then
123
+ if [[ "$platform" == macos ]]; then
124
+ icon_output=$contents/Resources
125
+ icon_minimum=14.0
126
+ icon_targets=(--target-device mac)
127
+ else
128
+ icon_output=$bundle
129
+ icon_minimum=17.0
130
+ icon_targets=(--target-device iphone --target-device ipad)
131
+ fi
132
+ xcrun --sdk "$sdk" actool \
133
+ --compile "$icon_output" \
134
+ --platform "$sdk" \
135
+ --minimum-deployment-target "$icon_minimum" \
136
+ --app-icon AppIcon \
137
+ "${icon_targets[@]}" \
138
+ --output-partial-info-plist "$icon_info_plist" \
139
+ --output-format human-readable-text \
140
+ "$icons/AppIcon.icon"
141
+ fi
142
+
143
+ shopt -s nullglob
144
+ swift_sources=()
145
+ for source in "$native_shell"/*.swift; do
146
+ [[ -f "$source" ]] || continue
147
+ swift_sources+=("$source")
148
+ done
149
+
150
+ if [[ ! -f "$native_shell/TokamakShell.swift" ]]; then
151
+ echo "Apple native shell source is missing: $native_shell/TokamakShell.swift" >&2
152
+ exit 1
153
+ fi
154
+
155
+ for plugin in "$input/plugins"/*; do
156
+ [[ -d "$plugin" ]] || continue
157
+ for source in "$plugin/sources"/*; do
158
+ [[ -f "$source" ]] || continue
159
+ case "$source" in
160
+ *.swift) swift_sources+=("$source") ;;
161
+ *)
162
+ echo "Apple plugin source must be Swift: $source" >&2
163
+ exit 1
164
+ ;;
165
+ esac
166
+ done
167
+ done
168
+
169
+ registry=$temporary/TokamakPluginRegistry.swift
170
+ {
171
+ printf '%s\n\n' 'import Foundation'
172
+ printf '%s\n' 'func tokamakPlugins() -> [any TokamakPlugin] {'
173
+ printf '%s\n' ' ['
174
+ for plugin in "$input/plugins"/*; do
175
+ [[ -d "$plugin" ]] || continue
176
+ printf ' %s(),\n' "$(cat "$plugin/class")"
177
+ done
178
+ printf '%s\n' ' ]'
179
+ printf '%s\n' '}'
180
+ } > "$registry"
181
+
182
+ swift_args=(
183
+ "${swift_sources[@]}"
184
+ "$registry"
185
+ -target "$triple"
186
+ -swift-version 5
187
+ -Osize
188
+ -whole-module-optimization
189
+ -module-cache-path "$module_cache"
190
+ -F "$framework_root"
191
+ -framework TokamakRuntime
192
+ -framework CoreFoundation
193
+ -lc++
194
+ -lresolv
195
+ -Xlinker -dead_strip
196
+ -Xlinker -no_exported_symbols
197
+ )
198
+ for plugin in "$input/plugins"/*; do
199
+ [[ -d "$plugin" ]] || continue
200
+ for framework_file in "$plugin/frameworks"/*; do
201
+ [[ -f "$framework_file" ]] || continue
202
+ swift_args+=(-framework "$(cat "$framework_file")")
203
+ done
204
+ done
205
+
206
+ xcrun --sdk "$sdk" swiftc "${swift_args[@]}" -o "$executable_dir/$app_slug"
207
+ xcrun --sdk "$sdk" strip "$executable_dir/$app_slug"
208
+
209
+ plist_args=(plist --input "$input" --output "$contents/Info.plist")
210
+ if [[ -f "$icon_info_plist" ]]; then
211
+ plist_args+=(--icon-info-plist "$icon_info_plist")
212
+ fi
213
+ "$apple_tool" "${plist_args[@]}"
214
+
215
+ sign_bundle
216
+ if [[ -n "${bundle_key:-}" ]]; then
217
+ mkdir -p "$(dirname "$bundle_key_file")"
218
+ printf '%s' "$bundle_key" > "$bundle_key_file"
219
+ fi
@@ -0,0 +1,45 @@
1
+ #ifndef TOKAMAK_RUNTIME_H
2
+ #define TOKAMAK_RUNTIME_H
3
+
4
+ #include <stdbool.h>
5
+ #include <stddef.h>
6
+ #include <stdint.h>
7
+
8
+ enum {
9
+ TOKAMAK_DECISION_DEFAULT = 0,
10
+ TOKAMAK_DECISION_CANCEL = 1,
11
+ TOKAMAK_DECISION_USE = 2,
12
+ };
13
+
14
+ typedef struct {
15
+ uint8_t *data;
16
+ size_t len;
17
+ } TokamakBytes;
18
+
19
+ typedef struct {
20
+ TokamakBytes certificate;
21
+ TokamakBytes private_key;
22
+ } TokamakIdentity;
23
+
24
+ void *tokamak_runtime_start(const char *packaged_dir, const char *state_dir,
25
+ const char *host, char *error, size_t error_len);
26
+ void *tokamak_runtime_start_development(const char *state_dir, const char *host,
27
+ const char *endpoint,
28
+ const char *session_token, char *error,
29
+ size_t error_len);
30
+ uint16_t tokamak_runtime_port(const void *runtime);
31
+ uint16_t tokamak_runtime_restore_gateway(const void *runtime, char *error,
32
+ size_t error_len);
33
+ bool tokamak_runtime_suspend(const void *runtime);
34
+ bool tokamak_runtime_resume(const void *runtime);
35
+ void tokamak_runtime_stop(void *runtime);
36
+
37
+ int32_t tokamak_runtime_server_authority(const void *runtime, const char *host,
38
+ TokamakBytes *authority);
39
+ int32_t tokamak_runtime_client_identity(const void *runtime, const char *host,
40
+ size_t previous_failures,
41
+ TokamakIdentity *identity);
42
+ void tokamak_bytes_free(TokamakBytes bytes);
43
+ void tokamak_identity_free(TokamakIdentity identity);
44
+
45
+ #endif
@@ -0,0 +1,4 @@
1
+ framework module TokamakRuntime {
2
+ umbrella header "TokamakRuntime.h"
3
+ export *
4
+ }
@@ -0,0 +1,216 @@
1
+ import Foundation
2
+ import WebKit
3
+
4
+ struct TokamakPluginError: Error {
5
+ let name: String
6
+ let message: String
7
+
8
+ static func notSupported(_ message: String) -> Self {
9
+ Self(name: "NotSupportedError", message: message)
10
+ }
11
+ }
12
+
13
+ typealias TokamakPluginReply = (Result<Any?, TokamakPluginError>) -> Void
14
+
15
+ protocol TokamakPlugin: AnyObject {
16
+ var id: String { get }
17
+
18
+ func call(
19
+ method: String,
20
+ arguments: Any,
21
+ reply: @escaping TokamakPluginReply
22
+ )
23
+
24
+ func subscribe(
25
+ method: String,
26
+ arguments: Any,
27
+ reply: @escaping TokamakPluginReply
28
+ ) -> (() -> Void)
29
+ }
30
+
31
+ extension TokamakPlugin {
32
+ func call(
33
+ method: String,
34
+ arguments: Any,
35
+ reply: @escaping TokamakPluginReply
36
+ ) {
37
+ reply(.failure(.notSupported("\(id).\(method) is not supported")))
38
+ }
39
+
40
+ func subscribe(
41
+ method: String,
42
+ arguments: Any,
43
+ reply: @escaping TokamakPluginReply
44
+ ) -> (() -> Void) {
45
+ reply(.failure(.notSupported("\(id).\(method) is not supported")))
46
+ return {}
47
+ }
48
+ }
49
+
50
+ final class TokamakPluginBridge: NSObject, WKScriptMessageHandler {
51
+ private struct RequestKey: Hashable {
52
+ let session: String
53
+ let id: Int
54
+ }
55
+
56
+ private let host: String
57
+ private let plugins: [String: any TokamakPlugin]
58
+ private var cancellations: [RequestKey: () -> Void] = [:]
59
+ weak var webView: WKWebView?
60
+
61
+ init(host: String, plugins: [any TokamakPlugin]) {
62
+ self.host = host
63
+ var pluginsById: [String: any TokamakPlugin] = [:]
64
+ for plugin in plugins {
65
+ precondition(pluginsById[plugin.id] == nil, "Duplicate plugin ID")
66
+ pluginsById[plugin.id] = plugin
67
+ }
68
+ self.plugins = pluginsById
69
+ }
70
+
71
+ func install(in controller: WKUserContentController) {
72
+ controller.add(self, name: "tokamak")
73
+ controller.addUserScript(
74
+ WKUserScript(
75
+ source: Self.bootstrap(host: host),
76
+ injectionTime: .atDocumentStart,
77
+ forMainFrameOnly: true
78
+ )
79
+ )
80
+ }
81
+
82
+ func close() {
83
+ let cancelAll = Array(cancellations.values)
84
+ cancellations.removeAll()
85
+ for cancel in cancelAll {
86
+ cancel()
87
+ }
88
+ }
89
+
90
+ func userContentController(
91
+ _ userContentController: WKUserContentController,
92
+ didReceive message: WKScriptMessage
93
+ ) {
94
+ let origin = message.frameInfo.securityOrigin
95
+ guard
96
+ message.frameInfo.isMainFrame,
97
+ origin.protocol == "https",
98
+ origin.host == host,
99
+ origin.port == 0 || origin.port == 443,
100
+ let encoded = message.body as? String,
101
+ let data = encoded.data(using: .utf8),
102
+ let request = try? JSONSerialization.jsonObject(with: data)
103
+ as? [String: Any],
104
+ let type = request["type"] as? String,
105
+ let session = request["session"] as? String
106
+ else {
107
+ return
108
+ }
109
+
110
+ if type == "reset" {
111
+ close()
112
+ return
113
+ }
114
+ guard let id = request["id"] as? Int else { return }
115
+ let key = RequestKey(session: session, id: id)
116
+ if type == "cancel" {
117
+ cancellations.removeValue(forKey: key)?()
118
+ return
119
+ }
120
+ dispatch(type: type, key: key, request: request)
121
+ }
122
+
123
+ private func dispatch(
124
+ type: String,
125
+ key: RequestKey,
126
+ request: [String: Any]
127
+ ) {
128
+ guard
129
+ let pluginId = request["plugin"] as? String,
130
+ let method = request["method"] as? String,
131
+ let plugin = plugins[pluginId]
132
+ else {
133
+ send(
134
+ key: key,
135
+ result: .failure(.notSupported("Plugin is not supported")),
136
+ done: true
137
+ )
138
+ return
139
+ }
140
+ let arguments = request["arguments"] ?? NSNull()
141
+ let reply: TokamakPluginReply = { [weak self] result in
142
+ self?.send(key: key, result: result, done: type == "call")
143
+ }
144
+ switch type {
145
+ case "call":
146
+ plugin.call(method: method, arguments: arguments, reply: reply)
147
+ case "subscribe":
148
+ let cancellation = plugin.subscribe(
149
+ method: method,
150
+ arguments: arguments,
151
+ reply: reply
152
+ )
153
+ cancellations[key] = cancellation
154
+ default:
155
+ send(
156
+ key: key,
157
+ result: .failure(.notSupported("Plugin operation is not supported")),
158
+ done: true
159
+ )
160
+ }
161
+ }
162
+
163
+ private func send(
164
+ key: RequestKey,
165
+ result: Result<Any?, TokamakPluginError>,
166
+ done: Bool
167
+ ) {
168
+ if done {
169
+ cancellations.removeValue(forKey: key)?()
170
+ }
171
+ var response: [String: Any] = [
172
+ "session": key.session,
173
+ "id": key.id,
174
+ "done": done,
175
+ ]
176
+ switch result {
177
+ case .success(let value):
178
+ response["value"] = value ?? NSNull()
179
+ case .failure(let error):
180
+ response["error"] = ["name": error.name, "message": error.message]
181
+ }
182
+ guard
183
+ JSONSerialization.isValidJSONObject(response),
184
+ let data = try? JSONSerialization.data(withJSONObject: response),
185
+ let json = String(data: data, encoding: .utf8)
186
+ else {
187
+ return
188
+ }
189
+ DispatchQueue.main.async { [weak self] in
190
+ guard
191
+ let self,
192
+ self.webView?.url?.scheme == "https",
193
+ self.webView?.url?.host == self.host,
194
+ self.webView?.url?.port == nil || self.webView?.url?.port == 443
195
+ else {
196
+ return
197
+ }
198
+ self.webView?.evaluateJavaScript(
199
+ "globalThis.__tokamakReceive?.(\(json))"
200
+ )
201
+ }
202
+ }
203
+
204
+ private static func bootstrap(host: String) -> String {
205
+ """
206
+ if (globalThis.location.origin === "https://\(host)") {
207
+ globalThis.__tokamakNative = {
208
+ onmessage: null,
209
+ postMessage(message) {
210
+ globalThis.webkit.messageHandlers.tokamak.postMessage(message);
211
+ }
212
+ };
213
+ }
214
+ """
215
+ }
216
+ }
@@ -0,0 +1,645 @@
1
+ import Foundation
2
+ import Network
3
+ import Security
4
+ import TokamakRuntime
5
+ import WebKit
6
+
7
+ #if os(macOS)
8
+ import AppKit
9
+ #else
10
+ import UIKit
11
+ #endif
12
+
13
+ private let startupErrorFile = "startup-error.log"
14
+ private let failurePage =
15
+ "<h1>App failed to start</h1><p>Check startup-error.log for details.</p>"
16
+
17
+ private enum ShellError: Error, CustomStringConvertible {
18
+ case configuration(String)
19
+ case runtime(String)
20
+
21
+ var description: String {
22
+ switch self {
23
+ case .configuration(let message), .runtime(let message):
24
+ return message
25
+ }
26
+ }
27
+ }
28
+
29
+ private enum AuthenticationMaterial<Value> {
30
+ case defaultHandling
31
+ case cancel
32
+ case use(Value)
33
+ }
34
+
35
+ private final class RuntimeHost {
36
+ let host: String
37
+ private var handle: UnsafeMutableRawPointer?
38
+
39
+ init() throws {
40
+ guard
41
+ let host = Bundle.main.object(forInfoDictionaryKey: "TokamakHost") as? String,
42
+ !host.isEmpty
43
+ else {
44
+ throw ShellError.configuration("TokamakHost is required")
45
+ }
46
+ self.host = host
47
+
48
+ let state = try Self.stateDirectory()
49
+ var error = [CChar](repeating: 0, count: 512)
50
+ if let endpoint = Bundle.main.object(forInfoDictionaryKey: "TokamakDevEndpoint") as? String,
51
+ let sessionToken = Bundle.main.object(forInfoDictionaryKey: "TokamakDevSessionToken")
52
+ as? String
53
+ {
54
+ handle = state.path.withCString { statePath in
55
+ host.withCString { host in
56
+ endpoint.withCString { endpoint in
57
+ sessionToken.withCString { sessionToken in
58
+ error.withUnsafeMutableBufferPointer { error in
59
+ tokamak_runtime_start_development(
60
+ statePath,
61
+ host,
62
+ endpoint,
63
+ sessionToken,
64
+ error.baseAddress,
65
+ error.count
66
+ )
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ } else {
73
+ let bundle = Bundle.main.resourceURL?.appendingPathComponent("app")
74
+ guard let bundle else {
75
+ throw ShellError.configuration("app bundle resources are unavailable")
76
+ }
77
+ handle = bundle.path.withCString { bundlePath in
78
+ state.path.withCString { statePath in
79
+ host.withCString { host in
80
+ error.withUnsafeMutableBufferPointer { error in
81
+ tokamak_runtime_start(
82
+ bundlePath,
83
+ statePath,
84
+ host,
85
+ error.baseAddress,
86
+ error.count
87
+ )
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }
93
+ guard handle != nil else {
94
+ throw ShellError.runtime(String(cString: error))
95
+ }
96
+ try? FileManager.default.removeItem(
97
+ at: state.appendingPathComponent(startupErrorFile)
98
+ )
99
+ }
100
+
101
+ deinit {
102
+ if let handle {
103
+ tokamak_runtime_stop(handle)
104
+ }
105
+ }
106
+
107
+ var port: UInt16 {
108
+ tokamak_runtime_port(handle)
109
+ }
110
+
111
+ func restoreGateway() throws -> UInt16 {
112
+ var error = [CChar](repeating: 0, count: 512)
113
+ let port = error.withUnsafeMutableBufferPointer { error in
114
+ tokamak_runtime_restore_gateway(
115
+ handle,
116
+ error.baseAddress,
117
+ error.count
118
+ )
119
+ }
120
+ guard port != 0 else {
121
+ throw ShellError.runtime(String(cString: error))
122
+ }
123
+ return port
124
+ }
125
+
126
+ func serverAuthority(
127
+ host: String
128
+ ) -> AuthenticationMaterial<Data> {
129
+ var bytes = TokamakBytes(data: nil, len: 0)
130
+ let decision = host.withCString {
131
+ tokamak_runtime_server_authority(handle, $0, &bytes)
132
+ }
133
+ switch decision {
134
+ case Int32(TOKAMAK_DECISION_DEFAULT):
135
+ return .defaultHandling
136
+ case Int32(TOKAMAK_DECISION_USE):
137
+ defer { tokamak_bytes_free(bytes) }
138
+ guard let data = bytes.data else { return .cancel }
139
+ return .use(Data(bytes: data, count: bytes.len))
140
+ default:
141
+ return .cancel
142
+ }
143
+ }
144
+
145
+ func clientIdentity(
146
+ host: String,
147
+ previousFailures: Int
148
+ ) -> AuthenticationMaterial<(Data, Data)> {
149
+ var identity = TokamakIdentity(
150
+ certificate: TokamakBytes(data: nil, len: 0),
151
+ private_key: TokamakBytes(data: nil, len: 0)
152
+ )
153
+ let decision = host.withCString {
154
+ tokamak_runtime_client_identity(
155
+ handle,
156
+ $0,
157
+ max(previousFailures, 0),
158
+ &identity
159
+ )
160
+ }
161
+ switch decision {
162
+ case Int32(TOKAMAK_DECISION_DEFAULT):
163
+ return .defaultHandling
164
+ case Int32(TOKAMAK_DECISION_USE):
165
+ defer { tokamak_identity_free(identity) }
166
+ guard
167
+ let certificate = identity.certificate.data,
168
+ let privateKey = identity.private_key.data
169
+ else {
170
+ return .cancel
171
+ }
172
+ return .use(
173
+ (
174
+ Data(bytes: certificate, count: identity.certificate.len),
175
+ Data(bytes: privateKey, count: identity.private_key.len)
176
+ ))
177
+ default:
178
+ return .cancel
179
+ }
180
+ }
181
+
182
+ static func recordStartupFailure(_ error: Error) {
183
+ print("tokamak runtime startup failed: \(error)")
184
+ guard let state = try? stateDirectory() else { return }
185
+ try? String(describing: error).write(
186
+ to: state.appendingPathComponent(startupErrorFile),
187
+ atomically: true,
188
+ encoding: .utf8
189
+ )
190
+ }
191
+
192
+ private static func stateDirectory() throws -> URL {
193
+ guard let identifier = Bundle.main.bundleIdentifier else {
194
+ throw ShellError.configuration("CFBundleIdentifier is required")
195
+ }
196
+ let root = try FileManager.default.url(
197
+ for: .cachesDirectory,
198
+ in: .userDomainMask,
199
+ appropriateFor: nil,
200
+ create: true
201
+ )
202
+ let state = root.appendingPathComponent(identifier, isDirectory: true)
203
+ try FileManager.default.createDirectory(
204
+ at: state,
205
+ withIntermediateDirectories: true
206
+ )
207
+ return state
208
+ }
209
+ }
210
+
211
+ private final class NavigationDelegate: NSObject, WKNavigationDelegate, WKUIDelegate {
212
+ private let runtime: RuntimeHost
213
+ private let pluginBridge: TokamakPluginBridge
214
+
215
+ init(runtime: RuntimeHost, pluginBridge: TokamakPluginBridge) {
216
+ self.runtime = runtime
217
+ self.pluginBridge = pluginBridge
218
+ }
219
+
220
+ func webView(
221
+ _ webView: WKWebView,
222
+ didStartProvisionalNavigation navigation: WKNavigation?
223
+ ) {
224
+ pluginBridge.close()
225
+ }
226
+
227
+ func webView(
228
+ _ webView: WKWebView,
229
+ decidePolicyFor navigationAction: WKNavigationAction,
230
+ decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
231
+ ) {
232
+ guard let url = navigationAction.request.url else {
233
+ decisionHandler(.cancel)
234
+ return
235
+ }
236
+
237
+ if navigationAction.targetFrame?.isMainFrame == false {
238
+ decisionHandler(.allow)
239
+ return
240
+ }
241
+
242
+ if isAppOrigin(url) {
243
+ if navigationAction.targetFrame == nil {
244
+ webView.load(navigationAction.request)
245
+ decisionHandler(.cancel)
246
+ } else {
247
+ decisionHandler(.allow)
248
+ }
249
+ return
250
+ }
251
+
252
+ openExternal(url)
253
+ decisionHandler(.cancel)
254
+ }
255
+
256
+ func webView(
257
+ _ webView: WKWebView,
258
+ decidePolicyFor navigationResponse: WKNavigationResponse,
259
+ decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void
260
+ ) {
261
+ guard
262
+ navigationResponse.isForMainFrame,
263
+ let url = navigationResponse.response.url
264
+ else {
265
+ decisionHandler(.allow)
266
+ return
267
+ }
268
+
269
+ if isAppOrigin(url) {
270
+ decisionHandler(.allow)
271
+ } else {
272
+ openExternal(url)
273
+ decisionHandler(.cancel)
274
+ }
275
+ }
276
+
277
+ func webView(
278
+ _ webView: WKWebView,
279
+ createWebViewWith configuration: WKWebViewConfiguration,
280
+ for navigationAction: WKNavigationAction,
281
+ windowFeatures: WKWindowFeatures
282
+ ) -> WKWebView? {
283
+ _ = (configuration, windowFeatures)
284
+ guard let url = navigationAction.request.url else { return nil }
285
+ if isAppOrigin(url) {
286
+ webView.load(navigationAction.request)
287
+ } else {
288
+ openExternal(url)
289
+ }
290
+ return nil
291
+ }
292
+
293
+ func webView(
294
+ _ webView: WKWebView,
295
+ didReceive challenge: URLAuthenticationChallenge,
296
+ completionHandler:
297
+ @escaping (
298
+ URLSession.AuthChallengeDisposition,
299
+ URLCredential?
300
+ ) -> Void
301
+ ) {
302
+ let space = challenge.protectionSpace
303
+ switch space.authenticationMethod {
304
+ case NSURLAuthenticationMethodServerTrust:
305
+ answer(
306
+ runtime.serverAuthority(host: space.host),
307
+ trust: space.serverTrust,
308
+ completion: completionHandler
309
+ )
310
+ case NSURLAuthenticationMethodClientCertificate:
311
+ answer(
312
+ runtime.clientIdentity(
313
+ host: space.host,
314
+ previousFailures: challenge.previousFailureCount
315
+ ),
316
+ completion: completionHandler
317
+ )
318
+ default:
319
+ completionHandler(.performDefaultHandling, nil)
320
+ }
321
+ }
322
+
323
+ func webView(
324
+ _ webView: WKWebView,
325
+ didFailProvisionalNavigation navigation: WKNavigation?,
326
+ withError error: Error
327
+ ) {
328
+ print("tokamak WebView navigation failed: \(error)")
329
+ }
330
+
331
+ private func isAppOrigin(_ url: URL) -> Bool {
332
+ guard
333
+ url.scheme?.caseInsensitiveCompare("https") == .orderedSame,
334
+ url.host?.caseInsensitiveCompare(runtime.host) == .orderedSame
335
+ else {
336
+ return false
337
+ }
338
+ return url.port == nil || url.port == 443
339
+ }
340
+
341
+ private func openExternal(_ url: URL) {
342
+ #if os(iOS)
343
+ UIApplication.shared.open(url)
344
+ #else
345
+ NSWorkspace.shared.open(url)
346
+ #endif
347
+ }
348
+
349
+ private func answer(
350
+ _ material: AuthenticationMaterial<Data>,
351
+ trust: SecTrust?,
352
+ completion:
353
+ @escaping (
354
+ URLSession.AuthChallengeDisposition,
355
+ URLCredential?
356
+ ) -> Void
357
+ ) {
358
+ switch material {
359
+ case .defaultHandling:
360
+ completion(.performDefaultHandling, nil)
361
+ case .cancel:
362
+ completion(.cancelAuthenticationChallenge, nil)
363
+ case .use(let authority):
364
+ guard
365
+ let trust,
366
+ let certificate = SecCertificateCreateWithData(
367
+ nil,
368
+ authority as CFData
369
+ ),
370
+ SecTrustSetAnchorCertificates(
371
+ trust,
372
+ [certificate] as CFArray
373
+ ) == errSecSuccess,
374
+ SecTrustSetAnchorCertificatesOnly(trust, true) == errSecSuccess
375
+ else {
376
+ completion(.cancelAuthenticationChallenge, nil)
377
+ return
378
+ }
379
+ let queue = DispatchQueue.global(qos: .userInitiated)
380
+ queue.async {
381
+ SecTrustEvaluateAsyncWithError(trust, queue) { trust, trusted, _ in
382
+ DispatchQueue.main.async {
383
+ guard trusted else {
384
+ completion(.cancelAuthenticationChallenge, nil)
385
+ return
386
+ }
387
+ completion(.useCredential, URLCredential(trust: trust))
388
+ }
389
+ }
390
+ }
391
+ }
392
+ }
393
+
394
+ private func answer(
395
+ _ material: AuthenticationMaterial<(Data, Data)>,
396
+ completion: (
397
+ URLSession.AuthChallengeDisposition,
398
+ URLCredential?
399
+ ) -> Void
400
+ ) {
401
+ switch material {
402
+ case .defaultHandling:
403
+ completion(.performDefaultHandling, nil)
404
+ case .cancel:
405
+ completion(.cancelAuthenticationChallenge, nil)
406
+ case .use(let value):
407
+ guard
408
+ let identity = identity(
409
+ certificate: value.0,
410
+ privateKey: value.1
411
+ )
412
+ else {
413
+ completion(.cancelAuthenticationChallenge, nil)
414
+ return
415
+ }
416
+ completion(
417
+ .useCredential,
418
+ URLCredential(
419
+ identity: identity,
420
+ certificates: nil,
421
+ persistence: .none
422
+ )
423
+ )
424
+ }
425
+ }
426
+
427
+ private func identity(
428
+ certificate: Data,
429
+ privateKey: Data
430
+ ) -> SecIdentity? {
431
+ guard
432
+ let certificate = SecCertificateCreateWithData(
433
+ nil,
434
+ certificate as CFData
435
+ )
436
+ else {
437
+ return nil
438
+ }
439
+ let attributes: [CFString: Any] = [
440
+ kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
441
+ kSecAttrKeyClass: kSecAttrKeyClassPrivate,
442
+ kSecAttrKeySizeInBits: 256,
443
+ ]
444
+ guard
445
+ let key = SecKeyCreateWithData(
446
+ privateKey as CFData,
447
+ attributes as CFDictionary,
448
+ nil
449
+ )
450
+ else {
451
+ return nil
452
+ }
453
+ return SecIdentityCreate(nil, certificate, key)
454
+ }
455
+ }
456
+
457
+ private final class TokamakController {
458
+ private var runtime: RuntimeHost?
459
+ private var navigation: NavigationDelegate?
460
+ private var pluginBridge: TokamakPluginBridge?
461
+ private weak var activeWebView: WKWebView?
462
+ private var dataStore: WKWebsiteDataStore?
463
+ private var proxyPort: UInt16?
464
+
465
+ func start(
466
+ frame: CGRect,
467
+ completion: @escaping (WKWebView) -> Void
468
+ ) {
469
+ DispatchQueue.global(qos: .userInitiated).async {
470
+ let result = Result { try RuntimeHost() }
471
+ DispatchQueue.main.async {
472
+ switch result {
473
+ case .success(let runtime):
474
+ self.runtime = runtime
475
+ completion(self.webView(frame: frame, runtime: runtime))
476
+ case .failure(let error):
477
+ RuntimeHost.recordStartupFailure(error)
478
+ completion(self.failureWebView(frame: frame))
479
+ }
480
+ }
481
+ }
482
+ }
483
+
484
+ func restoreGateway() {
485
+ guard let runtime, let dataStore else { return }
486
+ do {
487
+ let port = try runtime.restoreGateway()
488
+ guard port != proxyPort else { return }
489
+ setProxy(port: port, host: runtime.host, dataStore: dataStore)
490
+ proxyPort = port
491
+ activeWebView?.reload()
492
+ } catch {
493
+ print("tokamak gateway could not recover: \(error)")
494
+ }
495
+ }
496
+
497
+ private func webView(frame: CGRect, runtime: RuntimeHost) -> WKWebView {
498
+ let configuration = WKWebViewConfiguration()
499
+ let dataStore = WKWebsiteDataStore.default()
500
+ let port = runtime.port
501
+ setProxy(port: port, host: runtime.host, dataStore: dataStore)
502
+ self.dataStore = dataStore
503
+ self.proxyPort = port
504
+ configuration.websiteDataStore = dataStore
505
+
506
+ let pluginBridge = TokamakPluginBridge(
507
+ host: runtime.host,
508
+ plugins: tokamakPlugins()
509
+ )
510
+ pluginBridge.install(in: configuration.userContentController)
511
+ let navigation = NavigationDelegate(
512
+ runtime: runtime,
513
+ pluginBridge: pluginBridge
514
+ )
515
+ self.navigation = navigation
516
+ let webView = WKWebView(frame: frame, configuration: configuration)
517
+ #if os(iOS)
518
+ webView.scrollView.bounces = false
519
+ #endif
520
+ webView.allowsLinkPreview = false
521
+ activeWebView = webView
522
+ pluginBridge.webView = webView
523
+ self.pluginBridge = pluginBridge
524
+ webView.navigationDelegate = navigation
525
+ webView.uiDelegate = navigation
526
+ webView.load(URLRequest(url: URL(string: "https://\(runtime.host)/")!))
527
+ return webView
528
+ }
529
+
530
+ private func setProxy(
531
+ port: UInt16,
532
+ host: String,
533
+ dataStore: WKWebsiteDataStore
534
+ ) {
535
+ var proxy = ProxyConfiguration(
536
+ httpCONNECTProxy: .hostPort(
537
+ host: "127.0.0.1",
538
+ port: NWEndpoint.Port(rawValue: port)!
539
+ )
540
+ )
541
+ proxy.matchDomains = [host]
542
+ proxy.allowFailover = false
543
+ dataStore.proxyConfigurations = [proxy]
544
+ }
545
+
546
+ private func failureWebView(frame: CGRect) -> WKWebView {
547
+ let webView = WKWebView(frame: frame)
548
+ webView.loadHTMLString(failurePage, baseURL: nil)
549
+ return webView
550
+ }
551
+ }
552
+
553
+ #if os(macOS)
554
+ private final class TokamakMacApplicationDelegate: NSObject, NSApplicationDelegate {
555
+ private let controller = TokamakController()
556
+ private var window: NSWindow?
557
+
558
+ func applicationDidFinishLaunching(_ notification: Notification) {
559
+ let window = NSWindow(
560
+ contentRect: NSRect(x: 0, y: 0, width: 1024, height: 768),
561
+ styleMask: [
562
+ .titled,
563
+ .closable,
564
+ .miniaturizable,
565
+ .resizable,
566
+ ],
567
+ backing: .buffered,
568
+ defer: false
569
+ )
570
+ window.title =
571
+ Bundle.main.object(
572
+ forInfoDictionaryKey: "CFBundleName"
573
+ ) as? String ?? "tokamak"
574
+ window.center()
575
+ window.makeKeyAndOrderFront(nil)
576
+ NSApplication.shared.activate(ignoringOtherApps: true)
577
+ self.window = window
578
+
579
+ guard let content = window.contentView else { return }
580
+ controller.start(frame: content.bounds) { webView in
581
+ webView.autoresizingMask = [.width, .height]
582
+ content.addSubview(webView)
583
+ }
584
+ }
585
+
586
+ func applicationShouldTerminateAfterLastWindowClosed(
587
+ _ sender: NSApplication
588
+ ) -> Bool {
589
+ true
590
+ }
591
+ }
592
+
593
+ @main
594
+ private enum TokamakApplication {
595
+ static func main() {
596
+ let application = NSApplication.shared
597
+ let delegate = TokamakMacApplicationDelegate()
598
+ application.setActivationPolicy(.regular)
599
+ application.delegate = delegate
600
+ application.run()
601
+ }
602
+ }
603
+ #else
604
+ private final class TokamakIOSApplicationDelegate: UIResponder, UIApplicationDelegate {
605
+ private let controller = TokamakController()
606
+ var window: UIWindow?
607
+
608
+ func application(
609
+ _ application: UIApplication,
610
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
611
+ ) -> Bool {
612
+ let window = UIWindow(frame: UIScreen.main.bounds)
613
+ let viewController = UIViewController()
614
+ window.rootViewController = viewController
615
+ window.makeKeyAndVisible()
616
+ self.window = window
617
+
618
+ controller.start(frame: viewController.view.bounds) { webView in
619
+ webView.autoresizingMask = [
620
+ .flexibleWidth,
621
+ .flexibleHeight,
622
+ ]
623
+ viewController.view.addSubview(webView)
624
+ }
625
+ return true
626
+ }
627
+
628
+ func applicationWillEnterForeground(_ application: UIApplication) {
629
+ controller.restoreGateway()
630
+ }
631
+
632
+ }
633
+
634
+ @main
635
+ private enum TokamakApplication {
636
+ static func main() {
637
+ UIApplicationMain(
638
+ CommandLine.argc,
639
+ CommandLine.unsafeArgv,
640
+ nil,
641
+ NSStringFromClass(TokamakIOSApplicationDelegate.self)
642
+ )
643
+ }
644
+ }
645
+ #endif
@@ -0,0 +1,21 @@
1
+ {
2
+ "tokamakVersion": "0.1.1",
3
+ "target": "ios-simulator-arm64",
4
+ "artifacts": [
5
+ {
6
+ "kind": "runtimeLibrary",
7
+ "path": "frameworks/TokamakRuntime.framework"
8
+ },
9
+ {
10
+ "kind": "nativeShellDirectory",
11
+ "path": "native-shell"
12
+ },
13
+ {
14
+ "kind": "esbuildExecutable",
15
+ "path": "tools/runtime/node_modules/esbuild/bin/esbuild"
16
+ }
17
+ ],
18
+ "requiredTools": [
19
+ "xcrun"
20
+ ]
21
+ }
@@ -0,0 +1,3 @@
1
+ # esbuild
2
+
3
+ This is the macOS ARM 64-bit binary for esbuild, a JavaScript bundler and minifier. See https://github.com/evanw/esbuild for details.
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@esbuild/darwin-arm64",
3
+ "version": "0.28.1",
4
+ "description": "The macOS ARM 64-bit binary for esbuild, a JavaScript bundler.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/evanw/esbuild.git"
8
+ },
9
+ "license": "MIT",
10
+ "preferUnplugged": true,
11
+ "engines": {
12
+ "node": ">=18"
13
+ },
14
+ "os": [
15
+ "darwin"
16
+ ],
17
+ "cpu": [
18
+ "arm64"
19
+ ]
20
+ }
@@ -0,0 +1,3 @@
1
+ # esbuild
2
+
3
+ This is the macOS 64-bit binary for esbuild, a JavaScript bundler and minifier. See https://github.com/evanw/esbuild for details.
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@esbuild/darwin-x64",
3
+ "version": "0.28.1",
4
+ "description": "The macOS 64-bit binary for esbuild, a JavaScript bundler.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/evanw/esbuild.git"
8
+ },
9
+ "license": "MIT",
10
+ "preferUnplugged": true,
11
+ "engines": {
12
+ "node": ">=18"
13
+ },
14
+ "os": [
15
+ "darwin"
16
+ ],
17
+ "cpu": [
18
+ "x64"
19
+ ]
20
+ }
@@ -0,0 +1,3 @@
1
+ # esbuild
2
+
3
+ This is the Linux 64-bit binary for esbuild, a JavaScript bundler and minifier. See https://github.com/evanw/esbuild for details.
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@esbuild/linux-x64",
3
+ "version": "0.28.1",
4
+ "description": "The Linux 64-bit binary for esbuild, a JavaScript bundler.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/evanw/esbuild.git"
8
+ },
9
+ "license": "MIT",
10
+ "preferUnplugged": true,
11
+ "engines": {
12
+ "node": ">=18"
13
+ },
14
+ "os": [
15
+ "linux"
16
+ ],
17
+ "cpu": [
18
+ "x64"
19
+ ]
20
+ }
@@ -0,0 +1,3 @@
1
+ # esbuild
2
+
3
+ This is the Windows 64-bit binary for esbuild, a JavaScript bundler and minifier. See https://github.com/evanw/esbuild for details.
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@esbuild/win32-x64",
3
+ "version": "0.28.1",
4
+ "description": "The Windows 64-bit binary for esbuild, a JavaScript bundler.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/evanw/esbuild.git"
8
+ },
9
+ "license": "MIT",
10
+ "preferUnplugged": true,
11
+ "engines": {
12
+ "node": ">=18"
13
+ },
14
+ "os": [
15
+ "win32"
16
+ ],
17
+ "cpu": [
18
+ "x64"
19
+ ]
20
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Evan Wallace
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,24 @@
1
+ const { spawnSync } = require("node:child_process");
2
+ const path = require("node:path");
3
+
4
+ function esbuildPath(platform, arch) {
5
+ const hosts = ["darwin-arm64", "darwin-x64", "linux-x64", "win32-x64"];
6
+ const host = `${platform}-${arch}`;
7
+ if (!hosts.includes(host)) {
8
+ throw new Error(`tokamak does not provide esbuild for ${host}`);
9
+ }
10
+ const executable = platform === "win32" ? "esbuild.exe" : "bin/esbuild";
11
+ return path.resolve(__dirname, "../..", "@esbuild", host, executable);
12
+ }
13
+
14
+ if (require.main === module) {
15
+ const result = spawnSync(esbuildPath(process.platform, process.arch), process.argv.slice(2), {
16
+ stdio: "inherit",
17
+ });
18
+ if (result.error) {
19
+ throw result.error;
20
+ }
21
+ process.exit(result.status ?? 1);
22
+ }
23
+
24
+ module.exports = { esbuildPath };
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@tokamakdev/platform-ios-simulator-arm64",
3
+ "version": "0.1.1-beta.38",
4
+ "description": "The tokamak platform pack for ios-simulator-arm64",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/mantty/tokamak.git",
8
+ "directory": "tokamak-cli/npm/platform-packs/ios-simulator-arm64"
9
+ },
10
+ "os": [
11
+ "darwin"
12
+ ],
13
+ "cpu": [
14
+ "arm64"
15
+ ],
16
+ "preferUnplugged": true,
17
+ "files": [
18
+ "ios-simulator-arm64"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ }
23
+ }