diff options
| author | Christian Cleberg <[email protected]> | 2026-04-24 12:38:38 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-24 12:38:38 -0500 |
| commit | d0b3b7a15b59266b4ae4262fdb0364245092c17c (patch) | |
| tree | 5adb0df9963caf78aa98fa17adfb2c175291ded9 /DomainDigCLI.swift | |
| parent | b35f1b432fc24fbab1fa05593c9a0bca7e4d95a6 (diff) | |
| download | domain-dig-d0b3b7a15b59266b4ae4262fdb0364245092c17c.tar.gz domain-dig-d0b3b7a15b59266b4ae4262fdb0364245092c17c.tar.bz2 domain-dig-d0b3b7a15b59266b4ae4262fdb0364245092c17c.zip | |
feat(v3.7.0): add timeline and advanced diffing system
- introduce TimelineView for historical snapshots
- allow comparison between any two snapshots
- implement DiffService for structured domain diffs
- improve diff visualization with clear change indicators
- add navigation across changes
- optimize history loading for performance
- extend CLI and export to support timeline data
Diffstat (limited to 'DomainDigCLI.swift')
| -rw-r--r-- | DomainDigCLI.swift | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/DomainDigCLI.swift b/DomainDigCLI.swift index bb9dd80..da289a9 100644 --- a/DomainDigCLI.swift +++ b/DomainDigCLI.swift @@ -17,6 +17,16 @@ struct DomainDigCLI { return } + if arguments.first == "history" { + runHistoryCommand(arguments: Array(arguments.dropFirst()), wantsJSON: wantsJSON) + return + } + + if arguments.first == "diff" { + runDiffCommand(arguments: Array(arguments.dropFirst()), wantsJSON: wantsJSON) + return + } + if arguments.first == "monitor" { await runMonitorCommand(wantsJSON: wantsJSON) return @@ -216,6 +226,10 @@ struct DomainDigCLI { isPartialSnapshot: snapshot.isPartialSnapshot, validationIssues: snapshot.validationIssues, totalLookupDurationMs: snapshot.totalLookupDurationMs, + snapshotIndex: snapshot.snapshotIndex, + previousSnapshotID: snapshot.previousSnapshotID, + changeCount: snapshot.changeCount, + severitySummary: snapshot.severitySummary, dnsSections: snapshot.dnsSections, dnsError: snapshot.dnsError, availabilityResult: snapshot.availabilityResult, @@ -265,6 +279,107 @@ struct DomainDigCLI { DomainDataPortabilityService.loadHistoryEntries() } + private static func runHistoryCommand(arguments: [String], wantsJSON: Bool) { + guard let domain = arguments.first(where: { !$0.hasPrefix("-") }) else { + fputs("usage: domaindig history <domain> [--json]\n", stderr) + Foundation.exit(1) + } + + let entries = loadHistoryEntries() + .filter { $0.domain.caseInsensitiveCompare(domain) == .orderedSame } + .sorted { $0.timestamp > $1.timestamp } + + if wantsJSON { + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + encoder.dateEncodingStrategy = .iso8601 + let payload = entries.map { entry in + [ + "id": entry.id.uuidString, + "timestamp": ISO8601DateFormatter().string(from: entry.timestamp), + "changeSummary": entry.changeSummary?.message ?? "No change summary", + "changeCount": "\(entry.changeCount)", + "severity": entry.severitySummary?.title ?? "N/A" + ] + } + if let data = try? JSONSerialization.data(withJSONObject: payload, options: [.prettyPrinted, .sortedKeys]) { + FileHandle.standardOutput.write(data) + FileHandle.standardOutput.write(Data([0x0A])) + return + } + } + + let lines = entries.map { entry in + [ + entry.id.uuidString, + entry.timestamp.formatted(date: .abbreviated, time: .shortened), + entry.changeSummary?.message ?? "No change summary" + ].joined(separator: " | ") + } + + FileHandle.standardOutput.write(Data((lines.isEmpty ? "No history found.\n" : lines.joined(separator: "\n") + "\n").utf8)) + } + + private static func runDiffCommand(arguments: [String], wantsJSON: Bool) { + guard let domain = arguments.first(where: { !$0.hasPrefix("-") }) else { + fputs("usage: domaindig diff <domain> --from <id> --to <id> [--json]\n", stderr) + Foundation.exit(1) + } + + guard let fromID = optionValue(named: "--from", in: arguments), + let toID = optionValue(named: "--to", in: arguments), + let fromUUID = UUID(uuidString: fromID), + let toUUID = UUID(uuidString: toID) else { + fputs("domaindig diff: --from and --to must be valid snapshot IDs\n", stderr) + Foundation.exit(1) + } + + let entries = loadHistoryEntries().filter { $0.domain.caseInsensitiveCompare(domain) == .orderedSame } + guard let fromEntry = entries.first(where: { $0.id == fromUUID }), + let toEntry = entries.first(where: { $0.id == toUUID }) else { + fputs("domaindig diff: snapshots not found for domain\n", stderr) + Foundation.exit(1) + } + + let orderedEntries = [fromEntry, toEntry].sorted { $0.timestamp < $1.timestamp } + let diff = DiffService.compare(from: orderedEntries[0].snapshot, to: orderedEntries[1].snapshot) + + if wantsJSON { + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + encoder.dateEncodingStrategy = .iso8601 + if let data = try? encoder.encode(diff) { + FileHandle.standardOutput.write(data) + FileHandle.standardOutput.write(Data([0x0A])) + return + } + } + + var lines = [ + "DomainDig Diff", + "Domain: \(domain)", + "From: \(orderedEntries[0].id.uuidString)", + "To: \(orderedEntries[1].id.uuidString)" + ] + + for section in diff.sections where section.hasChanges { + lines.append("") + lines.append(section.title) + for item in section.items where item.hasChanges { + lines.append("\(item.changeType.marker) \(item.label): \(item.oldValue ?? "none") -> \(item.newValue ?? "none")") + } + } + + FileHandle.standardOutput.write(Data((lines.joined(separator: "\n") + "\n").utf8)) + } + + private static func optionValue(named name: String, in arguments: [String]) -> String? { + guard let index = arguments.firstIndex(of: name), arguments.indices.contains(index + 1) else { + return nil + } + return arguments[index + 1] + } + private static func runBackupCommand(arguments: [String], wantsJSON: Bool) { guard let subcommand = arguments.first else { fputs(usageText, stderr) @@ -424,6 +539,8 @@ struct DomainDigCLI { private static var usageText: String { """ usage: domaindig <domain> [--json] [--ownership-history] [--dns-history] [--extended-subdomains] [--pricing] [--show-usage] + domaindig history <domain> [--json] + domaindig diff <domain> --from <id> --to <id> [--json] domaindig monitor [--json] domaindig backup export [path] domaindig backup import <path> [--replace] |
