craft-native 0.0.369 → 0.0.375

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.
@@ -222,7 +222,7 @@ internal class CraftNativeCapabilities(
222
222
  "pickVideo" -> pickVideo(requestToken, args, completion)
223
223
  "pickFile" -> pickFile(requestToken, args, completion)
224
224
  "download", "write", "remove" -> networkExecutor.execute {
225
- complete(requestToken, files(requestToken, method, args), completion)
225
+ completeFile(requestToken, files(requestToken, method, args), completion)
226
226
  }
227
227
  else -> complete(requestToken, failure("UNKNOWN_ACTION", "Unsupported Files method"), completion)
228
228
  }
@@ -492,6 +492,7 @@ internal class CraftNativeCapabilities(
492
492
 
493
493
  private fun pickVideo(requestToken: String, args: JSONArray, completion: (CraftNativeCapabilityResult) -> Unit) {
494
494
  mainHandler.post {
495
+ if (fileRequestCancelled(requestToken)) return@post
495
496
  val host = activity ?: return@post complete(requestToken, failure("NEEDS_ACTIVITY", "No screen is showing to pick from"), completion)
496
497
  if (pendingFilePick != null) {
497
498
  complete(requestToken, failure("BUSY", "Another file picker is already open"), completion)
@@ -518,7 +519,7 @@ internal class CraftNativeCapabilities(
518
519
  private fun copyVideo(requestToken: String, uri: Uri, limit: Long): CraftNativeCapabilityResult {
519
520
  var target: File? = null
520
521
  return try {
521
- if (cancelled.contains(requestToken)) throw IllegalStateException("CANCELLED: Video selection was cancelled")
522
+ if (fileRequestCancelled(requestToken)) throw IllegalStateException("CANCELLED: Video selection was cancelled")
522
523
  val name = context.contentResolver.query(uri, arrayOf(android.provider.OpenableColumns.DISPLAY_NAME), null, null, null)?.use {
523
524
  if (it.moveToFirst()) it.getString(0) else null
524
525
  } ?: "video.mp4"
@@ -535,7 +536,7 @@ internal class CraftNativeCapabilities(
535
536
  while (true) {
536
537
  val count = source.read(buffer)
537
538
  if (count < 0) break
538
- if (cancelled.contains(requestToken)) throw IllegalStateException("CANCELLED: Video selection was cancelled")
539
+ if (fileRequestCancelled(requestToken)) throw IllegalStateException("CANCELLED: Video selection was cancelled")
539
540
  size += count
540
541
  require(size <= limit) { "Choose a video under ${limit / 1024 / 1024}MB." }
541
542
  output.write(buffer, 0, count)
@@ -558,6 +559,7 @@ internal class CraftNativeCapabilities(
558
559
  completion: (CraftNativeCapabilityResult) -> Unit,
559
560
  ) {
560
561
  mainHandler.post {
562
+ if (fileRequestCancelled(requestToken)) return@post
561
563
  val host = activity ?: return@post complete(requestToken, failure("NEEDS_ACTIVITY", "No screen is showing to pick from"), completion)
562
564
  if (pendingFilePick != null) {
563
565
  complete(requestToken, failure("BUSY", "Another file picker is already open"), completion)
@@ -594,6 +596,7 @@ internal class CraftNativeCapabilities(
594
596
 
595
597
  private fun pickFile(requestToken: String, args: JSONArray, completion: (CraftNativeCapabilityResult) -> Unit) {
596
598
  mainHandler.post {
599
+ if (fileRequestCancelled(requestToken)) return@post
597
600
  val host = activity ?: return@post complete(requestToken, failure("NEEDS_ACTIVITY", "No screen is showing to pick from"), completion)
598
601
  if (pendingFilePick != null) {
599
602
  complete(requestToken, failure("BUSY", "Another file picker is already open"), completion)
@@ -674,7 +677,7 @@ internal class CraftNativeCapabilities(
674
677
  networkExecutor.execute {
675
678
  val result = data?.data?.let { copyVideo(pending.requestToken, it, pending.videoLimit) }
676
679
  ?: failure("PICKER_FAILED", "The selected video could not be read")
677
- complete(pending.requestToken, result, pending.completion)
680
+ completeFile(pending.requestToken, result, pending.completion)
678
681
  }
679
682
  return true
680
683
  }
@@ -687,30 +690,28 @@ internal class CraftNativeCapabilities(
687
690
  data?.data?.let { add(it) }
688
691
  }.distinct().take(pending.selectionLimit)
689
692
  val result = copyDocuments(pending.requestToken, uris, pending.fileLimit)
690
- complete(pending.requestToken, result, pending.completion)
693
+ completeFile(pending.requestToken, result, pending.completion)
691
694
  }
692
695
  return true
693
696
  }
694
697
  networkExecutor.execute {
695
- val descriptor = if (pending.source == "camera") {
696
- val bitmap = data?.extras?.get("data") as? Bitmap
697
- bitmap?.let { writeImage(it, pending.maxPixels, pending.quality, "photo.jpg", pending.requestToken) }
698
- } else {
699
- val uri = data?.data
700
- uri?.let { copyUri(pending.requestToken, it, pending.maxPixels, pending.quality) }
701
- }
702
- complete(
703
- pending.requestToken,
704
- descriptor?.let(::success) ?: if (cancelled.contains(pending.requestToken)) failure("CANCELLED", "Image selection was cancelled")
705
- else failure("PICKER_FAILED", "The selected image could not be read"),
706
- pending.completion,
707
- )
698
+ val result = runCatching {
699
+ val descriptor = if (pending.source == "camera") {
700
+ val bitmap = data?.extras?.get("data") as? Bitmap
701
+ bitmap?.let { writeImage(it, pending.maxPixels, pending.quality, "photo.jpg", pending.requestToken) }
702
+ } else {
703
+ data?.data?.let { copyUri(pending.requestToken, it, pending.maxPixels, pending.quality) }
704
+ }
705
+ descriptor?.let(::success) ?: if (fileRequestCancelled(pending.requestToken)) failure("CANCELLED", "Image selection was cancelled")
706
+ else failure("PICKER_FAILED", "The selected image could not be read")
707
+ }.getOrElse { failure("READ_ERROR", it.message ?: "The selected image could not be read") }
708
+ completeFile(pending.requestToken, result, pending.completion)
708
709
  }
709
710
  return true
710
711
  }
711
712
 
712
713
  private fun copyUri(requestToken: String, uri: Uri, maxPixels: Int, quality: Float): JSONObject? {
713
- if (cancelled.contains(requestToken)) return null
714
+ if (fileRequestCancelled(requestToken)) return null
714
715
  val name = context.contentResolver.query(uri, arrayOf(android.provider.OpenableColumns.DISPLAY_NAME), null, null, null)?.use {
715
716
  if (it.moveToFirst()) it.getString(0) else null
716
717
  } ?: "photo"
@@ -729,7 +730,7 @@ internal class CraftNativeCapabilities(
729
730
  val copied = JSONArray()
730
731
  return try {
731
732
  uris.forEach { copied.put(copyDocument(requestToken, it, limit)) }
732
- if (cancelled.contains(requestToken)) throw IllegalStateException("CANCELLED: File selection was cancelled")
733
+ if (fileRequestCancelled(requestToken)) throw IllegalStateException("CANCELLED: File selection was cancelled")
733
734
  success(copied)
734
735
  } catch (error: Exception) {
735
736
  for (index in 0 until copied.length()) {
@@ -745,6 +746,7 @@ internal class CraftNativeCapabilities(
745
746
  private fun copyDocument(requestToken: String, uri: Uri, limit: Long): JSONObject {
746
747
  var target: File? = null
747
748
  return try {
749
+ if (fileRequestCancelled(requestToken)) throw IllegalStateException("CANCELLED: File selection was cancelled")
748
750
  val name = context.contentResolver.query(uri, arrayOf(android.provider.OpenableColumns.DISPLAY_NAME), null, null, null)?.use {
749
751
  if (it.moveToFirst()) it.getString(0) else null
750
752
  } ?: "file"
@@ -759,7 +761,7 @@ internal class CraftNativeCapabilities(
759
761
  while (true) {
760
762
  val count = source.read(buffer)
761
763
  if (count < 0) break
762
- if (cancelled.contains(requestToken)) throw IllegalStateException("CANCELLED: File selection was cancelled")
764
+ if (fileRequestCancelled(requestToken)) throw IllegalStateException("CANCELLED: File selection was cancelled")
763
765
  size += count
764
766
  require(size <= limit) { "FILE_TOO_LARGE: Choose a file under ${limit / 1024 / 1024}MB." }
765
767
  output.write(buffer, 0, count)
@@ -785,7 +787,7 @@ internal class CraftNativeCapabilities(
785
787
  }
786
788
 
787
789
  private fun writeImage(bitmap: Bitmap, maxPixels: Int, quality: Float, name: String, requestToken: String? = null): JSONObject? {
788
- if (requestToken != null && cancelled.contains(requestToken)) {
790
+ if (requestToken != null && fileRequestCancelled(requestToken)) {
789
791
  bitmap.recycle()
790
792
  return null
791
793
  }
@@ -809,7 +811,7 @@ internal class CraftNativeCapabilities(
809
811
  file.parentFile?.deleteRecursively()
810
812
  return null
811
813
  }
812
- if (requestToken != null && cancelled.contains(requestToken)) {
814
+ if (requestToken != null && fileRequestCancelled(requestToken)) {
813
815
  file.parentFile?.deleteRecursively()
814
816
  return null
815
817
  }
@@ -819,7 +821,7 @@ internal class CraftNativeCapabilities(
819
821
  private fun store(bytes: ByteArray, name: String, type: String): CraftNativeCapabilityResult {
820
822
  val file = File(fileRoot, UUID.randomUUID().toString()).apply { mkdirs() }.resolve(safeFileName(name))
821
823
  return runCatching { file.writeBytes(bytes); success(descriptor(file, type)) }
822
- .getOrElse { failure("WRITE_ERROR", it.message ?: "File could not be written") }
824
+ .getOrElse { file.parentFile?.deleteRecursively(); failure("WRITE_ERROR", it.message ?: "File could not be written") }
823
825
  }
824
826
 
825
827
  private fun descriptor(file: File, type: String): JSONObject = JSONObject()
@@ -860,16 +862,36 @@ internal class CraftNativeCapabilities(
860
862
  private fun headerValue(value: String): String = value.replace("\r", "").replace("\n", "")
861
863
  private fun validURL(raw: String): URL? = runCatching { URL(raw) }.getOrNull()?.takeIf { it.protocol.lowercase() in setOf("http", "https") && it.host.isNotBlank() }
862
864
 
865
+ private fun fileRequestCancelled(requestToken: String): Boolean =
866
+ closed.get() || cancelled.contains(requestToken) || Thread.currentThread().isInterrupted
867
+
868
+ private fun discardFiles(result: CraftNativeCapabilityResult) {
869
+ if (result.code != null) return
870
+ val data = result.data
871
+ val files = if (data is JSONArray) (0 until data.length()).mapNotNull { data.optJSONObject(it) }
872
+ else listOfNotNull(data as? JSONObject)
873
+ files.forEach { file ->
874
+ ownedFile(file.optString("path"))?.parentFile?.deleteRecursively()
875
+ }
876
+ }
877
+
878
+ private fun completeFile(requestToken: String, result: CraftNativeCapabilityResult, completion: (CraftNativeCapabilityResult) -> Unit) {
879
+ complete(requestToken, result, completion, discard = { discardFiles(result) })
880
+ }
881
+
863
882
  private fun complete(
864
883
  requestToken: String,
865
884
  result: CraftNativeCapabilityResult,
866
885
  completion: (CraftNativeCapabilityResult) -> Unit,
886
+ discard: (() -> Unit)? = null,
867
887
  ) {
868
- if (android.os.Looper.myLooper() == android.os.Looper.getMainLooper()) {
869
- if (!cancelled.remove(requestToken) && !closed.get()) completion(result)
870
- } else mainHandler.post {
871
- if (!cancelled.remove(requestToken) && !closed.get()) completion(result)
888
+ val deliver: () -> Unit = {
889
+ if (cancelled.remove(requestToken) || closed.get()) discard?.invoke()
890
+ else completion(result)
872
891
  }
892
+ if (android.os.Looper.myLooper() == android.os.Looper.getMainLooper()) {
893
+ deliver()
894
+ } else mainHandler.post { deliver() }
873
895
  }
874
896
 
875
897
  private fun storage(method: String, args: JSONArray): CraftNativeCapabilityResult = synchronized(CraftNativeStorage.lock) {
@@ -20,7 +20,10 @@ import kotlin.math.min
20
20
  * invalidates the previous load. The player is released on every terminal
21
21
  * path, including cancellation, decode failure and screen teardown.
22
22
  */
23
- internal class CraftNativeSounds(private val context: Context) {
23
+ internal class CraftNativeSounds(
24
+ private val context: Context,
25
+ private val requestFocus: (AudioManager, AudioFocusRequest) -> Int = { manager, request -> manager.requestAudioFocus(request) },
26
+ ) {
24
27
  private data class Sound(
25
28
  val player: MediaPlayer,
26
29
  var volume: Float,
@@ -33,6 +36,8 @@ internal class CraftNativeSounds(private val context: Context) {
33
36
 
34
37
  private val main = Handler(Looper.getMainLooper())
35
38
  private val sounds = mutableMapOf<String, Sound>()
39
+ private var focusSuspended = false
40
+ private var ducked = false
36
41
  private val audioManager by lazy { context.getSystemService(Context.AUDIO_SERVICE) as AudioManager }
37
42
  private val focusRequest by lazy {
38
43
  AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
@@ -74,12 +79,13 @@ internal class CraftNativeSounds(private val context: Context) {
74
79
  emit?.invoke(JSONObject().put("id", id).put("event", "error").put("message", message))
75
80
  }
76
81
 
77
- if (audioManager.requestAudioFocus(focusRequest) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
82
+ if (requestFocus(audioManager, focusRequest) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
78
83
  settle(failure("AUDIO_FOCUS_DENIED", "Audio focus was denied by the operating system"))
79
84
  remove(id)
80
85
  emit?.invoke(JSONObject().put("id", id).put("event", "error").put("message", "Audio focus was denied by the operating system"))
81
86
  return
82
87
  }
88
+ onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN)
83
89
 
84
90
  runCatching {
85
91
  player.setAudioAttributes(AudioAttributes.Builder()
@@ -87,7 +93,7 @@ internal class CraftNativeSounds(private val context: Context) {
87
93
  .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
88
94
  .build())
89
95
  player.isLooping = options.optBoolean("loop", false)
90
- player.setVolume(if (fadeIn > 0) 0f else volume, if (fadeIn > 0) 0f else volume)
96
+ applyVolume(sound)
91
97
  player.setOnPreparedListener {
92
98
  if (sounds[id] !== sound) return@setOnPreparedListener
93
99
  sound.prepared = true
@@ -95,7 +101,7 @@ internal class CraftNativeSounds(private val context: Context) {
95
101
  settle(CraftNativeCapabilityResult(data = true))
96
102
  if (fadeIn > 0) {
97
103
  if (!sound.paused) ramp(sound, volume, fadeIn / 1000.0)
98
- else { sound.volume = volume; player.setVolume(volume, volume) }
104
+ else { sound.volume = volume; applyVolume(sound) }
99
105
  }
100
106
  }
101
107
  player.setOnCompletionListener {
@@ -136,6 +142,7 @@ internal class CraftNativeSounds(private val context: Context) {
136
142
 
137
143
  fun resume(id: String): Boolean {
138
144
  val sound = sounds[id] ?: return false
145
+ if (focusSuspended) return false
139
146
  if (sound.prepared) runCatching { sound.player.start() }.onFailure { return false }
140
147
  sound.paused = false
141
148
  sound.interrupted = false
@@ -151,7 +158,7 @@ internal class CraftNativeSounds(private val context: Context) {
151
158
  if (fade > 0 && !sound.paused) ramp(sound, target, fade / 1000.0)
152
159
  else {
153
160
  sound.volume = target
154
- sound.player.setVolume(target, target)
161
+ applyVolume(sound)
155
162
  }
156
163
  return true
157
164
  }
@@ -195,7 +202,11 @@ internal class CraftNativeSounds(private val context: Context) {
195
202
  sound.player.stop()
196
203
  }
197
204
  sound.player.release()
198
- if (sounds.isEmpty()) audioManager.abandonAudioFocusRequest(focusRequest)
205
+ if (sounds.isEmpty()) {
206
+ audioManager.abandonAudioFocusRequest(focusRequest)
207
+ focusSuspended = false
208
+ ducked = false
209
+ }
199
210
  val answer = sound.pending
200
211
  sound.pending = null
201
212
  answer?.invoke(failure("CANCELLED", "Audio playback was stopped or replaced"))
@@ -211,21 +222,28 @@ internal class CraftNativeSounds(private val context: Context) {
211
222
  }
212
223
  }
213
224
  }
214
- AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> sounds.values.toList().forEach { sound ->
215
- if (!sound.paused) {
216
- sound.interrupted = true
217
- if (sound.prepared) runCatching { sound.player.pause() }
225
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
226
+ focusSuspended = true
227
+ sounds.values.toList().forEach { sound ->
228
+ if (!sound.paused) {
229
+ sound.interrupted = true
230
+ if (sound.prepared) runCatching { sound.player.pause() }
231
+ }
218
232
  }
219
233
  }
220
- AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> sounds.values.toList().forEach { sound ->
221
- runCatching { sound.player.setVolume(sound.volume * 0.2f, sound.volume * 0.2f) }
234
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
235
+ ducked = true
236
+ sounds.values.toList().forEach { sound -> runCatching { applyVolume(sound) } }
222
237
  }
223
- AudioManager.AUDIOFOCUS_GAIN -> sounds.values.toList().forEach { sound ->
224
- if (sound.interrupted && !sound.paused) {
225
- sound.interrupted = false
226
- if (sound.prepared) runCatching { sound.player.start() }
227
- } else {
228
- runCatching { sound.player.setVolume(sound.volume, sound.volume) }
238
+ AudioManager.AUDIOFOCUS_GAIN -> {
239
+ focusSuspended = false
240
+ ducked = false
241
+ sounds.values.toList().forEach { sound ->
242
+ runCatching { applyVolume(sound) }
243
+ if (sound.interrupted && !sound.paused) {
244
+ sound.interrupted = false
245
+ if (sound.prepared) runCatching { sound.player.start() }
246
+ }
229
247
  }
230
248
  }
231
249
  }
@@ -241,8 +259,8 @@ internal class CraftNativeSounds(private val context: Context) {
241
259
  if (!sounds.containsValue(sound)) return
242
260
  val progress = min(1.0, (android.os.SystemClock.uptimeMillis() - began).toDouble() / duration).toFloat()
243
261
  val value = start + (target - start) * progress
244
- sound.player.setVolume(value, value)
245
262
  sound.volume = value
263
+ applyVolume(sound)
246
264
  if (progress >= 1f) {
247
265
  sound.fade = null
248
266
  done?.invoke()
@@ -256,5 +274,12 @@ internal class CraftNativeSounds(private val context: Context) {
256
274
  main.post(tick)
257
275
  }
258
276
 
277
+ private fun outputVolume(sound: Sound): Float = sound.volume * if (ducked) 0.2f else 1f
278
+
279
+ private fun applyVolume(sound: Sound) {
280
+ val value = outputVolume(sound)
281
+ sound.player.setVolume(value, value)
282
+ }
283
+
259
284
  private fun failure(code: String, message: String) = CraftNativeCapabilityResult(code = code, message = message)
260
285
  }
@@ -1774,7 +1774,7 @@ export interface HealthLiveWorkoutEvent {
1774
1774
  message?: string;
1775
1775
  }
1776
1776
  /** The health reads and writes a host may or may not have, depending on the app build it runs in. */
1777
- export type HealthReadMethod = 'getWorkouts' | 'getDailyStatistics' | 'getHeartRateSamples' | 'getSamples' | 'getSleepIntervals' | 'saveData' | 'saveMindfulSession' | 'getMindfulSessions' | 'startHeartRateUpdates' | 'stopHeartRateUpdates' | 'startWorkout' | 'pauseWorkout' | 'resumeWorkout' | 'endWorkout';
1777
+ export type HealthReadMethod = 'getWorkouts' | 'getDailyStatistics' | 'getHeartRateSamples' | 'getSamples' | 'getSleepIntervals' | 'saveData' | 'saveMindfulSession' | 'getMindfulSessions' | 'startHeartRateUpdates' | 'stopHeartRateUpdates' | 'startWorkout' | 'pauseWorkout' | 'resumeWorkout' | 'endWorkout' | 'discardWorkout';
1778
1778
  export declare const health: {
1779
1779
  /**
1780
1780
  * Whether this app build answers `method`. An app installed before a read
@@ -1834,6 +1834,8 @@ export declare const health: {
1834
1834
  resumeWorkout(): Promise<boolean>;
1835
1835
  /** Ends and saves the current live workout. */
1836
1836
  endWorkout(): Promise<HealthLiveWorkoutResult>;
1837
+ /** iOS: ends the live session without saving a workout. Does not delete existing health samples. */
1838
+ discardWorkout(): Promise<boolean>;
1837
1839
  /** Receives OS workout state transitions; returns the unsubscribe. */
1838
1840
  onWorkout(callback: (event: HealthLiveWorkoutEvent) => void): () => void;
1839
1841
  /**
package/dist/cli.js CHANGED
@@ -3047,7 +3047,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
3047
3047
  `);
3048
3048
  }
3049
3049
  // package.json
3050
- var version = "0.0.369";
3050
+ var version = "0.0.375";
3051
3051
 
3052
3052
  // bin/cli.ts
3053
3053
  var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
@@ -790,6 +790,8 @@ export interface SwitchProps extends BaseProps {
790
790
  };
791
791
  thumbColor?: string;
792
792
  ios_backgroundColor?: string;
793
+ /** iOS-only solid UIControl switch: opaque thumb and immediate transitions. Defaults to native UISwitch. */
794
+ ios_solid?: boolean;
793
795
  }
794
796
  export interface CheckboxProps extends BaseProps {
795
797
  style?: StyleValue<ViewStyle>;
package/dist/index.cjs CHANGED
@@ -375,7 +375,7 @@ function craftBinaryNotFoundMessage(triedPath) {
375
375
  `);
376
376
  }
377
377
  // package.json
378
- var version = "0.0.369";
378
+ var version = "0.0.375";
379
379
 
380
380
  // src/sdk-version.ts
381
381
  var SDK_VERSION = version;
package/dist/index.js CHANGED
@@ -117,7 +117,7 @@ function craftBinaryNotFoundMessage(triedPath) {
117
117
  `);
118
118
  }
119
119
  // package.json
120
- var version = "0.0.369";
120
+ var version = "0.0.375";
121
121
 
122
122
  // src/sdk-version.ts
123
123
  var SDK_VERSION = version;
@@ -408,6 +408,8 @@ export interface InitOptions {
408
408
  * monorepo dev loop uses.
409
409
  */
410
410
  runtimeDir?: string | null;
411
+ /** Explicit Craft Zig source package for a per-app runtime build (or CRAFT_IOS_RUNTIME_SOURCE). */
412
+ runtimeSource?: string | null;
411
413
  }
412
414
  export interface BuildOptions {
413
415
  htmlPath?: string;
@@ -422,6 +424,8 @@ export interface BuildOptions {
422
424
  * leaves whatever `init` installed exactly as it is.
423
425
  */
424
426
  runtimeDir?: string | null;
427
+ /** Rebuild the installed runtime from this source using the app's current feature flags. */
428
+ runtimeSource?: string | null;
425
429
  }
426
430
  /**
427
431
  * Run the app's declared generator hook without invoking a shell.
@@ -498,7 +502,7 @@ export declare function renderRuntimeSettings(): string;
498
502
  * behind the `CRAFT_SCHEDULED_WORKOUTS` condition, so an app that does not
499
503
  * schedule workouts does not link it. `$(inherited)` keeps Debug's `DEBUG`.
500
504
  */
501
- export declare function renderFeatureSettings(config: Pick<CraftConfig, 'enableScheduledWorkouts'>): string;
505
+ export declare function renderFeatureSettings(config: Partial<CraftConfig>): string;
502
506
  export declare function renderPrivacyManifest(config: CraftConfig): string;
503
507
  /**
504
508
  * Whether a PNG carries an alpha channel, from its header: colour type 4
@@ -563,7 +567,7 @@ export declare function resolveRuntimeDir(override?: string | null): string | nu
563
567
  *
564
568
  * Returns true when a runtime was installed.
565
569
  */
566
- export declare function installRuntime(output: string, runtimeDir: string): Promise<boolean>;
570
+ export declare function installRuntime(output: string, runtimeDir: string, config?: CraftConfig): Promise<boolean>;
567
571
  /**
568
572
  * The identifier an app's display name becomes wherever Xcode or Swift needs
569
573
  * one: the project, the target, the product and the `@main` type.