@capgo/capacitor-updater 7.43.3 → 7.45.10

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.
@@ -54,10 +54,31 @@ import UIKit
54
54
  private var statsFlushTimer: Timer?
55
55
  private static let statsFlushInterval: TimeInterval = 1.0
56
56
 
57
+ private static func sanitizeHeaderValue(_ value: String) -> String {
58
+ if value.isEmpty {
59
+ return "unknown"
60
+ }
61
+
62
+ let filteredScalars = value.unicodeScalars.filter { scalar in
63
+ let cp = scalar.value
64
+ let isVisibleAscii = (0x20...0x7E).contains(cp)
65
+ let isIso88591 = (0xA0...0xFF).contains(cp)
66
+ return isVisibleAscii || isIso88591
67
+ }
68
+
69
+ let sanitized = String(String.UnicodeScalarView(filteredScalars)).trimmingCharacters(in: .whitespacesAndNewlines)
70
+ return sanitized.isEmpty ? "unknown" : sanitized
71
+ }
72
+
73
+ static func buildUserAgent(appId: String, pluginVersion: String, versionOs: String) -> String {
74
+ let safePluginVersion = sanitizeHeaderValue(pluginVersion)
75
+ let safeAppId = sanitizeHeaderValue(appId)
76
+ let safeVersionOs = sanitizeHeaderValue(versionOs)
77
+ return "CapacitorUpdater/\(safePluginVersion) (\(safeAppId)) ios/\(safeVersionOs)"
78
+ }
79
+
57
80
  private var userAgent: String {
58
- let safePluginVersion = pluginVersion.isEmpty ? "unknown" : pluginVersion
59
- let safeAppId = appId.isEmpty ? "unknown" : appId
60
- return "CapacitorUpdater/\(safePluginVersion) (\(safeAppId)) ios/\(versionOs)"
81
+ CapgoUpdater.buildUserAgent(appId: appId, pluginVersion: pluginVersion, versionOs: versionOs)
61
82
  }
62
83
 
63
84
  private lazy var alamofireSession: Session = {
@@ -71,6 +92,7 @@ import UIKit
71
92
  notifyDownloadRaw(id, percent, ignoreMultipleOfTen, bundle)
72
93
  }
73
94
  public var notifyDownload: (String, Int) -> Void = { _, _ in }
95
+ public var notifyListeners: (String, [String: Any]) -> Void = { _, _ in }
74
96
 
75
97
  public func setLogger(_ logger: Logger) {
76
98
  self.logger = logger
@@ -456,6 +478,47 @@ import UIKit
456
478
  public func getLatest(url: URL, channel: String?) -> AppVersion {
457
479
  let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
458
480
  let latest: AppVersion = AppVersion()
481
+ func applyLatestResponse(_ value: AppVersionDec?) {
482
+ if let url = value?.url {
483
+ latest.url = url
484
+ }
485
+ if let checksum = value?.checksum {
486
+ latest.checksum = checksum
487
+ }
488
+ if let version = value?.version {
489
+ latest.version = version
490
+ }
491
+ if let major = value?.major {
492
+ latest.major = major
493
+ }
494
+ if let breaking = value?.breaking {
495
+ latest.breaking = breaking
496
+ }
497
+ if let error = value?.error {
498
+ latest.error = error
499
+ }
500
+ if let kind = value?.kind {
501
+ latest.kind = kind
502
+ }
503
+ if let message = value?.message {
504
+ latest.message = message
505
+ }
506
+ if let sessionKey = value?.session_key {
507
+ latest.sessionKey = sessionKey
508
+ }
509
+ if let data = value?.data {
510
+ latest.data = data
511
+ }
512
+ if let manifest = value?.manifest {
513
+ latest.manifest = manifest
514
+ }
515
+ if let link = value?.link {
516
+ latest.link = link
517
+ }
518
+ if let comment = value?.comment {
519
+ latest.comment = comment
520
+ }
521
+ }
459
522
  var parameters: InfoObject = self.createInfoObject()
460
523
  if let channel = channel {
461
524
  parameters.defaultChannel = channel
@@ -467,48 +530,32 @@ import UIKit
467
530
  switch response.result {
468
531
  case .success:
469
532
  latest.statusCode = response.response?.statusCode ?? 0
470
- if let url = response.value?.url {
471
- latest.url = url
472
- }
473
- if let checksum = response.value?.checksum {
474
- latest.checksum = checksum
475
- }
476
- if let version = response.value?.version {
477
- latest.version = version
478
- }
479
- if let major = response.value?.major {
480
- latest.major = major
481
- }
482
- if let breaking = response.value?.breaking {
483
- latest.breaking = breaking
484
- }
485
- if let error = response.value?.error {
486
- latest.error = error
487
- }
488
- if let message = response.value?.message {
489
- latest.message = message
490
- }
491
- if let sessionKey = response.value?.session_key {
492
- latest.sessionKey = sessionKey
493
- }
494
- if let data = response.value?.data {
495
- latest.data = data
496
- }
497
- if let manifest = response.value?.manifest {
498
- latest.manifest = manifest
499
- }
500
- if let link = response.value?.link {
501
- latest.link = link
502
- }
503
- if let comment = response.value?.comment {
504
- latest.comment = comment
505
- }
533
+ applyLatestResponse(response.value)
506
534
  case let .failure(error):
507
535
  self.logger.error("Error getting latest version")
508
536
  self.logger.debug("Response: \(response.value.debugDescription), Error: \(error)")
509
- latest.message = "Error getting Latest"
510
- latest.error = "response_error"
511
537
  latest.statusCode = response.response?.statusCode ?? 0
538
+ if let data = response.data,
539
+ let decoded = try? JSONDecoder().decode(AppVersionDec.self, from: data) {
540
+ applyLatestResponse(decoded)
541
+ let decodedError = decoded.error ?? ""
542
+ let decodedKind = decoded.kind ?? ""
543
+ if decodedError.isEmpty && decodedKind.isEmpty {
544
+ if latest.message == nil || latest.message?.isEmpty == true {
545
+ latest.message = "Error getting Latest"
546
+ }
547
+ if latest.error == nil || latest.error?.isEmpty == true {
548
+ latest.error = "response_error"
549
+ }
550
+ if latest.kind == nil || latest.kind?.isEmpty == true {
551
+ latest.kind = "failed"
552
+ }
553
+ }
554
+ } else {
555
+ latest.message = "Error getting Latest"
556
+ latest.error = "response_error"
557
+ latest.kind = "failed"
558
+ }
512
559
  }
513
560
  semaphore.signal()
514
561
  }
@@ -522,6 +569,22 @@ import UIKit
522
569
  logger.info("Current bundle set to: \((bundle ).isEmpty ? BundleInfo.ID_BUILTIN : bundle)")
523
570
  }
524
571
 
572
+ static func shouldResetForForeignBundle(bundlePath: String?, isBuiltin: Bool, hasStoredBundleInfo: Bool) -> Bool {
573
+ guard let bundlePath, !bundlePath.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
574
+ return false
575
+ }
576
+ return !isBuiltin && !hasStoredBundleInfo
577
+ }
578
+
579
+ private func hasStoredBundleInfo(id: String) -> Bool {
580
+ guard !id.isEmpty,
581
+ id != BundleInfo.ID_BUILTIN,
582
+ id != BundleInfo.VERSION_UNKNOWN else {
583
+ return false
584
+ }
585
+ return UserDefaults.standard.object(forKey: "\(id)\(self.INFO_SUFFIX)") != nil
586
+ }
587
+
525
588
  // Per-download temp file paths to prevent collisions when multiple downloads run concurrently
526
589
  private func tempDataPath(for id: String) -> URL {
527
590
  return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("package_\(id).tmp")
@@ -578,10 +641,36 @@ import UIKit
578
641
  for entry in manifest {
579
642
  guard let fileName = entry.file_name,
580
643
  let downloadUrl = entry.download_url else {
644
+ let error = NSError(
645
+ domain: "ManifestEntryError",
646
+ code: 1,
647
+ userInfo: [
648
+ NSLocalizedDescriptionKey: "Manifest entry is missing file_name or download_url"
649
+ ]
650
+ )
651
+ errorLock.lock()
652
+ if downloadError == nil {
653
+ downloadError = error
654
+ }
655
+ errorLock.unlock()
656
+ hasError.value = true
657
+ logger.error("Manifest entry is missing file_name or download_url")
581
658
  continue
582
659
  }
583
660
  guard let entryFileHash = entry.file_hash, !entryFileHash.isEmpty else {
584
661
  logger.error("Missing file_hash for manifest entry: \(entry.file_name ?? "unknown")")
662
+ let error = NSError(
663
+ domain: "ManifestEntryError",
664
+ code: 2,
665
+ userInfo: [
666
+ NSLocalizedDescriptionKey: "Manifest entry is missing file_hash for \(entry.file_name ?? "unknown")"
667
+ ]
668
+ )
669
+ errorLock.lock()
670
+ if downloadError == nil {
671
+ downloadError = error
672
+ }
673
+ errorLock.unlock()
585
674
  hasError.value = true
586
675
  continue
587
676
  }
@@ -670,11 +759,16 @@ import UIKit
670
759
  // Execute all operations concurrently and wait for completion
671
760
  manifestDownloadQueue.addOperations(operations, waitUntilFinished: true)
672
761
 
673
- if hasError.value, let error = downloadError {
762
+ if hasError.value {
763
+ let resolvedError = downloadError ?? NSError(
764
+ domain: "ManifestDownloadError",
765
+ code: 1,
766
+ userInfo: [NSLocalizedDescriptionKey: "Manifest download failed due to invalid or missing entries"]
767
+ )
674
768
  // Update bundle status to ERROR if download failed
675
769
  let errorBundle = bundleInfo.setStatus(status: BundleStatus.ERROR.localizedString)
676
770
  self.saveBundleInfo(id: id, bundle: errorBundle)
677
- throw error
771
+ throw resolvedError
678
772
  }
679
773
 
680
774
  // Update bundle status to PENDING after successful download
@@ -1411,6 +1505,52 @@ import UIKit
1411
1505
  return libraryDir.appendingPathComponent(self.bundleDirectory).appendingPathComponent(id)
1412
1506
  }
1413
1507
 
1508
+ struct ResetState {
1509
+ let currentBundlePath: String
1510
+ let fallbackBundleId: String
1511
+ let nextBundleId: String?
1512
+ }
1513
+
1514
+ func captureResetState() -> ResetState {
1515
+ ResetState(
1516
+ currentBundlePath: UserDefaults.standard.string(forKey: self.CAP_SERVER_PATH) ?? self.DEFAULT_FOLDER,
1517
+ fallbackBundleId: UserDefaults.standard.string(forKey: self.FALLBACK_VERSION) ?? BundleInfo.ID_BUILTIN,
1518
+ nextBundleId: UserDefaults.standard.string(forKey: self.NEXT_VERSION)
1519
+ )
1520
+ }
1521
+
1522
+ func restoreResetState(_ state: ResetState) {
1523
+ let currentBundlePath = state.currentBundlePath.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
1524
+ ? self.DEFAULT_FOLDER
1525
+ : state.currentBundlePath
1526
+ let fallbackBundleId = state.fallbackBundleId.isEmpty ? BundleInfo.ID_BUILTIN : state.fallbackBundleId
1527
+
1528
+ self.setCurrentBundle(bundle: currentBundlePath)
1529
+ UserDefaults.standard.set(fallbackBundleId, forKey: self.FALLBACK_VERSION)
1530
+ if let nextBundleId = state.nextBundleId, !nextBundleId.isEmpty {
1531
+ UserDefaults.standard.set(nextBundleId, forKey: self.NEXT_VERSION)
1532
+ } else {
1533
+ UserDefaults.standard.removeObject(forKey: self.NEXT_VERSION)
1534
+ }
1535
+ UserDefaults.standard.synchronize()
1536
+ }
1537
+
1538
+ func prepareResetStateForTransition() {
1539
+ self.setCurrentBundle(bundle: "")
1540
+ self.setFallbackBundle(fallback: Optional<BundleInfo>.none)
1541
+ _ = self.setNextBundle(next: Optional<String>.none)
1542
+ }
1543
+
1544
+ func finalizeResetTransition(previousBundleName: String, isInternal: Bool) {
1545
+ if !isInternal {
1546
+ self.sendStats(action: "reset", versionName: self.getCurrentBundle().getVersionName(), oldVersionName: previousBundleName)
1547
+ }
1548
+ }
1549
+
1550
+ func canSet(bundle: BundleInfo) -> Bool {
1551
+ bundle.isBuiltin() || self.bundleExists(id: bundle.getId())
1552
+ }
1553
+
1414
1554
  public func set(bundle: BundleInfo) -> Bool {
1415
1555
  return self.set(id: bundle.getId())
1416
1556
  }
@@ -1448,11 +1588,36 @@ import UIKit
1448
1588
  return false
1449
1589
  }
1450
1590
 
1591
+ func stagePendingReload(bundle: BundleInfo) -> Bool {
1592
+ guard !bundle.isBuiltin(), bundleExists(id: bundle.getId()) else {
1593
+ return false
1594
+ }
1595
+ self.setCurrentBundle(bundle: self.getBundleDirectory(id: bundle.getId()).path)
1596
+ return true
1597
+ }
1598
+
1599
+ func finalizePendingReload(bundle: BundleInfo, previousBundleName: String) {
1600
+ guard !bundle.isBuiltin() else {
1601
+ return
1602
+ }
1603
+ self.sendStats(action: "set", versionName: bundle.getVersionName(), oldVersionName: previousBundleName)
1604
+ }
1605
+
1451
1606
  public func autoReset() {
1452
1607
  let currentBundle: BundleInfo = self.getCurrentBundle()
1453
1608
  if !currentBundle.isBuiltin() && !self.bundleExists(id: currentBundle.getId()) {
1454
1609
  logger.info("Folder at bundle path does not exist. Triggering reset.")
1455
1610
  self.reset()
1611
+ return
1612
+ }
1613
+ let bundlePath = UserDefaults.standard.string(forKey: self.CAP_SERVER_PATH)
1614
+ if Self.shouldResetForForeignBundle(
1615
+ bundlePath: bundlePath,
1616
+ isBuiltin: currentBundle.isBuiltin(),
1617
+ hasStoredBundleInfo: self.hasStoredBundleInfo(id: currentBundle.getId())
1618
+ ) {
1619
+ logger.info("Current bundle id is not one of the bundle ids stored by this plugin. Triggering reset.")
1620
+ self.reset()
1456
1621
  }
1457
1622
  }
1458
1623
 
@@ -1463,12 +1628,8 @@ import UIKit
1463
1628
  public func reset(isInternal: Bool) {
1464
1629
  logger.info("reset: \(isInternal)")
1465
1630
  let currentBundleName = self.getCurrentBundle().getVersionName()
1466
- self.setCurrentBundle(bundle: "")
1467
- self.setFallbackBundle(fallback: Optional<BundleInfo>.none)
1468
- _ = self.setNextBundle(next: Optional<String>.none)
1469
- if !isInternal {
1470
- self.sendStats(action: "reset", versionName: self.getCurrentBundle().getVersionName(), oldVersionName: currentBundleName)
1471
- }
1631
+ self.prepareResetStateForTransition()
1632
+ self.finalizeResetTransition(previousBundleName: currentBundleName, isInternal: isInternal)
1472
1633
  }
1473
1634
 
1474
1635
  public func setSuccess(bundle: BundleInfo, autoDeletePrevious: Bool) {
@@ -1851,7 +2012,7 @@ import UIKit
1851
2012
  }
1852
2013
  let result: BundleInfo
1853
2014
  if BundleInfo.ID_BUILTIN == trueId {
1854
- result = BundleInfo(id: trueId, version: "", status: BundleStatus.SUCCESS, checksum: "")
2015
+ result = BundleInfo(id: trueId, version: self.versionBuild, status: BundleStatus.SUCCESS, checksum: "")
1855
2016
  } else if BundleInfo.VERSION_UNKNOWN == trueId {
1856
2017
  result = BundleInfo(id: trueId, version: "", status: BundleStatus.ERROR, checksum: "")
1857
2018
  } else {
@@ -1954,6 +2115,8 @@ import UIKit
1954
2115
  UserDefaults.standard.set(nextId, forKey: self.NEXT_VERSION)
1955
2116
  UserDefaults.standard.synchronize()
1956
2117
  self.setBundleStatus(id: nextId, status: BundleStatus.PENDING)
2118
+ self.sendStats(action: "set_next", versionName: newBundle.getVersionName(), oldVersionName: self.getCurrentBundle().getVersionName())
2119
+ self.notifyListeners("setNext", ["bundle": newBundle.toJSON()])
1957
2120
  return true
1958
2121
  }
1959
2122
  }
@@ -97,25 +97,17 @@ public class DelayUpdateUtils {
97
97
 
98
98
  case "date":
99
99
  if let value = value, !value.isEmpty {
100
- do {
101
- let dateFormatter = ISO8601DateFormatter()
102
- dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
103
-
104
- if let date = dateFormatter.date(from: value) {
105
- if Date() > date {
106
- // swiftlint:disable:next line_length
107
- logger.info("Date delay (value: \(value)) condition removed due to expired date at index \(index)")
108
- } else {
109
- delayConditionListToKeep.append(condition)
110
- logger.info("Date delay (value: \(value)) kept at index \(index)")
111
- }
112
- } else {
100
+ if let date = parseDateCondition(value) {
101
+ if Date() > date {
113
102
  // swiftlint:disable:next line_length
114
- logger.error("Date delay (value: \(value)) condition removed due to parsing issue at index \(index)")
103
+ logger.info("Date delay (value: \(value)) condition removed due to expired date at index \(index)")
104
+ } else {
105
+ delayConditionListToKeep.append(condition)
106
+ logger.info("Date delay (value: \(value)) kept at index \(index)")
115
107
  }
116
- } catch {
108
+ } else {
117
109
  // swiftlint:disable:next line_length
118
- logger.error("Date delay (value: \(value)) condition removed due to parsing issue at index \(index): \(error)")
110
+ logger.error("Date delay (value: \(value)) condition removed due to parsing issue at index \(index)")
119
111
  }
120
112
  } else {
121
113
  // swiftlint:disable:next line_length
@@ -216,6 +208,35 @@ public class DelayUpdateUtils {
216
208
 
217
209
  // MARK: - Helper methods
218
210
 
211
+ private func parseDateCondition(_ value: String) -> Date? {
212
+ let withFractionalSeconds = ISO8601DateFormatter()
213
+ withFractionalSeconds.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
214
+ if let date = withFractionalSeconds.date(from: value) {
215
+ return date
216
+ }
217
+
218
+ let withoutFractionalSeconds = ISO8601DateFormatter()
219
+ withoutFractionalSeconds.formatOptions = [.withInternetDateTime]
220
+ if let date = withoutFractionalSeconds.date(from: value) {
221
+ return date
222
+ }
223
+
224
+ // Legacy fallback for strings without timezone.
225
+ for format in ["yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'HH:mm:ss"] {
226
+ let formatter = DateFormatter()
227
+ formatter.locale = Locale(identifier: "en_US_POSIX")
228
+ formatter.calendar = Calendar(identifier: .gregorian)
229
+ formatter.timeZone = .current
230
+ formatter.isLenient = false
231
+ formatter.dateFormat = format
232
+ if let date = formatter.date(from: value) {
233
+ return date
234
+ }
235
+ }
236
+
237
+ return nil
238
+ }
239
+
219
240
  private func toJson(object: Any) -> String {
220
241
  guard let data = try? JSONSerialization.data(withJSONObject: object, options: []) else {
221
242
  return ""
@@ -186,6 +186,7 @@ struct AppVersionDec: Decodable {
186
186
  let url: String?
187
187
  let message: String?
188
188
  let error: String?
189
+ let kind: String?
189
190
  let session_key: String?
190
191
  let major: Bool?
191
192
  let breaking: Bool?
@@ -203,6 +204,7 @@ public class AppVersion: NSObject {
203
204
  var url: String = ""
204
205
  var message: String?
205
206
  var error: String?
207
+ var kind: String?
206
208
  var sessionKey: String?
207
209
  var major: Bool?
208
210
  var breaking: Bool?
@@ -308,19 +308,36 @@ extension UIWindow {
308
308
  }
309
309
 
310
310
  let latest = updater.getLatest(url: updateUrl, channel: name)
311
+ let latestKind = latest.kind
312
+
313
+ let detail = [latest.message, latest.error, latestKind]
314
+ .compactMap { value in
315
+ guard let value, !value.isEmpty else { return nil }
316
+ return value
317
+ }
318
+ .first ?? "server did not provide a message"
311
319
 
312
320
  // Handle update errors first (before "no new version" check)
313
- if let error = latest.error, !error.isEmpty && error != "no_new_version_available" {
321
+ if latestKind == "failed" || (latest.error?.isEmpty == false && latestKind != "up_to_date" && latestKind != "blocked") {
322
+ DispatchQueue.main.async {
323
+ progressAlert.dismiss(animated: true) {
324
+ self.showError(message: "Channel set to \(name). Update check failed: \(detail)", plugin: plugin)
325
+ }
326
+ }
327
+ return
328
+ }
329
+
330
+ if latestKind == "blocked" {
314
331
  DispatchQueue.main.async {
315
332
  progressAlert.dismiss(animated: true) {
316
- self.showError(message: "Channel set to \(name). Update check failed: \(error)", plugin: plugin)
333
+ self.showError(message: "Channel set to \(name). Update check blocked: \(detail)", plugin: plugin)
317
334
  }
318
335
  }
319
336
  return
320
337
  }
321
338
 
322
339
  // Check if there's an actual update available
323
- if latest.error == "no_new_version_available" || latest.url.isEmpty {
340
+ if latestKind == "up_to_date" || latest.url.isEmpty {
324
341
  DispatchQueue.main.async {
325
342
  progressAlert.dismiss(animated: true) {
326
343
  self.showSuccess(message: "Channel set to \(name). Already on latest version.", plugin: plugin)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "7.43.3",
3
+ "version": "7.45.10",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",
@@ -41,23 +41,26 @@
41
41
  "native"
42
42
  ],
43
43
  "scripts": {
44
- "verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
44
+ "verify": "bun run verify:ios && bun run verify:android && bun run verify:web",
45
45
  "verify:ios": "xcodebuild -scheme CapgoCapacitorUpdater -destination generic/platform=iOS",
46
46
  "verify:android": "cd android && ./gradlew clean build test && cd ..",
47
- "verify:web": "npm run build",
48
- "test": "npm run test:ios && npm run test:android",
47
+ "verify:web": "bun run build",
48
+ "test": "bun run test:ios && bun run test:android",
49
49
  "test:ios": "./scripts/test-ios.sh",
50
50
  "test:android": "cd android && ./gradlew test && cd ..",
51
- "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
52
- "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
51
+ "test:maestro": "./scripts/maestro/run-android-live-update.sh",
52
+ "test:maestro:android": "./scripts/test-maestro-android.sh",
53
+ "test:maestro:ios": "./scripts/test-maestro-ios.sh",
54
+ "lint": "bun run eslint && bun run prettier -- --check && bun run swiftlint -- lint",
55
+ "fmt": "bun run eslint -- --fix && bun run prettier -- --write && bun run swiftlint -- --fix --format",
53
56
  "eslint": "eslint . --ext .ts",
54
57
  "prettier": "prettier-pretty-check \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
55
58
  "swiftlint": "node-swiftlint",
56
59
  "docgen": "node scripts/generate-docs.js",
57
- "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
60
+ "build": "bun run clean && bun run docgen && tsc && rollup -c rollup.config.mjs",
58
61
  "clean": "rimraf ./dist",
59
62
  "watch": "tsc --watch",
60
- "prepublishOnly": "npm run build",
63
+ "prepublishOnly": "bun run build",
61
64
  "check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs"
62
65
  },
63
66
  "devDependencies": {