Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 43 additions & 37 deletions .github/workflows/validate-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,54 @@ on:
branches: [main]

env:
IOS_PLATFORM: iOS Simulator
IOS_DEVICE: iPhone 15
IOS_VERSION: 17.4
XCODE_VERSION: "26.0"

jobs:
# TODO: enable when swiftlint supports a linux binary in its artifact bundle
# lint:
# name: Lint
# timeout-minutes: 2
# runs-on: ubuntu-latest
# defaults:
# run:
# working-directory: ./Vault
#
# steps:
# - uses: actions/checkout@v4
# - name: Lint Sources
# run: make lint
lint:
name: Lint
timeout-minutes: 2
runs-on: macos-26
defaults:
run:
working-directory: ./Vault

# build:
# name: Build
# timeout-minutes: 5
# runs-on: macos-14
#
# steps:
# - uses: actions/checkout@v4
# - run: sudo xcode-select -s /Applications/Xcode_15.3.app/Contents/Developer
# - name: Build App
# run: xcodebuild -workspace Vault.xcworkspace -scheme VaultApp -destination 'platform=${{ env.IOS_PLATFORM }},name=${{ env.IOS_DEVICE }},OS=${{ env.IOS_VERSION }}' -skipMacroValidation -quiet build
#
steps:
- uses: actions/checkout@v4
- run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app/Contents/Developer

- name: Cache SPM Dependencies
uses: actions/cache@v4
with:
path: |
~/Library/Caches/org.swift.swiftpm/repositories
Vault/.build
key: ${{ runner.os }}-spm-lint-${{ hashFiles('Vault/Package.resolved') }}
restore-keys: |
${{ runner.os }}-spm-lint-

- name: Check Linting & Formatting
run: make lint

# Don't test for now to save CI time
# test:
# name: Test
# timeout-minutes: 30
# runs-on: ubuntu-latest
# # runs-on: macos-14
# timeout-minutes: 10
# runs-on: macos-26
#
# steps:
# # We currently don't run tests in CI due to cost.
# # This can be re-enabled when the repo is made public!
# - name: Skip Testing
# run: true
# - uses: actions/checkout@v4
# - run: sudo xcode-select -s /Applications/Xcode_15.3.app/Contents/Developer
# - name: Run all Tests
# run: xcodebuild -workspace Vault.xcworkspace -scheme CI_iOS -destination 'platform=${{ env.IOS_PLATFORM }},name=${{ env.IOS_DEVICE }},OS=${{ env.IOS_VERSION }}' -skipMacroValidation -quiet test
# - run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app/Contents/Developer
#
# - name: Cache SPM Dependencies
# uses: actions/cache@v4
# with:
# path: ~/Library/Caches/org.swift.swiftpm/repositories
# key: ${{ runner.os }}-spm-${{ hashFiles('Vault/Package.resolved') }}
# restore-keys: |
# ${{ runner.os }}-spm-
#
# - name: Clear SPM Artifacts
# run: rm -rf ~/Library/Caches/org.swift.swiftpm/artifacts
#
# - name: Test CI_macOS
# run: xcodebuild test -workspace Vault.xcworkspace -scheme CI_macOS -skipMacroValidation -skipPackagePluginValidation
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ There is purposely no automatic or online backup, so you never need to worry abo
There's a few ways that you can use Vault to store your data:

1. Super secret data

- Store data you really don't want to be accessed in encrypted notes, like cryptocurrency private keys. Hide them and add a killcode so, if under duress, you can wipe them with plausible deniability. Restore from a backup when you get home.

2. Store OTP codes
Expand Down
3 changes: 3 additions & 0 deletions Vault/.swiftpm/xcode/xcshareddata/xcschemes/CI_iOS.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
reference = "container:TestPlans/CI/iOSAllTests.xctestplan"
default = "YES">
</TestPlanReference>
<TestPlanReference
reference = "container:../macOS_SupportedTests.xctestplan">
</TestPlanReference>
</TestPlans>
<Testables>
<TestableReference
Expand Down
59 changes: 59 additions & 0 deletions Vault/.swiftpm/xcode/xcshareddata/xcschemes/CI_macOS.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference
reference = "container:TestPlans/CI/macOS_SupportedTests.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CryptoEngineTests"
BuildableName = "CryptoEngineTests"
BlueprintName = "CryptoEngineTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
2 changes: 2 additions & 0 deletions Vault/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ benchmark-keygen:

.PHONY: format
format:
swift package plugin --allow-writing-to-package-directory swiftlint --fix --quiet ./Sources
swift package --allow-writing-to-package-directory format --sources=./

.PHONY: lint
lint:
swift package plugin swiftlint --strict --quiet ./Sources
swift package --allow-writing-to-package-directory format --lint --sources=./

.PHONY: clean
Expand Down
12 changes: 5 additions & 7 deletions Vault/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,20 +276,18 @@ let package = Package(
capability: .command(
intent: .custom(
verb: "format",
description: "Formats Swift source files using swiftformat and swiftlint"
description: "Formats Swift source files using swiftformat only"
),
permissions: [.writeToPackageDirectory(reason: "Format source code")]
)
),
dependencies: [
"swiftformat",
]
),
.binaryTarget(
name: "swiftformat",
url: "https://github.com/nicklockwood/SwiftFormat/releases/download/\(swiftFormatVersion)/swiftformat.artifactbundle.zip",
checksum: swiftFormatChecksum
),
.binaryTarget(
name: "swiftlint",
url: "https://github.com/realm/SwiftLint/releases/download/\(swiftLintVersion)/SwiftLintBinary-macos.artifactbundle.zip",
checksum: swiftLintChecksum
),
]
)
48 changes: 0 additions & 48 deletions Vault/Plugins/FormatLint/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ struct FormatLintPlugin: CommandPlugin {
let formatLintContext = try makeFormatLintContext(context: context, arguments: arguments)

let start = Date()
// Lint before formatting so any formatting that results in lint errors is surfaced and we can address it.
try formatLintContext.runSwiftLint()
try formatLintContext.runSwiftFormat()
let end = Date()
let elapsed = end.timeIntervalSince(start)
Expand All @@ -32,7 +30,6 @@ struct FormatLintPlugin: CommandPlugin {
.format
}
return try FormatLintContext(
swiftlint: context.tool(named: "swiftlint"),
swiftformat: context.tool(named: "swiftformat"),
workDirectory: context.pluginWorkDirectoryURL,
swiftSourcesDirectory: swiftSources,
Expand All @@ -42,40 +39,12 @@ struct FormatLintPlugin: CommandPlugin {
}

struct FormatLintContext {
var swiftlint: PluginContext.Tool
var swiftformat: PluginContext.Tool
var workDirectory: URL
var swiftSourcesDirectory: String
var action: Action
}

extension FormatLintContext {
func makeSwiftLintArgs() -> [String] {
var swiftLintArguments = [String]()
swiftLintArguments += ["--cache-path", workDirectory.appending(path: "swiftlint.cache").path()]
swiftLintArguments += ["--quiet"]
switch action {
case .format:
swiftLintArguments += ["--fix"]
case .lint:
swiftLintArguments += ["--strict"]
}
swiftLintArguments += [swiftSourcesDirectory]
return swiftLintArguments
}

func runSwiftLint() throws {
print("🔍 swiftlint: \(action.swiftLintVerb.lowercased())")
try runProcess(
url: swiftlint.url,
arguments: makeSwiftLintArgs(),
exitCodeHandler: swiftLintExitCodeHandler(code:)
)
}
}

// MARK: - swiftformat

extension FormatLintContext {
func makeSwiftFormatArgs() -> [String] {
var swiftFormatArguments = [String]()
Expand Down Expand Up @@ -110,13 +79,6 @@ enum Action {
case .lint: "Checking"
}
}

var swiftLintVerb: String {
switch self {
case .format: "Fixing"
case .lint: "Linting"
}
}
}

enum CommandError: Error {
Expand Down Expand Up @@ -149,13 +111,3 @@ func swiftFormatExitCodeHandler(code: Int32) throws {
throw CommandError.unknownError(exitCode: code)
}
}

func swiftLintExitCodeHandler(code: Int32) throws {
switch code {
case 0:
print("☑️ swiftlint done")
default:
print("❌ swiftlint failure")
throw CommandError.exitWithError
}
}
3 changes: 3 additions & 0 deletions Vault/Sources/TestHelpers/PDFDocument+Snapshot.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#if canImport(UIKit)
import Foundation
import PDFKit
import SnapshotTesting
Expand Down Expand Up @@ -36,3 +37,5 @@ extension PDFDocument {
}
}
}

#endif
4 changes: 2 additions & 2 deletions Vault/Sources/TestHelpers/SuspendForever.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import Foundation
/// This is achieved by awaiting on a stream that never yields any values.
///
/// - throws: `CancellationError` if the task this is in is cancelled.
public func suspendForever() async throws -> Never {
public func suspendForever() async throws {
let stream = AsyncStream<Never> { _ in }
for await _ in stream {
return fatalError("Unreachable")
fatalError("Unreachable")
}
// The stream will only exit without a value in the case that it's been cancelled.
// Propagate the cancellation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public final class BackupImportFlowViewModel {
case let .readyToImport(applicationPayload):
payloadState = .ready(applicationPayload, UUID())
}
} catch let error as LocalizedError {
} catch let error as any LocalizedError {
payloadState = .error(.init(localizedError: error))
} catch {
payloadState = .error(PresentationError(
Expand Down Expand Up @@ -140,7 +140,7 @@ public final class BackupImportFlowViewModel {
try await dataModel.importOverride(payload: payload)
}
importState = .success
} catch let error as LocalizedError {
} catch let error as any LocalizedError {
importState = .error(.init(localizedError: error))
} catch {
importState = .error(PresentationError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public final class BackupKeyDecryptorViewModel {
)
decryptionKeyState = .validDecryptionKey
decryptedVaultSubject.send(vaultApplicationPayload)
} catch let error as LocalizedError {
} catch let error as any LocalizedError {
decryptionKeyState = .error(.init(localizedError: error))
} catch {
decryptionKeyState = .error(PresentationError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public final class EncryptedItemDetailViewModel {
case let .decrypted(item):
state = .decrypted(item, generatedPassword)
}
} catch let localized as LocalizedError {
} catch let localized as any LocalizedError {
state = .decryptionError(PresentationError(localizedError: localized))
} catch {
state = .decryptionError(PresentationError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public protocol OTPCodeTimerUpdater: AnyObject {

/// Controller for producing timers for a given code, according to a clock.
@MainActor
public final class OTPCodeTimerUpdaterImpl: OTPCodeTimerUpdater, Sendable {
public final class OTPCodeTimerUpdaterImpl: OTPCodeTimerUpdater {
private let timerStateSubject: CurrentValueSubject<OTPCodeTimerState, Never>
private let period: UInt64
private let timerTask = SharedMutex<Task<Void, any Error>?>(nil)
Expand Down
2 changes: 1 addition & 1 deletion Vault/Sources/VaultFeed/Storage/VaultDataModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import VaultKeygen
/// This is isolated to the main actor for the purposes of UI interop.
@MainActor
@Observable
public final class VaultDataModel: Sendable {
public final class VaultDataModel {
public enum State {
case base, loaded, loading
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,14 @@ extension PersistedLocalVaultStore: VaultStoreReader {
return .true
} else {
let searchingTagIds = tags.map(\.id).reducedToSet()
// Returns the number of tags matched by this item.
let tagsMatchingSearch = #Expression<PersistedVaultItem, Int> { item in
item.tags.filter { tag in
searchingTagIds.contains(tag.id)
}.count
}
// Performs an "AND" query by checking if the number of tags matched equals
// the number of tags we are searching for.
let searchingTagsCount = searchingTagIds.count
// Performs an "AND" query by checking if all searched tags are present in the item's tags.
// We convert the set to an array to use filter, which is supported in SwiftData predicates.
// An item matches if the count of its tags that match our search equals the search count.
let searchingTagsArray = Array(searchingTagIds)
return #Predicate<PersistedVaultItem> { item in
tagsMatchingSearch.evaluate(item) == searchingTagsCount
item.tags.count(where: { tag in
searchingTagsArray.contains(tag.id)
}) == searchingTagsArray.count
}
}
}
Expand Down
Loading