craft-native 0.0.304 → 0.0.328

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 (33) hide show
  1. package/dist/android/src/index.d.ts +3 -5
  2. package/dist/android/src/index.js +50 -7
  3. package/dist/android/templates/CraftBridge.kt.template +0 -90
  4. package/dist/android/templates/CraftNative.kt.template +3 -0
  5. package/dist/android/templates/CraftNativeBackground.kt.template +8 -2
  6. package/dist/android/templates/CraftNativeServices.kt.template +100 -1
  7. package/dist/android/templates/CraftNativeShell.kt.template +26 -8
  8. package/dist/android/templates/CraftPermissionPolicy.kt.template +96 -0
  9. package/dist/android/templates/MainActivityNative.kt.template +120 -6
  10. package/dist/android/templates/build.gradle.kts.app.template +1 -5
  11. package/dist/api/mobile.d.ts +9 -0
  12. package/dist/cli.js +63 -20
  13. package/dist/index.cjs +67 -20
  14. package/dist/index.d.cts +2 -2
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.js +67 -20
  17. package/dist/ios/src/index.d.ts +2 -5
  18. package/dist/ios/src/index.js +26 -1
  19. package/dist/ios/src/testing.d.ts +6 -0
  20. package/dist/ios/templates/CraftActivityAttributes.swift +29 -2
  21. package/dist/ios/templates/CraftApp.swift +45 -7
  22. package/dist/ios/templates/CraftHealth.swift +14 -0
  23. package/dist/ios/templates/CraftHybrid.swift +6 -0
  24. package/dist/ios/templates/CraftLiveActivities.swift +106 -2
  25. package/dist/ios/templates/CraftLiveActivityWidget.swift.template +1 -1
  26. package/dist/ios/templates/CraftNativeActions.swift +10 -1
  27. package/dist/ios/templates/CraftNativeScreen.swift +170 -9
  28. package/dist/ios/templates/CraftVoiceRecording.swift +75 -0
  29. package/dist/ios/templates/project.yml.template +6 -0
  30. package/dist/ios/templates/testing/DriverTests.swift.template +38 -0
  31. package/dist/package.d.ts +15 -4
  32. package/dist/types.d.ts +11 -4
  33. package/package.json +1 -1
@@ -864,6 +864,7 @@ internal class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
864
864
  super.onWindowVisibilityChanged(visibility)
865
865
  if (visibility != View.VISIBLE) celebration?.stop()
866
866
  }
867
+ var pageViewport: Pair<Int, Int>? = null
867
868
  var onLayoutChanged: (() -> Unit)? = null
868
869
  private val styles = mutableMapOf<View, CraftNativeLayoutStyle>()
869
870
  private var wrap = false
@@ -1839,6 +1840,12 @@ internal class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
1839
1840
  }
1840
1841
 
1841
1842
  override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
1843
+ pageViewport?.let { (pageWidth, pageHeight) ->
1844
+ val pages = (0 until childCount).map { getChildAt(it) }.filter { it.visibility != View.GONE }
1845
+ pages.forEach { it.measure(MeasureSpec.makeMeasureSpec(pageWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(pageHeight, MeasureSpec.EXACTLY)) }
1846
+ setMeasuredDimension(pageWidth * pages.size, pageHeight)
1847
+ return
1848
+ }
1842
1849
  val unboundedHorizontal = unboundedWidth && orientation == HORIZONTAL
1843
1850
  val widthMode = if (unboundedHorizontal) View.MeasureSpec.UNSPECIFIED else View.MeasureSpec.getMode(widthMeasureSpec)
1844
1851
  widthDefinite = widthMode == View.MeasureSpec.EXACTLY
@@ -1875,6 +1882,12 @@ internal class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
1875
1882
 
1876
1883
  override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
1877
1884
  celebration?.layout()
1885
+ pageViewport?.let { (pageWidth, pageHeight) ->
1886
+ val pages = (0 until childCount).map { getChildAt(it) }.filter { it.visibility != View.GONE }
1887
+ pages.forEachIndexed { index, child -> child.layout(index * pageWidth, 0, (index + 1) * pageWidth, pageHeight) }
1888
+ onLayoutChanged?.invoke()
1889
+ return
1890
+ }
1878
1891
  widthDefinite = true
1879
1892
  if (grid) {
1880
1893
  val layout = gridLayout(width, height)
@@ -2237,15 +2250,92 @@ private class CraftNativeKeyboardAvoidingView(context: Context) : FrameLayout(co
2237
2250
  }
2238
2251
  }
2239
2252
 
2240
- private class CraftNativeScrollView(context: Context) : SwipeRefreshLayout(context) {
2253
+ /** CSS family fallback order, using bundled app fonts before platform families. */
2254
+ internal fun craftNativeFontFamily(family: String?, bundledFonts: Map<String, Typeface> = emptyMap()): Typeface =
2255
+ family?.split(',')?.asSequence()?.map { it.trim().trim('"', '\'') }?.mapNotNull { name ->
2256
+ bundledFonts[name] ?: when (name.lowercase()) {
2257
+ "monospace", "ui-monospace" -> Typeface.MONOSPACE
2258
+ "serif", "ui-serif" -> Typeface.SERIF
2259
+ "sans-serif", "ui-sans-serif", "system-ui", "-apple-system" -> Typeface.SANS_SERIF
2260
+ else -> Typeface.create(name, Typeface.NORMAL).takeUnless { it == Typeface.DEFAULT }
2261
+ }
2262
+ }?.firstOrNull() ?: Typeface.DEFAULT
2263
+
2264
+ internal class CraftNativeScrollView(context: Context) : SwipeRefreshLayout(context) {
2241
2265
  val content = CraftNativeFlexLayout(context)
2242
2266
  // Keep one refresh target across axis changes. Replacing all children of
2243
2267
  // SwipeRefreshLayout would also detach its own animated indicator.
2244
2268
  private val scrollContainer = FrameLayout(context)
2245
- private val vertical = ScrollView(context)
2246
- private val horizontal = HorizontalScrollView(context)
2269
+ private val vertical = object : ScrollView(context) {
2270
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean = isEnabled && super.onInterceptTouchEvent(event)
2271
+ override fun onTouchEvent(event: MotionEvent): Boolean = isEnabled && super.onTouchEvent(event)
2272
+ }
2273
+ private val horizontal = object : HorizontalScrollView(context) {
2274
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean = isEnabled && super.onInterceptTouchEvent(event)
2275
+ override fun onTouchEvent(event: MotionEvent): Boolean = isEnabled && super.onTouchEvent(event)
2276
+ }
2247
2277
  private var horizontalMode: Boolean? = null
2248
2278
  private var pagingEnabled = false
2279
+ private var authoredPageIndex: Int? = null
2280
+ private var pendingPageIndex: Int? = null
2281
+ private var currentPageIndex = 0
2282
+ private var reportedPage: Pair<Int, Int>? = null
2283
+ private var pageHandler: ((Int, Int) -> Unit)? = null
2284
+ private var pageSettleGeneration = 0
2285
+ private var pageDragging = false
2286
+ private var pageInMotion = false
2287
+
2288
+ private fun schedulePageSettle(scroller: View) {
2289
+ val generation = ++pageSettleGeneration
2290
+ scroller.postDelayed({
2291
+ if (generation == pageSettleGeneration && !pageDragging) {
2292
+ pageInMotion = false
2293
+ settlePage()
2294
+ }
2295
+ }, 80L)
2296
+ }
2297
+
2298
+ fun configurePager(value: Any?, onSelect: ((Int, Int) -> Unit)?) {
2299
+ val next = (value as? Number)?.toDouble()?.takeIf { it.isFinite() }?.coerceIn(0.0, (Int.MAX_VALUE / 2).toDouble())?.toInt()
2300
+ if (next != authoredPageIndex) pendingPageIndex = next
2301
+ authoredPageIndex = next
2302
+ pageHandler = onSelect
2303
+ if (next == null) { content.pageViewport = null; reportedPage = null }
2304
+ requestLayout()
2305
+ }
2306
+
2307
+ private fun settlePage() {
2308
+ if (authoredPageIndex == null || horizontal.width <= 0 || pageDragging || pageInMotion) return
2309
+ val pages = (0 until content.childCount).map { content.getChildAt(it) }.filter { it.visibility != View.GONE }
2310
+ val index = (horizontal.scrollX.toFloat() / horizontal.width).roundToInt().coerceIn(0, max(0, pages.size - 1))
2311
+ val target = index * horizontal.width
2312
+ if (horizontal.scrollX != target) {
2313
+ // ACTION_UP can start native motion after our touch listener's snap.
2314
+ // A rounded index is not settled until that viewport is fully shown.
2315
+ pageInMotion = true
2316
+ horizontal.smoothScrollTo(target, 0)
2317
+ schedulePageSettle(horizontal)
2318
+ return
2319
+ }
2320
+ currentPageIndex = index
2321
+ pages.forEachIndexed { i, page -> page.importantForAccessibility = if (i == index) View.IMPORTANT_FOR_ACCESSIBILITY_AUTO else View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS }
2322
+ val report = index to pages.size
2323
+ if (report != reportedPage) { reportedPage = report; pageHandler?.invoke(index, pages.size) }
2324
+ }
2325
+
2326
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
2327
+ val oldWidth = horizontal.width
2328
+ super.onLayout(changed, left, top, right, bottom)
2329
+ if (authoredPageIndex != null && horizontalMode == true && horizontal.width > 0) {
2330
+ val count = (0 until content.childCount).count { content.getChildAt(it).visibility != View.GONE }
2331
+ val index = (pendingPageIndex ?: currentPageIndex).coerceIn(0, max(0, count - 1))
2332
+ if (pendingPageIndex != null || oldWidth != horizontal.width || index != currentPageIndex) horizontal.scrollTo(index * horizontal.width, 0)
2333
+ pendingPageIndex = null
2334
+ currentPageIndex = index
2335
+ settlePage()
2336
+ }
2337
+ }
2338
+
2249
2339
  private var bounces = true
2250
2340
  private var alwaysBounceVertical = true
2251
2341
  private var alwaysBounceHorizontal = true
@@ -2273,6 +2363,10 @@ private class CraftNativeScrollView(context: Context) : SwipeRefreshLayout(conte
2273
2363
  // configured. Interception is gated below instead.
2274
2364
  isEnabled = true
2275
2365
  setHorizontal(false)
2366
+ // Paging is owned by this native control even before an Activity
2367
+ // supplies optional bridge callbacks. Its normal configuration may
2368
+ // replace these listeners without changing gesture ownership.
2369
+ setScrollListeners(null, null, null, null, null, "none", null)
2276
2370
  }
2277
2371
 
2278
2372
  override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
@@ -2301,6 +2395,7 @@ private class CraftNativeScrollView(context: Context) : SwipeRefreshLayout(conte
2301
2395
  val unspecified = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
2302
2396
  val horizontalPadding = paddingLeft + paddingRight
2303
2397
  val verticalPadding = paddingTop + paddingBottom
2398
+ content.pageViewport = if (authoredPageIndex != null && sideways) max(0, widthSize - horizontalPadding) to max(0, heightSize - verticalPadding) else null
2304
2399
  content.measure(
2305
2400
  if (sideways || widthMode == MeasureSpec.UNSPECIFIED) unspecified
2306
2401
  else MeasureSpec.makeMeasureSpec(max(0, widthSize - horizontalPadding), MeasureSpec.EXACTLY),
@@ -2316,7 +2411,7 @@ private class CraftNativeScrollView(context: Context) : SwipeRefreshLayout(conte
2316
2411
  ViewGroup.LayoutParams.WRAP_CONTENT,
2317
2412
  ViewGroup.LayoutParams.MATCH_PARENT,
2318
2413
  )
2319
- params.width = max(params.width, content.measuredWidth)
2414
+ params.width = if (authoredPageIndex != null) content.measuredWidth else max(params.width, content.measuredWidth)
2320
2415
  content.layoutParams = params
2321
2416
  }
2322
2417
  fun fit(mode: Int, size: Int, wanted: Int): Int = when (mode) {
@@ -2648,11 +2743,18 @@ private class CraftNativeScrollView(context: Context) : SwipeRefreshLayout(conte
2648
2743
 
2649
2744
  scroller.setOnScrollChangeListener { _, x, y, _, _ ->
2650
2745
  if (horizontalMode != true) updateStickyHeaders()
2746
+ if (authoredPageIndex != null) {
2747
+ pageInMotion = true
2748
+ schedulePageSettle(scroller)
2749
+ }
2651
2750
  onScroll?.invoke(x, y)
2652
2751
  }
2653
2752
  scroller.setOnTouchListener { _, event ->
2654
2753
  when (event.actionMasked) {
2655
2754
  android.view.MotionEvent.ACTION_DOWN -> {
2755
+ pageDragging = true
2756
+ pageInMotion = false
2757
+ pageSettleGeneration += 1
2656
2758
  if (momentumActive) {
2657
2759
  momentumActive = false
2658
2760
  onMomentumEnd?.invoke()
@@ -2674,6 +2776,8 @@ private class CraftNativeScrollView(context: Context) : SwipeRefreshLayout(conte
2674
2776
  }
2675
2777
  onEndDrag?.invoke()
2676
2778
  if (pagingEnabled) snapToPage(scroller)
2779
+ pageDragging = false
2780
+ if (authoredPageIndex != null) schedulePageSettle(scroller)
2677
2781
  if (moved && !momentumActive) {
2678
2782
  momentumActive = true
2679
2783
  lastX = scroller.scrollX
@@ -2692,6 +2796,10 @@ private class CraftNativeScrollView(context: Context) : SwipeRefreshLayout(conte
2692
2796
 
2693
2797
  fun clearScrollListeners() {
2694
2798
  scrollListenerGeneration += 1
2799
+ pageSettleGeneration += 1
2800
+ pageDragging = false
2801
+ pageInMotion = false
2802
+ pageHandler = null
2695
2803
  vertical.setOnScrollChangeListener(null)
2696
2804
  vertical.setOnTouchListener(null)
2697
2805
  horizontal.setOnScrollChangeListener(null)
@@ -4868,7 +4976,7 @@ class MainActivity : AppCompatActivity() {
4868
4976
  val bars = chrome ?: return
4869
4977
  val depth = screens.indexOf(screen).coerceAtLeast(0)
4870
4978
  val fallback = if (depth == 0) routes.tabs.getOrNull(currentTab)?.title else null
4871
- bars.apply(screen.options, depth, fallback)
4979
+ bars.apply(screen.options, depth, fallback, screen.path)
4872
4980
  }
4873
4981
 
4874
4982
  /** Applies a screen-specific background, falling back to the dynamic app colour. */
@@ -4932,6 +5040,7 @@ class MainActivity : AppCompatActivity() {
4932
5040
  put("viewReparent")
4933
5041
  put("viewNonModalPresentation")
4934
5042
  put("nativePlatformControls")
5043
+ put("nativePager")
4935
5044
  }
4936
5045
 
4937
5046
  private fun appVersion(): Pair<String, String> = runCatching {
@@ -6095,6 +6204,10 @@ class MainActivity : AppCompatActivity() {
6095
6204
  craftNativeBoolean(props.opt("alwaysBounceHorizontal"), horizontal),
6096
6205
  )
6097
6206
  setPagingEnabled(craftNativeBoolean(props.opt("pagingEnabled")))
6207
+ val pageHandler = craftNativeEventName(events, "onPageSelected").takeIf { it.isNotBlank() }
6208
+ configurePager(props.opt("pageIndex"), pageHandler?.let { handler ->
6209
+ { index, count -> send(screen, "EVENT", JSONObject().put("handlerName", handler).put("nativeEvent", JSONObject().put("index", index).put("count", count))) }
6210
+ })
6098
6211
  val refreshNode = (0 until children.length()).asSequence()
6099
6212
  .mapNotNull { children.optJSONObject(it) }
6100
6213
  .firstOrNull { canonicalType(it.optString("type")) == "RefreshControl" }
@@ -6916,7 +7029,7 @@ class MainActivity : AppCompatActivity() {
6916
7029
  }
6917
7030
  val italic = craftNativeOptionalString(style.opt("fontStyle")) == "italic"
6918
7031
  val family = craftNativeOptionalString(style.opt("fontFamily"))?.takeIf { it.isNotBlank() }
6919
- val base = family?.let { bundledFonts[it] } ?: Typeface.create(family, Typeface.NORMAL)
7032
+ val base = craftNativeFontFamily(family, bundledFonts)
6920
7033
  view.typeface = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
6921
7034
  Typeface.create(base, weight, italic)
6922
7035
  } else Typeface.create(base, (if (weight >= 600) Typeface.BOLD else Typeface.NORMAL) or
@@ -7555,6 +7668,7 @@ class MainActivity : AppCompatActivity() {
7555
7668
  }
7556
7669
 
7557
7670
  override fun onPause() {
7671
+ if (::services.isInitialized) services.pauseVoice()
7558
7672
  nativeAppState = "inactive"
7559
7673
  publish("APP_STATE", JSONObject().put("state", nativeAppState))
7560
7674
  super.onPause()
@@ -116,11 +116,7 @@ dependencies {
116
116
 
117
117
  {{WALLET_DEPENDENCY}}
118
118
 
119
- // On-device computer vision APIs exposed by CraftBridge
120
- implementation("com.google.mlkit:barcode-scanning:17.3.0")
121
- implementation("com.google.mlkit:image-labeling:17.0.9")
122
- implementation("com.google.mlkit:object-detection:17.0.2")
123
- implementation("com.google.mlkit:text-recognition:16.0.1")
119
+ {{MACHINE_LEARNING_DEPENDENCIES}}
124
120
 
125
121
  // Google Play Billing (In-App Purchase)
126
122
  implementation("com.android.billingclient:billing:6.1.0")
@@ -1844,6 +1844,15 @@ export declare const health: {
1844
1844
  onHeartRate(callback: (samples: HealthHeartRateSample[]) => void): () => void;
1845
1845
  };
1846
1846
  export interface LiveActivityState {
1847
+ /** Native step boundaries retained on device; at most 1000, with detail limited to 96 characters. Only the current state is sent to the widget. */
1848
+ transitions?: Array<{
1849
+ at: number;
1850
+ detail: string;
1851
+ duration?: number | null;
1852
+ }>;
1853
+ /** An actual HealthKit workout (iOS 26+, already authorised) owns background updates. */
1854
+ workoutType?: string;
1855
+ sessionStartedAt?: number;
1847
1856
  /** What to show once staleAt passes without fresh app state. */
1848
1857
  staleStatus?: string;
1849
1858
  /** Current step, interval or session detail. */
package/dist/cli.js CHANGED
@@ -262,6 +262,7 @@ async function packageMacOS(config, outDir) {
262
262
  iconPath: config.iconPath,
263
263
  provisioningProfile: opts.provisioningProfile,
264
264
  additionalExecutables: opts.additionalExecutables,
265
+ nativeRuntimePath: opts.nativeRuntimePath,
265
266
  outputPath: appBundlePath
266
267
  });
267
268
  if (!appBundle.success) {
@@ -310,6 +311,12 @@ async function packageMacOS(config, outDir) {
310
311
  } catch (error) {
311
312
  return [{ success: false, platform: "macos", format: "app", error: error.message }];
312
313
  }
314
+ if (opts.notarize && !opts.appStore) {
315
+ const notarized = await notarizeIfRequested(opts, appBundlePath, "app");
316
+ results.push(...notarized);
317
+ if (notarized.some((result) => !result.success))
318
+ return results;
319
+ }
313
320
  results.push({ success: true, platform: "macos", format: "app", outputPath: appBundlePath });
314
321
  if (opts.dmg !== false) {
315
322
  const outputPath = join2(outDir, `${name}-${version}.dmg`);
@@ -358,24 +365,36 @@ async function notarizeIfRequested(opts, artifactPath, format) {
358
365
  error: "Notarization requires a keychain profile, API key, or Apple ID/password/team ID"
359
366
  }];
360
367
  }
361
- const submitted = await runTool("xcrun", notarytoolArguments({
362
- artifactPath,
363
- appleId: opts.appleId,
364
- applePassword: opts.applePassword,
365
- teamId: opts.teamId,
366
- keychainProfile: opts.notaryKeychainProfile,
367
- apiKey: opts.notaryApiKey
368
- }));
369
- if (!submitted.success)
370
- return [{ success: false, platform: "macos", format: `${format} (notarize)`, error: submitted.error }];
371
- const stapled = await runTool("xcrun", ["stapler", "staple", artifactPath]);
372
- return [{
373
- success: stapled.success,
374
- platform: "macos",
375
- format: `${format} (notarize)`,
376
- outputPath: stapled.success ? artifactPath : undefined,
377
- error: stapled.success ? undefined : stapled.error
378
- }];
368
+ const scratch = format === "app" ? createTempDir("craft-notarize-") : undefined;
369
+ try {
370
+ const submission = scratch ? join2(scratch, "app.zip") : artifactPath;
371
+ if (scratch) {
372
+ const zipped = await runTool("ditto", ["-c", "-k", "--keepParent", artifactPath, submission]);
373
+ if (!zipped.success)
374
+ return [{ success: false, platform: "macos", format: `${format} (notarize)`, error: zipped.error }];
375
+ }
376
+ const submitted = await runTool("xcrun", notarytoolArguments({
377
+ artifactPath: submission,
378
+ appleId: opts.appleId,
379
+ applePassword: opts.applePassword,
380
+ teamId: opts.teamId,
381
+ keychainProfile: opts.notaryKeychainProfile,
382
+ apiKey: opts.notaryApiKey
383
+ }));
384
+ if (!submitted.success)
385
+ return [{ success: false, platform: "macos", format: `${format} (notarize)`, error: submitted.error }];
386
+ const stapled = await runTool("xcrun", ["stapler", "staple", artifactPath]);
387
+ return [{
388
+ success: stapled.success,
389
+ platform: "macos",
390
+ format: `${format} (notarize)`,
391
+ outputPath: stapled.success ? artifactPath : undefined,
392
+ error: stapled.success ? undefined : stapled.error
393
+ }];
394
+ } finally {
395
+ if (scratch)
396
+ removeTempDir(scratch);
397
+ }
379
398
  }
380
399
  async function packageWindows(config, outDir) {
381
400
  const results = [];
@@ -543,7 +562,7 @@ ${schemes.length ? `MimeType=${schemes.map((scheme) => `x-scheme-handler/${schem
543
562
  }
544
563
  function macOSInfoPlist(metadata) {
545
564
  const entries = [
546
- ["CFBundleExecutable", metadata.name],
565
+ ["CFBundleExecutable", metadata.executableName || metadata.name],
547
566
  ["CFBundleIdentifier", metadata.bundleId],
548
567
  ["CFBundleName", metadata.name],
549
568
  ["CFBundleShortVersionString", metadata.version],
@@ -686,6 +705,9 @@ function copyMacOSAppBundle(source, destination) {
686
705
  return { success: false, error: error.message };
687
706
  }
688
707
  }
708
+ function macOSRuntimeExecutablePath(appBundlePath) {
709
+ return join2(appBundlePath, "Contents", "Helpers", "CraftRuntime.app", "Contents", "MacOS", "craft");
710
+ }
689
711
  function createMacOSAppBundle(opts) {
690
712
  try {
691
713
  const { name, binaryPath, iconPath, provisioningProfile, additionalExecutables, outputPath } = opts;
@@ -702,6 +724,27 @@ function createMacOSAppBundle(opts) {
702
724
  copyFileSync(executable, destination);
703
725
  chmodSync(destination, 493);
704
726
  }
727
+ if (opts.nativeRuntimePath) {
728
+ const helper = macOSRuntimeExecutablePath(outputPath);
729
+ const helperContents = resolve(helper, "..", "..");
730
+ mkdirSync(join2(helperContents, "MacOS"), { recursive: true });
731
+ copyFileSync(opts.nativeRuntimePath, helper);
732
+ chmodSync(helper, 493);
733
+ if (iconPath) {
734
+ mkdirSync(join2(helperContents, "Resources"), { recursive: true });
735
+ copyFileSync(iconPath, join2(helperContents, "Resources", "icon.icns"));
736
+ }
737
+ writeFileSync3(join2(helperContents, "Info.plist"), macOSInfoPlist({
738
+ name,
739
+ executableName: "craft",
740
+ version: opts.version,
741
+ bundleId: `${opts.bundleId}.runtime`,
742
+ buildNumber: opts.buildNumber,
743
+ minimumSystemVersion: opts.minimumSystemVersion,
744
+ menuBarOnly: true,
745
+ iconName: iconPath ? "icon" : undefined
746
+ }));
747
+ }
705
748
  let iconName;
706
749
  if (iconPath) {
707
750
  if (!existsSync4(iconPath))
@@ -2945,7 +2988,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
2945
2988
  `);
2946
2989
  }
2947
2990
  // package.json
2948
- var version = "0.0.304";
2991
+ var version = "0.0.328";
2949
2992
 
2950
2993
  // bin/cli.ts
2951
2994
  var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
package/dist/index.cjs CHANGED
@@ -158,6 +158,7 @@ __export(exports_src, {
158
158
  liveActivities: () => liveActivities,
159
159
  loadURL: () => loadURL,
160
160
  location: () => location,
161
+ macOSRuntimeExecutablePath: () => macOSRuntimeExecutablePath,
161
162
  materialYou: () => materialYou,
162
163
  media: () => media,
163
164
  menuItem: () => menuItem,
@@ -374,7 +375,7 @@ function craftBinaryNotFoundMessage(triedPath) {
374
375
  `);
375
376
  }
376
377
  // package.json
377
- var version = "0.0.304";
378
+ var version = "0.0.328";
378
379
 
379
380
  // src/sdk-version.ts
380
381
  var SDK_VERSION = version;
@@ -627,6 +628,7 @@ async function packageMacOS(config, outDir) {
627
628
  iconPath: config.iconPath,
628
629
  provisioningProfile: opts.provisioningProfile,
629
630
  additionalExecutables: opts.additionalExecutables,
631
+ nativeRuntimePath: opts.nativeRuntimePath,
630
632
  outputPath: appBundlePath
631
633
  });
632
634
  if (!appBundle.success) {
@@ -675,6 +677,12 @@ async function packageMacOS(config, outDir) {
675
677
  } catch (error) {
676
678
  return [{ success: false, platform: "macos", format: "app", error: error.message }];
677
679
  }
680
+ if (opts.notarize && !opts.appStore) {
681
+ const notarized = await notarizeIfRequested(opts, appBundlePath, "app");
682
+ results.push(...notarized);
683
+ if (notarized.some((result) => !result.success))
684
+ return results;
685
+ }
678
686
  results.push({ success: true, platform: "macos", format: "app", outputPath: appBundlePath });
679
687
  if (opts.dmg !== false) {
680
688
  const outputPath = import_path.join(outDir, `${name}-${version}.dmg`);
@@ -723,24 +731,36 @@ async function notarizeIfRequested(opts, artifactPath, format) {
723
731
  error: "Notarization requires a keychain profile, API key, or Apple ID/password/team ID"
724
732
  }];
725
733
  }
726
- const submitted = await runTool("xcrun", notarytoolArguments({
727
- artifactPath,
728
- appleId: opts.appleId,
729
- applePassword: opts.applePassword,
730
- teamId: opts.teamId,
731
- keychainProfile: opts.notaryKeychainProfile,
732
- apiKey: opts.notaryApiKey
733
- }));
734
- if (!submitted.success)
735
- return [{ success: false, platform: "macos", format: `${format} (notarize)`, error: submitted.error }];
736
- const stapled = await runTool("xcrun", ["stapler", "staple", artifactPath]);
737
- return [{
738
- success: stapled.success,
739
- platform: "macos",
740
- format: `${format} (notarize)`,
741
- outputPath: stapled.success ? artifactPath : undefined,
742
- error: stapled.success ? undefined : stapled.error
743
- }];
734
+ const scratch = format === "app" ? createTempDir("craft-notarize-") : undefined;
735
+ try {
736
+ const submission = scratch ? import_path.join(scratch, "app.zip") : artifactPath;
737
+ if (scratch) {
738
+ const zipped = await runTool("ditto", ["-c", "-k", "--keepParent", artifactPath, submission]);
739
+ if (!zipped.success)
740
+ return [{ success: false, platform: "macos", format: `${format} (notarize)`, error: zipped.error }];
741
+ }
742
+ const submitted = await runTool("xcrun", notarytoolArguments({
743
+ artifactPath: submission,
744
+ appleId: opts.appleId,
745
+ applePassword: opts.applePassword,
746
+ teamId: opts.teamId,
747
+ keychainProfile: opts.notaryKeychainProfile,
748
+ apiKey: opts.notaryApiKey
749
+ }));
750
+ if (!submitted.success)
751
+ return [{ success: false, platform: "macos", format: `${format} (notarize)`, error: submitted.error }];
752
+ const stapled = await runTool("xcrun", ["stapler", "staple", artifactPath]);
753
+ return [{
754
+ success: stapled.success,
755
+ platform: "macos",
756
+ format: `${format} (notarize)`,
757
+ outputPath: stapled.success ? artifactPath : undefined,
758
+ error: stapled.success ? undefined : stapled.error
759
+ }];
760
+ } finally {
761
+ if (scratch)
762
+ removeTempDir(scratch);
763
+ }
744
764
  }
745
765
  async function packageWindows(config, outDir) {
746
766
  const results = [];
@@ -909,7 +929,7 @@ ${schemes.length ? `MimeType=${schemes.map((scheme) => `x-scheme-handler/${schem
909
929
  }
910
930
  function macOSInfoPlist(metadata) {
911
931
  const entries = [
912
- ["CFBundleExecutable", metadata.name],
932
+ ["CFBundleExecutable", metadata.executableName || metadata.name],
913
933
  ["CFBundleIdentifier", metadata.bundleId],
914
934
  ["CFBundleName", metadata.name],
915
935
  ["CFBundleShortVersionString", metadata.version],
@@ -1052,6 +1072,9 @@ function copyMacOSAppBundle(source, destination) {
1052
1072
  return { success: false, error: error.message };
1053
1073
  }
1054
1074
  }
1075
+ function macOSRuntimeExecutablePath(appBundlePath) {
1076
+ return import_path.join(appBundlePath, "Contents", "Helpers", "CraftRuntime.app", "Contents", "MacOS", "craft");
1077
+ }
1055
1078
  function createMacOSAppBundle(opts) {
1056
1079
  try {
1057
1080
  const { name, binaryPath, iconPath, provisioningProfile, additionalExecutables, outputPath } = opts;
@@ -1068,6 +1091,27 @@ function createMacOSAppBundle(opts) {
1068
1091
  import_fs.copyFileSync(executable, destination);
1069
1092
  import_fs.chmodSync(destination, 493);
1070
1093
  }
1094
+ if (opts.nativeRuntimePath) {
1095
+ const helper = macOSRuntimeExecutablePath(outputPath);
1096
+ const helperContents = import_path.resolve(helper, "..", "..");
1097
+ import_fs.mkdirSync(import_path.join(helperContents, "MacOS"), { recursive: true });
1098
+ import_fs.copyFileSync(opts.nativeRuntimePath, helper);
1099
+ import_fs.chmodSync(helper, 493);
1100
+ if (iconPath) {
1101
+ import_fs.mkdirSync(import_path.join(helperContents, "Resources"), { recursive: true });
1102
+ import_fs.copyFileSync(iconPath, import_path.join(helperContents, "Resources", "icon.icns"));
1103
+ }
1104
+ import_fs.writeFileSync(import_path.join(helperContents, "Info.plist"), macOSInfoPlist({
1105
+ name,
1106
+ executableName: "craft",
1107
+ version: opts.version,
1108
+ bundleId: `${opts.bundleId}.runtime`,
1109
+ buildNumber: opts.buildNumber,
1110
+ minimumSystemVersion: opts.minimumSystemVersion,
1111
+ menuBarOnly: true,
1112
+ iconName: iconPath ? "icon" : undefined
1113
+ }));
1114
+ }
1071
1115
  let iconName;
1072
1116
  if (iconPath) {
1073
1117
  if (!import_fs.existsSync(iconPath))
@@ -10884,6 +10928,9 @@ class AutoUpdater extends import_events3.EventEmitter {
10884
10928
  if (this.isNewerVersion(this.cachedManifest.version, this.config.currentVersion)) {
10885
10929
  this.updateInfo = this.cachedManifest;
10886
10930
  this.emit("update-available", this.cachedManifest);
10931
+ if (this.config.autoDownload) {
10932
+ await this.downloadUpdate();
10933
+ }
10887
10934
  return this.cachedManifest;
10888
10935
  }
10889
10936
  this.emit("update-not-available");
package/dist/index.d.cts CHANGED
@@ -24,7 +24,7 @@ export declare function parseCraftVersionOutput(output: string): string;
24
24
  * (#236).
25
25
  */
26
26
  export declare function craftProcessFailedMessage(code: number | null, stderr: string, quiet: boolean): string;
27
- export { packageApp, pack, type PackageConfig, type PackageResult } from './package.js';
27
+ export { packageApp, pack, macOSRuntimeExecutablePath, type PackageConfig, type PackageResult } from './package.js';
28
28
  export { craftBinaryNotFoundMessage, resolveCraftBinary } from './binary-resolver.js';
29
29
  export * from './utils/index.js';
30
30
  export * from './api/index.js';
@@ -85,4 +85,4 @@ export declare function show(html: string, options?: WindowOptions): Promise<voi
85
85
  * Quick helper to load a URL
86
86
  */
87
87
  export declare function loadURL(url: string, options?: WindowOptions): Promise<void>;
88
- export type { WindowOptions, CraftSidebarAPI, CraftNativeUIAPI, CraftContextMenuItem, CraftContextMenuResult, AppConfig, CraftTrayAPI, CraftWindowAPI, CraftAppAPI, CraftBridgeAPI, MenubarState, CraftTimersAPI, Permission, HapticType, CraftHapticsAPI, CameraOptions, PhotoPickerOptions, CraftMobileAPI, CraftFileSystemAPI, CraftFsWatchEvent, CraftFsWatchHandle, CraftDatabaseAPI, CraftHttpAPI, CraftCryptoAPI, CraftCapabilities, CapabilityNamespace, CapabilityNamespaceStatus, CapabilityAction, CraftPreferencesAPI, CraftPreferencesInfo, CraftSettingsAPI, PreferenceValue, CraftEventType, CraftEventMap, CraftEventHandler, CraftEventEmitter, IOSConfig, AndroidConfig, MacOSConfig, WindowsConfig, LinuxConfig, CraftAppConfig, } from './types.js';
88
+ export type { WindowOptions, CraftSidebarAPI, CraftNativeUIAPI, CraftContextMenuItem, CraftContextMenuResult, AppConfig, CraftTrayAPI, CraftWindowAPI, CraftAppAPI, CraftBridgeAPI, MenubarState, MenubarInitOptions, CraftTimersAPI, Permission, HapticType, CraftHapticsAPI, CameraOptions, PhotoPickerOptions, CraftMobileAPI, CraftFileSystemAPI, CraftFsWatchEvent, CraftFsWatchHandle, CraftDatabaseAPI, CraftHttpAPI, CraftCryptoAPI, CraftCapabilities, CapabilityNamespace, CapabilityNamespaceStatus, CapabilityAction, CraftPreferencesAPI, CraftPreferencesInfo, CraftSettingsAPI, PreferenceValue, CraftEventType, CraftEventMap, CraftEventHandler, CraftEventEmitter, IOSConfig, AndroidConfig, MacOSConfig, WindowsConfig, LinuxConfig, CraftAppConfig, } from './types.js';
package/dist/index.d.ts CHANGED
@@ -24,7 +24,7 @@ export declare function parseCraftVersionOutput(output: string): string;
24
24
  * (#236).
25
25
  */
26
26
  export declare function craftProcessFailedMessage(code: number | null, stderr: string, quiet: boolean): string;
27
- export { packageApp, pack, type PackageConfig, type PackageResult } from './package.js';
27
+ export { packageApp, pack, macOSRuntimeExecutablePath, type PackageConfig, type PackageResult } from './package.js';
28
28
  export { craftBinaryNotFoundMessage, resolveCraftBinary } from './binary-resolver.js';
29
29
  export * from './utils/index.js';
30
30
  export * from './api/index.js';
@@ -85,4 +85,4 @@ export declare function show(html: string, options?: WindowOptions): Promise<voi
85
85
  * Quick helper to load a URL
86
86
  */
87
87
  export declare function loadURL(url: string, options?: WindowOptions): Promise<void>;
88
- export type { WindowOptions, CraftSidebarAPI, CraftNativeUIAPI, CraftContextMenuItem, CraftContextMenuResult, AppConfig, CraftTrayAPI, CraftWindowAPI, CraftAppAPI, CraftBridgeAPI, MenubarState, CraftTimersAPI, Permission, HapticType, CraftHapticsAPI, CameraOptions, PhotoPickerOptions, CraftMobileAPI, CraftFileSystemAPI, CraftFsWatchEvent, CraftFsWatchHandle, CraftDatabaseAPI, CraftHttpAPI, CraftCryptoAPI, CraftCapabilities, CapabilityNamespace, CapabilityNamespaceStatus, CapabilityAction, CraftPreferencesAPI, CraftPreferencesInfo, CraftSettingsAPI, PreferenceValue, CraftEventType, CraftEventMap, CraftEventHandler, CraftEventEmitter, IOSConfig, AndroidConfig, MacOSConfig, WindowsConfig, LinuxConfig, CraftAppConfig, } from './types.js';
88
+ export type { WindowOptions, CraftSidebarAPI, CraftNativeUIAPI, CraftContextMenuItem, CraftContextMenuResult, AppConfig, CraftTrayAPI, CraftWindowAPI, CraftAppAPI, CraftBridgeAPI, MenubarState, MenubarInitOptions, CraftTimersAPI, Permission, HapticType, CraftHapticsAPI, CameraOptions, PhotoPickerOptions, CraftMobileAPI, CraftFileSystemAPI, CraftFsWatchEvent, CraftFsWatchHandle, CraftDatabaseAPI, CraftHttpAPI, CraftCryptoAPI, CraftCapabilities, CapabilityNamespace, CapabilityNamespaceStatus, CapabilityAction, CraftPreferencesAPI, CraftPreferencesInfo, CraftSettingsAPI, PreferenceValue, CraftEventType, CraftEventMap, CraftEventHandler, CraftEventEmitter, IOSConfig, AndroidConfig, MacOSConfig, WindowsConfig, LinuxConfig, CraftAppConfig, } from './types.js';