native-update 1.4.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/Readme.md +13 -1
  2. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdatePlugin.kt +15 -0
  3. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdateWorker.kt +23 -7
  4. package/android/src/main/java/com/aoneahsan/nativeupdate/LiveUpdatePlugin.kt +152 -4
  5. package/android/src/main/java/com/aoneahsan/nativeupdate/NativeUpdatePlugin.kt +14 -1
  6. package/android/src/main/java/com/aoneahsan/nativeupdate/NotificationActionReceiver.kt +10 -1
  7. package/android/src/main/java/com/aoneahsan/nativeupdate/SecurityManager.kt +18 -18
  8. package/cli/AGENTS.md +29 -0
  9. package/cli/CLAUDE.md +51 -0
  10. package/dist/esm/__tests__/security-enforcement.test.d.ts +1 -0
  11. package/dist/esm/__tests__/security-enforcement.test.js +95 -0
  12. package/dist/esm/__tests__/security-enforcement.test.js.map +1 -0
  13. package/dist/esm/core/config.d.ts +6 -15
  14. package/dist/esm/core/config.js +1 -4
  15. package/dist/esm/core/config.js.map +1 -1
  16. package/dist/esm/core/security.d.ts +11 -3
  17. package/dist/esm/core/security.js +19 -6
  18. package/dist/esm/core/security.js.map +1 -1
  19. package/dist/esm/definitions.d.ts +38 -24
  20. package/dist/esm/definitions.js.map +1 -1
  21. package/dist/esm/firestore/firestore-client.js +4 -0
  22. package/dist/esm/firestore/firestore-client.js.map +1 -1
  23. package/dist/esm/firestore/schema.d.ts +2 -0
  24. package/dist/esm/firestore/schema.js.map +1 -1
  25. package/dist/esm/index.d.ts +1 -2
  26. package/dist/esm/index.js +0 -2
  27. package/dist/esm/index.js.map +1 -1
  28. package/dist/esm/live-update/download-manager.d.ts +36 -5
  29. package/dist/esm/live-update/download-manager.js +61 -22
  30. package/dist/esm/live-update/download-manager.js.map +1 -1
  31. package/dist/esm/live-update/update-manager.d.ts +12 -1
  32. package/dist/esm/live-update/update-manager.js +38 -10
  33. package/dist/esm/live-update/update-manager.js.map +1 -1
  34. package/dist/esm/live-update/version-manager.d.ts +9 -0
  35. package/dist/esm/live-update/version-manager.js +40 -0
  36. package/dist/esm/live-update/version-manager.js.map +1 -1
  37. package/dist/esm/plugin.js +95 -175
  38. package/dist/esm/plugin.js.map +1 -1
  39. package/dist/esm/web.d.ts +18 -1
  40. package/dist/esm/web.js +69 -24
  41. package/dist/esm/web.js.map +1 -1
  42. package/dist/plugin.cjs.js +1 -1
  43. package/dist/plugin.cjs.js.map +1 -1
  44. package/dist/plugin.esm.js +1 -1
  45. package/dist/plugin.esm.js.map +1 -1
  46. package/dist/plugin.js +2 -2
  47. package/dist/plugin.js.map +1 -1
  48. package/docs/AGENTS.md +38 -0
  49. package/docs/CHANGELOG.md +167 -0
  50. package/docs/CLAUDE.md +101 -0
  51. package/docs/MIGRATION.md +87 -0
  52. package/docs/README.md +13 -2
  53. package/docs/deployment/HOSTINGER_DEPLOY.md +329 -0
  54. package/docs/features/laravel-nova-backend/ASSESSMENT-SUMMARY.md +96 -0
  55. package/docs/features/laravel-nova-backend/IMPLEMENTATION-PLAN.md +504 -0
  56. package/docs/features/laravel-nova-backend/credentials.ignore.md +34 -0
  57. package/docs/features/laravel-nova-backend/progress-tracker.json +184 -0
  58. package/docs/guides/no-cost-backend-implementation-plan.md +77 -0
  59. package/docs/guides/no-cost-firestore-google-drive-backend.md +60 -0
  60. package/docs/project-knowledge-base/01-system-overview.md +218 -0
  61. package/docs/project-knowledge-base/02-routes-pages-forms-users.md +346 -0
  62. package/docs/project-knowledge-base/03-tech-stack-modules-services.md +347 -0
  63. package/docs/project-knowledge-base/04-data-models-integrations.md +307 -0
  64. package/docs/project-knowledge-base/05-docs-corpus-inventory.md +193 -0
  65. package/docs/project-knowledge-base/06-operations-testing-legal-content.md +170 -0
  66. package/docs/project-knowledge-base/README.md +90 -0
  67. package/docs/project-profiles/native-update-capacitor-update-platform-project-profile-last-updated-2026-03-16.md +454 -0
  68. package/docs/project-profiles/native-update-capacitor-update-platform-project-profile-last-updated-2026-03-24.md +66 -0
  69. package/docs/project-profiles/native-update-capacitor-update-platform-project-profile-last-updated-2026-03-25.md +67 -0
  70. package/docs/seo-aeo-rules.json +3043 -0
  71. package/docs/tracking/seo-checklist-tracker.json +333 -0
  72. package/ios/Plugin/BackgroundUpdate/BackgroundUpdatePlugin.swift +50 -6
  73. package/ios/Plugin/LiveUpdate/LiveUpdatePlugin.swift +238 -8
  74. package/ios/Plugin/NativeUpdatePlugin.swift +8 -0
  75. package/ios/Plugin/Security/SecurityManager.swift +13 -14
  76. package/package.json +31 -32
  77. package/docs/play-console-rejection-rules.json +0 -428
@@ -11,6 +11,15 @@ class LiveUpdatePlugin {
11
11
  private var session: URLSession!
12
12
  private var downloadTask: URLSessionDownloadTask?
13
13
  private let securityManager = SecurityManager()
14
+
15
+ // UserDefaults keys for the A2 (boot-time re-verify) and A3
16
+ // (crash-loop auto-rollback) defenses. Mirrors the Android constants
17
+ // in LiveUpdatePlugin.kt so the two platforms stay in lockstep.
18
+ private static let activeBundleKey = "native_update_active_bundle"
19
+ private static let previousBundleKey = "native_update_previous_active_bundle"
20
+ private static let pendingBundleKey = "native_update_pending_verify_bundle"
21
+ private static let pendingAttemptsKey = "native_update_pending_verify_attempts"
22
+ private static let maxPendingAttempts = 2
14
23
 
15
24
  init(plugin: CAPPlugin) {
16
25
  self.plugin = plugin
@@ -156,8 +165,10 @@ class LiveUpdatePlugin {
156
165
  }
157
166
  }
158
167
 
159
- // Create bundle info
160
- let bundleInfo: [String: Any] = [
168
+ // Create bundle info. Persist the signature so the
169
+ // boot-time re-verify (verifyActiveBundleOnBoot) can run
170
+ // without needing to re-download.
171
+ var bundleInfo: [String: Any] = [
161
172
  "bundleId": bundleId,
162
173
  "version": version,
163
174
  "path": downloadedFile.path,
@@ -167,6 +178,9 @@ class LiveUpdatePlugin {
167
178
  "checksum": checksum,
168
179
  "verified": true
169
180
  ]
181
+ if let signature = call.getString("signature") {
182
+ bundleInfo["signature"] = signature
183
+ }
170
184
 
171
185
  // Save bundle info
172
186
  saveBundleInfo(bundleInfo)
@@ -467,7 +481,22 @@ class LiveUpdatePlugin {
467
481
  }
468
482
 
469
483
  private func setActiveBundle(_ bundleId: String) {
470
- UserDefaults.standard.set(bundleId, forKey: "native_update_active_bundle")
484
+ let defaults = UserDefaults.standard
485
+ let currentActive = defaults.string(forKey: Self.activeBundleKey)
486
+
487
+ defaults.set(bundleId, forKey: Self.activeBundleKey)
488
+
489
+ // Preserve the previous active bundle as a rollback target. Skip
490
+ // self-activation so we don't nuke a real previous-bundle pointer.
491
+ if let previous = currentActive, previous != bundleId {
492
+ defaults.set(previous, forKey: Self.previousBundleKey)
493
+ }
494
+
495
+ // Mark this activation as pending-verify. notifyAppReady() clears
496
+ // it; otherwise the next cold start counts against the bundle and
497
+ // eventually rolls back to the previous known-good bundle.
498
+ defaults.set(bundleId, forKey: Self.pendingBundleKey)
499
+ defaults.set(0, forKey: Self.pendingAttemptsKey)
471
500
  }
472
501
 
473
502
  private func clearAllBundles() {
@@ -542,7 +571,142 @@ class LiveUpdatePlugin {
542
571
  }
543
572
 
544
573
  private func markBundleAsVerified() {
545
- UserDefaults.standard.set(true, forKey: "native_update_current_bundle_verified")
574
+ // notifyAppReady() is the host app's "I booted clean on this bundle"
575
+ // signal. Clear the pending-verify state so cold starts after this
576
+ // no longer count against the bundle.
577
+ let defaults = UserDefaults.standard
578
+ defaults.set(true, forKey: "native_update_current_bundle_verified")
579
+ defaults.removeObject(forKey: Self.pendingBundleKey)
580
+ defaults.removeObject(forKey: Self.pendingAttemptsKey)
581
+ }
582
+
583
+ /// Called from NativeUpdatePlugin.load() on every cold start. Runs two
584
+ /// independent defenses, both silent when no OTA bundle is active:
585
+ ///
586
+ /// A2 — re-hash the active bundle and verify it still matches the
587
+ /// checksum stored at install time. If signature + publicKey are
588
+ /// configured, also re-verify the signature. Catches on-disk
589
+ /// tampering on jailbroken devices.
590
+ /// A3 — if the active bundle was never confirmed by notifyAppReady()
591
+ /// on the previous launch, treat this cold start as a failed
592
+ /// boot. After maxPendingAttempts failures, rollback to the
593
+ /// previous known-good bundle to break the crash loop.
594
+ ///
595
+ /// On any failure the method rolls back and fires an
596
+ /// updateStateChanged event with status "ROLLBACK" so the host app
597
+ /// and analytics layer can observe it.
598
+ func verifyActiveBundleOnBoot() {
599
+ let defaults = UserDefaults.standard
600
+
601
+ // A3: crash-loop detection.
602
+ if let pendingBundleId = defaults.string(forKey: Self.pendingBundleKey) {
603
+ let nextAttempt = defaults.integer(forKey: Self.pendingAttemptsKey) + 1
604
+ if nextAttempt > Self.maxPendingAttempts {
605
+ rollbackToPrevious(
606
+ reason: "auto-rollback after \(nextAttempt) failed cold starts",
607
+ fromBundleId: pendingBundleId
608
+ )
609
+ defaults.removeObject(forKey: Self.pendingBundleKey)
610
+ defaults.removeObject(forKey: Self.pendingAttemptsKey)
611
+ return
612
+ }
613
+ defaults.set(nextAttempt, forKey: Self.pendingAttemptsKey)
614
+ }
615
+
616
+ // A2: integrity re-verify. Only for non-default active bundles.
617
+ guard let activeBundleId = defaults.string(forKey: Self.activeBundleKey),
618
+ activeBundleId != "default" else {
619
+ return
620
+ }
621
+
622
+ guard let bundleInfo = getBundleInfoById(activeBundleId) else {
623
+ rollbackToPrevious(
624
+ reason: "active bundle info missing from UserDefaults",
625
+ fromBundleId: activeBundleId
626
+ )
627
+ return
628
+ }
629
+
630
+ guard let bundlePath = bundleInfo["path"] as? String, !bundlePath.isEmpty,
631
+ let expectedChecksum = bundleInfo["checksum"] as? String, !expectedChecksum.isEmpty else {
632
+ return
633
+ }
634
+
635
+ let bundleURL = URL(fileURLWithPath: bundlePath)
636
+ guard FileManager.default.fileExists(atPath: bundlePath) else {
637
+ rollbackToPrevious(
638
+ reason: "bundle file missing on disk",
639
+ fromBundleId: activeBundleId
640
+ )
641
+ return
642
+ }
643
+
644
+ do {
645
+ let actualChecksum = try calculateChecksum(for: bundleURL)
646
+ if actualChecksum != expectedChecksum {
647
+ rollbackToPrevious(
648
+ reason: "bundle checksum mismatch on boot",
649
+ fromBundleId: activeBundleId
650
+ )
651
+ return
652
+ }
653
+ } catch {
654
+ rollbackToPrevious(
655
+ reason: "bundle checksum computation failed: \(error.localizedDescription)",
656
+ fromBundleId: activeBundleId
657
+ )
658
+ return
659
+ }
660
+
661
+ // Signature re-verify when both a signature and a public key are
662
+ // available. Absence of either is treated as "not configured" and
663
+ // skipped — Phase A1 already enforces fail-closed at download time.
664
+ if let signature = bundleInfo["signature"] as? String, !signature.isEmpty,
665
+ let publicKey = (config?["security"] as? [String: Any])?["publicKey"] as? String,
666
+ !publicKey.isEmpty {
667
+ do {
668
+ let bytes = try Data(contentsOf: bundleURL)
669
+ if !securityManager.verifySignature(
670
+ data: bytes,
671
+ signature: signature,
672
+ publicKeyString: publicKey
673
+ ) {
674
+ rollbackToPrevious(
675
+ reason: "bundle signature mismatch on boot",
676
+ fromBundleId: activeBundleId
677
+ )
678
+ }
679
+ } catch {
680
+ rollbackToPrevious(
681
+ reason: "bundle signature check failed: \(error.localizedDescription)",
682
+ fromBundleId: activeBundleId
683
+ )
684
+ }
685
+ }
686
+ }
687
+
688
+ private func rollbackToPrevious(reason: String, fromBundleId: String) {
689
+ let defaults = UserDefaults.standard
690
+ let previous = defaults.string(forKey: Self.previousBundleKey)
691
+ if let previous = previous {
692
+ defaults.set(previous, forKey: Self.activeBundleKey)
693
+ } else {
694
+ defaults.removeObject(forKey: Self.activeBundleKey)
695
+ }
696
+ defaults.removeObject(forKey: Self.previousBundleKey)
697
+
698
+ stateChangeListener?([
699
+ "status": "ROLLBACK",
700
+ "bundleId": fromBundleId,
701
+ "rolledBackTo": previous ?? "default",
702
+ "reason": reason
703
+ ])
704
+ }
705
+
706
+ private func getBundleInfoById(_ bundleId: String) -> [String: Any]? {
707
+ let defaults = UserDefaults.standard
708
+ let bundles = defaults.dictionary(forKey: "native_update_bundles") ?? [:]
709
+ return bundles[bundleId] as? [String: Any]
546
710
  }
547
711
 
548
712
  // MARK: - Bundle Extraction and WebView Configuration
@@ -574,14 +738,80 @@ class LiveUpdatePlugin {
574
738
  }
575
739
  }
576
740
 
741
+ /// Hard cap on total uncompressed size of an OTA bundle. Protects
742
+ /// against zip-bombs (a small zip that expands to gigabytes and fills
743
+ /// the device). Chosen as 5x the default 100MB downloaded-bundle limit
744
+ /// — enough headroom for typical web-app bundles after minification
745
+ /// reverses, below a threshold that would be hostile on mobile.
746
+ private static let maxUncompressedBundleBytes: UInt64 = 500 * 1024 * 1024
747
+
577
748
  private func extractZipBundle(from zipUrl: URL, to destinationUrl: URL) throws {
578
- // Ensure destination directory exists
749
+ // Ensure destination directory exists.
579
750
  try FileManager.default.createDirectory(at: destinationUrl, withIntermediateDirectories: true, attributes: nil)
580
751
 
581
- // Extract the ZIP archive using ZIPFoundation
752
+ // Inspect every entry before extracting anything. Three defenses:
753
+ // 1. zip-slip — an entry whose destination path resolves outside
754
+ // the intended directory would let a malicious bundle write
755
+ // over any file in the app sandbox (e.g. overwriting
756
+ // built-in code) when unzipItem later runs.
757
+ // 2. symlink attack — symlinks in the archive can redirect
758
+ // subsequent writes outside the sandbox even if the entry
759
+ // itself resolves cleanly.
760
+ // 3. zip-bomb — sum of uncompressed sizes capped; refuses to
761
+ // extract an archive that would exhaust device storage.
762
+ guard let archive = Archive(url: zipUrl, accessMode: .read) else {
763
+ throw NSError(domain: "LiveUpdatePlugin", code: 7, userInfo: [
764
+ NSLocalizedDescriptionKey: "Could not open bundle archive for inspection"
765
+ ])
766
+ }
767
+
768
+ let canonicalDestination = destinationUrl
769
+ .standardizedFileURL
770
+ .resolvingSymlinksInPath()
771
+ .path
772
+ let destinationPrefix = canonicalDestination.hasSuffix("/")
773
+ ? canonicalDestination
774
+ : canonicalDestination + "/"
775
+
776
+ var totalUncompressed: UInt64 = 0
777
+
778
+ for entry in archive {
779
+ if entry.type == .symlink {
780
+ throw NSError(domain: "LiveUpdatePlugin", code: 8, userInfo: [
781
+ NSLocalizedDescriptionKey: "Bundle contains symlink entry (refused): \(entry.path)"
782
+ ])
783
+ }
784
+
785
+ if entry.path.hasPrefix("/") {
786
+ throw NSError(domain: "LiveUpdatePlugin", code: 9, userInfo: [
787
+ NSLocalizedDescriptionKey: "Bundle entry has absolute path (refused): \(entry.path)"
788
+ ])
789
+ }
790
+
791
+ let entryURL = destinationUrl
792
+ .appendingPathComponent(entry.path)
793
+ .standardizedFileURL
794
+ let entryPath = entryURL.resolvingSymlinksInPath().path
795
+
796
+ if entryPath != canonicalDestination && !entryPath.hasPrefix(destinationPrefix) {
797
+ throw NSError(domain: "LiveUpdatePlugin", code: 10, userInfo: [
798
+ NSLocalizedDescriptionKey: "Bundle entry escapes destination (zip-slip refused): \(entry.path)"
799
+ ])
800
+ }
801
+
802
+ let (newTotal, overflow) = totalUncompressed.addingReportingOverflow(entry.uncompressedSize)
803
+ if overflow || newTotal > Self.maxUncompressedBundleBytes {
804
+ throw NSError(domain: "LiveUpdatePlugin", code: 11, userInfo: [
805
+ NSLocalizedDescriptionKey: "Bundle exceeds maximum uncompressed size (\(Self.maxUncompressedBundleBytes) bytes) — possible zip-bomb"
806
+ ])
807
+ }
808
+ totalUncompressed = newTotal
809
+ }
810
+
811
+ // All entries passed inspection — safe to extract.
582
812
  try FileManager.default.unzipItem(at: zipUrl, to: destinationUrl)
583
813
 
584
- // Verify extraction succeeded by checking for index.html
814
+ // Verify extraction succeeded by checking for index.html.
585
815
  let indexPath = destinationUrl.appendingPathComponent("index.html")
586
816
  guard FileManager.default.fileExists(atPath: indexPath.path) else {
587
817
  throw NSError(domain: "LiveUpdatePlugin", code: 6, userInfo: [
@@ -589,7 +819,7 @@ class LiveUpdatePlugin {
589
819
  ])
590
820
  }
591
821
 
592
- // Clean up the ZIP file after successful extraction
822
+ // Clean up the ZIP file after successful extraction.
593
823
  try? FileManager.default.removeItem(at: zipUrl)
594
824
  }
595
825
 
@@ -33,6 +33,14 @@ public class NativeUpdatePlugin: CAPPlugin {
33
33
  appUpdatePlugin.setEventListener { [weak self] eventName, data in
34
34
  self?.notifyListeners(eventName, data: data)
35
35
  }
36
+
37
+ // Boot-time integrity re-verify + crash-loop auto-rollback. Runs
38
+ // before any host-app code touches the WebView so a tampered or
39
+ // crash-looping bundle never gets a chance to load. Signature
40
+ // re-verify runs again after configure() when the publicKey
41
+ // arrives from the host app; checksum re-verify here is
42
+ // sufficient to catch most tampering.
43
+ liveUpdatePlugin.verifyActiveBundleOnBoot()
36
44
  }
37
45
 
38
46
  @objc func initialize(_ call: CAPPluginCall) {
@@ -25,7 +25,8 @@ class SecurityManager {
25
25
  "pins": getCertificatePins()
26
26
  ],
27
27
  "validateInputs": config?["validateInputs"] as? Bool ?? true,
28
- "secureStorage": config?["secureStorage"] as? Bool ?? true
28
+ // Hard-coded true since v2 — secure storage is no longer opt-out.
29
+ "secureStorage": true
29
30
  ]
30
31
  }
31
32
 
@@ -142,21 +143,17 @@ class SecurityManager {
142
143
  return digest.map { String(format: "%02x", $0) }.joined()
143
144
  }
144
145
 
146
+ /// Writes always land in the Keychain since v2. The old
147
+ /// `secureStorage: false` opt-out was removed because the default
148
+ /// ran in plaintext UserDefaults unless a host app explicitly opted
149
+ /// in — which meant most integrations stored API keys in a trivially
150
+ /// readable location. See matching change in SecurityManager.kt.
145
151
  func saveSecureData(key: String, value: String) {
146
- if isSecureStorageEnabled() {
147
- keychain.set(value, forKey: key)
148
- } else {
149
- // Fallback to UserDefaults (not recommended)
150
- UserDefaults.standard.set(value, forKey: key)
151
- }
152
+ keychain.set(value, forKey: key)
152
153
  }
153
-
154
+
154
155
  func getSecureData(key: String) -> String? {
155
- if isSecureStorageEnabled() {
156
- return keychain.string(forKey: key)
157
- } else {
158
- return UserDefaults.standard.string(forKey: key)
159
- }
156
+ return keychain.string(forKey: key)
160
157
  }
161
158
 
162
159
  func validatePath(_ path: String) -> Bool {
@@ -189,8 +186,10 @@ class SecurityManager {
189
186
  return config?["enforceHttps"] as? Bool ?? true
190
187
  }
191
188
 
189
+ /// Unconditionally true since v2. Kept so `getSecurityInfo()` still
190
+ /// reports the capability to callers.
192
191
  func isSecureStorageEnabled() -> Bool {
193
- return config?["secureStorage"] as? Bool ?? true
192
+ return true
194
193
  }
195
194
 
196
195
  func isInputValidationEnabled() -> Bool {
package/package.json CHANGED
@@ -1,18 +1,16 @@
1
1
  {
2
2
  "name": "native-update",
3
- "version": "1.4.8",
3
+ "version": "2.0.0",
4
4
  "engines": {
5
- "node": ">=24.13.0"
5
+ "node": ">=18.0.0"
6
6
  },
7
- "description": "Foundation package for building a comprehensive update system for Capacitor apps. Provides architecture and interfaces but requires backend implementation.",
7
+ "description": "Capacitor update plugin with live updates, app update checks, app reviews, and support for HTTP or Firestore-backed release delivery.",
8
8
  "type": "module",
9
9
  "main": "dist/plugin.cjs.js",
10
10
  "module": "dist/esm/index.js",
11
11
  "types": "dist/esm/index.d.ts",
12
12
  "unpkg": "dist/plugin.js",
13
- "bin": {
14
- "native-update": "./cli/index.js"
15
- },
13
+ "bin": "./cli/index.js",
16
14
  "files": [
17
15
  "android/src/main/",
18
16
  "android/build.gradle",
@@ -36,7 +34,7 @@
36
34
  "license": "MIT",
37
35
  "repository": {
38
36
  "type": "git",
39
- "url": "https://nativeupdate.aoneahsan.com"
37
+ "url": "git+https://github.com/aoneahsan/native-update.git"
40
38
  },
41
39
  "bugs": {
42
40
  "url": "https://nativeupdate.aoneahsan.com/contact"
@@ -59,18 +57,19 @@
59
57
  "android",
60
58
  "hybrid"
61
59
  ],
62
- "packageManager": "yarn@1.22.22",
60
+ "packageManager": "yarn@4.10.3",
63
61
  "scripts": {
64
- "build": "yarn run clean && yarn run tsc && rollup -c rollup.config.js",
65
- "build:prod": "yarn run clean && yarn run tsc && NODE_ENV=production rollup -c rollup.config.js",
62
+ "build": "rimraf ./dist && tsc && rollup -c rollup.config.js",
63
+ "build:prod": "rimraf ./dist && tsc && NODE_ENV=production rollup -c rollup.config.js",
66
64
  "clean": "rimraf ./dist",
67
65
  "tsc": "tsc",
68
66
  "watch": "tsc --watch",
69
67
  "lint": "eslint . --ext ts",
70
68
  "prettier": "prettier --write .",
71
- "prepublishOnly": "yarn run build:prod",
69
+ "prepublishOnly": "yarn build:prod",
72
70
  "swiftlint": "cd ios && swiftlint lint --fix --format --path Plugin --verbose",
73
71
  "test": "vitest",
72
+ "test:run": "vitest --run",
74
73
  "test:ui": "vitest --ui",
75
74
  "test:coverage": "vitest --coverage"
76
75
  },
@@ -78,32 +77,32 @@
78
77
  "archiver": "^7.0.1",
79
78
  "chalk": "^5.6.2",
80
79
  "commander": "^14.0.3",
81
- "express": "^5.2.1",
82
- "ora": "^9.1.0",
80
+ "ora": "^9.3.0",
83
81
  "prompts": "^2.4.2"
84
82
  },
85
83
  "devDependencies": {
86
- "@capacitor/android": "^8.0.2",
87
- "@capacitor/app": "^8.0.0",
88
- "@capacitor/core": "^8.0.2",
89
- "@capacitor/device": "^8.0.0",
90
- "@capacitor/filesystem": "^8.1.0",
91
- "@capacitor/ios": "^8.0.2",
92
- "@capacitor/preferences": "^8.0.0",
84
+ "@capacitor/android": "^8.3.0",
85
+ "@capacitor/app": "^8.1.0",
86
+ "@capacitor/core": "^8.3.0",
87
+ "@capacitor/device": "^8.0.2",
88
+ "@capacitor/filesystem": "^8.1.2",
89
+ "@capacitor/ios": "^8.3.0",
90
+ "@capacitor/preferences": "^8.0.1",
93
91
  "@rollup/plugin-json": "^6.1.0",
94
92
  "@rollup/plugin-node-resolve": "^16.0.3",
95
- "@rollup/plugin-terser": "^0.4.4",
96
- "@types/node": "^25.2.0",
97
- "@typescript-eslint/eslint-plugin": "^8.54.0",
98
- "@typescript-eslint/parser": "^8.54.0",
99
- "@vitest/ui": "^4.0.18",
100
- "eslint": "^9.39.2",
101
- "happy-dom": "^20.4.0",
102
- "prettier": "^3.8.1",
103
- "rimraf": "^6.1.2",
104
- "rollup": "^4.57.1",
105
- "typescript": "^5.9.3",
106
- "vitest": "^4.0.18"
93
+ "@rollup/plugin-terser": "^1.0.0",
94
+ "@types/node": "^25.6.0",
95
+ "@typescript-eslint/eslint-plugin": "^8.58.2",
96
+ "@typescript-eslint/parser": "^8.58.2",
97
+ "@vitest/ui": "^4.1.4",
98
+ "eslint": "^10.2.0",
99
+ "express": "^5.2.1",
100
+ "happy-dom": "^20.9.0",
101
+ "prettier": "^3.8.3",
102
+ "rimraf": "^6.1.3",
103
+ "rollup": "^4.60.1",
104
+ "typescript": "^6.0.2",
105
+ "vitest": "^4.1.4"
107
106
  },
108
107
  "peerDependencies": {
109
108
  "@capacitor/core": "^8.0.1"