#!/usr/bin/env bash
|
set -euo pipefail
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
DATA_LAYER="$ROOT_DIR/Apptag/DataLayer.swift"
|
KEYCHAIN_APP="/System/Library/CoreServices/Applications/Keychain Access.app"
|
|
rg -Fq 'URL(fileURLWithPath: "/System/Library/CoreServices/Applications")' "$DATA_LAYER"
|
rg -Fq '"/System/Library/CoreServices/Applications/"' "$DATA_LAYER"
|
|
swift - "$KEYCHAIN_APP" <<'SWIFT'
|
import Foundation
|
import CoreServices
|
|
let appPath = CommandLine.arguments[1]
|
let appURL = URL(fileURLWithPath: appPath)
|
|
func fail(_ message: String) -> Never {
|
FileHandle.standardError.write(Data("FAIL: \(message)\n".utf8))
|
exit(1)
|
}
|
|
guard FileManager.default.fileExists(atPath: appPath) else {
|
fail("Keychain Access app is missing at \(appPath)")
|
}
|
|
let indexedSearchPaths = [
|
"/Applications",
|
"/System/Applications",
|
"/System/Library/CoreServices/Applications",
|
"/System/Cryptexes/App/System/Applications",
|
"/System/Volumes/Preboot/Cryptexes/App/System/Applications",
|
FileManager.default.homeDirectoryForCurrentUser
|
.appendingPathComponent("Applications")
|
.path
|
]
|
|
guard indexedSearchPaths.contains(where: { appPath.hasPrefix($0 + "/") }) else {
|
fail("Keychain Access is not under a configured app search path")
|
}
|
|
var names: [String] = [
|
appURL.deletingPathExtension().lastPathComponent
|
]
|
|
if let item = MDItemCreate(nil, appPath as CFString),
|
let displayName = MDItemCopyAttribute(item, kMDItemDisplayName) as? String {
|
names.append(displayName)
|
}
|
|
if let bundle = Bundle(url: appURL),
|
let loctableURL = bundle.url(forResource: "InfoPlist", withExtension: "loctable"),
|
let loctable = NSDictionary(contentsOf: loctableURL) as? [String: Any],
|
let zhTable = loctable["zh_CN"] as? [String: Any] {
|
if let value = zhTable["CFBundleDisplayName"] as? String { names.append(value) }
|
if let value = zhTable["CFBundleName"] as? String { names.append(value) }
|
}
|
|
func normalize(_ value: String) -> String {
|
value
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
.folding(options: [.caseInsensitive, .diacriticInsensitive], locale: .current)
|
.lowercased()
|
}
|
|
func pinyinCandidates(for value: String) -> [String] {
|
let mutable = NSMutableString(string: value)
|
CFStringTransform(mutable, nil, kCFStringTransformToLatin, false)
|
CFStringTransform(mutable, nil, kCFStringTransformStripCombiningMarks, false)
|
let spaced = normalize(mutable as String)
|
.components(separatedBy: CharacterSet.alphanumerics.inverted)
|
.filter { !$0.isEmpty }
|
.joined(separator: " ")
|
let compact = spaced.replacingOccurrences(of: " ", with: "")
|
let initials = spaced
|
.split(separator: " ")
|
.compactMap(\.first)
|
.map(String.init)
|
.joined()
|
var seen = Set<String>()
|
return [spaced, compact, initials].filter { !$0.isEmpty && seen.insert($0).inserted }
|
}
|
|
let searchable = names.flatMap { name in
|
[normalize(name)] + pinyinCandidates(for: name)
|
}
|
|
let queries = ["keychain", "钥匙串", "yaoshichuan", "ysc"]
|
for query in queries {
|
let token = normalize(query)
|
guard searchable.contains(where: { $0 == token || $0.hasPrefix(token) || $0.contains(token) }) else {
|
fail("query \(query) did not match Keychain Access candidates: \(searchable)")
|
}
|
}
|
|
print("PASS Keychain Access indexed search candidates: \(names.joined(separator: " | "))")
|
SWIFT
|