ralph-loop[epic-a-to-d-mainline]: iteration 2
This commit is contained in:
@@ -391,29 +391,7 @@ final class AtlasAppModel: ObservableObject {
|
||||
}
|
||||
|
||||
func refreshApps() async {
|
||||
guard !isAppActionRunning else {
|
||||
return
|
||||
}
|
||||
|
||||
selection = .apps
|
||||
isAppActionRunning = true
|
||||
activePreviewAppID = nil
|
||||
activeUninstallAppID = nil
|
||||
currentAppPreview = nil
|
||||
currentPreviewedAppID = nil
|
||||
latestAppsSummary = AtlasL10n.string("model.apps.refreshing")
|
||||
|
||||
do {
|
||||
let output = try await workspaceController.listApps()
|
||||
withAnimation(.snappy(duration: 0.24)) {
|
||||
snapshot = output.snapshot
|
||||
latestAppsSummary = output.summary
|
||||
}
|
||||
} catch {
|
||||
latestAppsSummary = error.localizedDescription
|
||||
}
|
||||
|
||||
isAppActionRunning = false
|
||||
await reloadAppsInventory(navigateToApps: true, resetPreview: true)
|
||||
}
|
||||
|
||||
func previewAppUninstall(appID: UUID) async {
|
||||
@@ -473,6 +451,8 @@ final class AtlasAppModel: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
let restoredItem = snapshot.recoveryItems.first(where: { $0.id == itemID })
|
||||
let shouldRefreshAppsAfterRestore = restoredItem?.isAppPayload == true
|
||||
restoringRecoveryItemID = itemID
|
||||
|
||||
do {
|
||||
@@ -481,8 +461,21 @@ final class AtlasAppModel: ObservableObject {
|
||||
snapshot = output.snapshot
|
||||
latestScanSummary = output.summary
|
||||
smartCleanExecutionIssue = nil
|
||||
if shouldRefreshAppsAfterRestore {
|
||||
currentAppPreview = nil
|
||||
currentPreviewedAppID = nil
|
||||
latestAppsSummary = output.summary
|
||||
}
|
||||
}
|
||||
if shouldRefreshAppsAfterRestore {
|
||||
await reloadAppsInventory(
|
||||
navigateToApps: false,
|
||||
resetPreview: true,
|
||||
loadingSummary: output.summary
|
||||
)
|
||||
} else {
|
||||
await refreshPlanPreview()
|
||||
}
|
||||
} catch {
|
||||
let persistedState = repository.loadState()
|
||||
withAnimation(.snappy(duration: 0.24)) {
|
||||
@@ -605,6 +598,40 @@ final class AtlasAppModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
private func reloadAppsInventory(
|
||||
navigateToApps: Bool,
|
||||
resetPreview: Bool,
|
||||
loadingSummary: String? = nil
|
||||
) async {
|
||||
guard !isAppActionRunning else {
|
||||
return
|
||||
}
|
||||
|
||||
if navigateToApps {
|
||||
selection = .apps
|
||||
}
|
||||
isAppActionRunning = true
|
||||
activePreviewAppID = nil
|
||||
activeUninstallAppID = nil
|
||||
if resetPreview {
|
||||
currentAppPreview = nil
|
||||
currentPreviewedAppID = nil
|
||||
}
|
||||
latestAppsSummary = loadingSummary ?? AtlasL10n.string("model.apps.refreshing")
|
||||
|
||||
do {
|
||||
let output = try await workspaceController.listApps()
|
||||
withAnimation(.snappy(duration: 0.24)) {
|
||||
snapshot = output.snapshot
|
||||
latestAppsSummary = output.summary
|
||||
}
|
||||
} catch {
|
||||
latestAppsSummary = error.localizedDescription
|
||||
}
|
||||
|
||||
isAppActionRunning = false
|
||||
}
|
||||
|
||||
private func filter<Element>(
|
||||
_ elements: [Element],
|
||||
route: AtlasRoute,
|
||||
@@ -627,6 +654,15 @@ final class AtlasAppModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
private extension RecoveryItem {
|
||||
var isAppPayload: Bool {
|
||||
if case .app = payload {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private extension AtlasAppModel {
|
||||
func resolvedTargetPaths(for item: ActionItem) -> [String] {
|
||||
if let targetPaths = item.targetPaths, !targetPaths.isEmpty {
|
||||
|
||||
@@ -284,6 +284,72 @@ final class AtlasAppModelTests: XCTestCase {
|
||||
XCTAssertNil(model.smartCleanExecutionIssue)
|
||||
}
|
||||
|
||||
func testRestoreAppRecoveryItemClearsPreviewAndRefreshesInventoryWithoutLeavingHistory() async throws {
|
||||
let repository = makeRepository()
|
||||
let app = AppFootprint(
|
||||
id: UUID(),
|
||||
name: "Recovered App",
|
||||
bundleIdentifier: "com.example.recovered",
|
||||
bundlePath: "/Applications/Recovered App.app",
|
||||
bytes: 2_048,
|
||||
leftoverItems: 9
|
||||
)
|
||||
let recoveryItem = RecoveryItem(
|
||||
id: UUID(),
|
||||
title: app.name,
|
||||
detail: "Restorable app payload",
|
||||
originalPath: app.bundlePath,
|
||||
bytes: app.bytes,
|
||||
deletedAt: Date(),
|
||||
expiresAt: Date().addingTimeInterval(3600),
|
||||
payload: .app(
|
||||
AtlasAppRecoveryPayload(
|
||||
app: app,
|
||||
uninstallEvidence: AtlasAppUninstallEvidence(
|
||||
bundlePath: app.bundlePath,
|
||||
bundleBytes: app.bytes,
|
||||
reviewOnlyGroups: []
|
||||
)
|
||||
)
|
||||
),
|
||||
restoreMappings: nil
|
||||
)
|
||||
let state = AtlasWorkspaceState(
|
||||
snapshot: AtlasWorkspaceSnapshot(
|
||||
reclaimableSpaceBytes: 0,
|
||||
findings: [],
|
||||
apps: [app],
|
||||
taskRuns: [],
|
||||
recoveryItems: [recoveryItem],
|
||||
permissions: [],
|
||||
healthSnapshot: nil
|
||||
),
|
||||
currentPlan: ActionPlan(title: "Review 0 selected findings", items: [], estimatedBytes: 0),
|
||||
settings: AtlasScaffoldWorkspace.state().settings
|
||||
)
|
||||
_ = try repository.saveState(state)
|
||||
|
||||
let worker = AtlasScaffoldWorkerService(
|
||||
repository: repository,
|
||||
appsInventoryProvider: RestoredInventoryProvider()
|
||||
)
|
||||
let model = AtlasAppModel(repository: repository, workerService: worker)
|
||||
|
||||
await model.previewAppUninstall(appID: app.id)
|
||||
XCTAssertNotNil(model.currentAppPreview)
|
||||
XCTAssertEqual(model.currentPreviewedAppID, app.id)
|
||||
|
||||
model.navigate(to: .history)
|
||||
await model.restoreRecoveryItem(recoveryItem.id)
|
||||
|
||||
XCTAssertEqual(model.selection, .history)
|
||||
XCTAssertNil(model.currentAppPreview)
|
||||
XCTAssertNil(model.currentPreviewedAppID)
|
||||
XCTAssertEqual(model.snapshot.apps.first?.leftoverItems, 1)
|
||||
XCTAssertEqual(model.latestAppsSummary, AtlasL10n.string("application.apps.loaded.one"))
|
||||
XCTAssertFalse(model.snapshot.recoveryItems.contains(where: { $0.id == recoveryItem.id }))
|
||||
}
|
||||
|
||||
func testRestoreExpiredRecoveryItemReloadsPersistedState() async throws {
|
||||
let baseDate = Date(timeIntervalSince1970: 1_710_000_000)
|
||||
let clock = TestClock(now: baseDate)
|
||||
@@ -496,6 +562,20 @@ private struct FakeInventoryProvider: AtlasAppInventoryProviding {
|
||||
}
|
||||
}
|
||||
|
||||
private struct RestoredInventoryProvider: AtlasAppInventoryProviding {
|
||||
func collectInstalledApps() async throws -> [AppFootprint] {
|
||||
[
|
||||
AppFootprint(
|
||||
name: "Recovered App",
|
||||
bundleIdentifier: "com.example.recovered",
|
||||
bundlePath: "/Applications/Recovered App.app",
|
||||
bytes: 2_048,
|
||||
leftoverItems: 1
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
private struct FailingSmartCleanProvider: AtlasSmartCleanScanProviding {
|
||||
func collectSmartCleanScan() async throws -> AtlasSmartCleanScanResult {
|
||||
throw NSError(domain: "AtlasAppModelTests", code: 1, userInfo: [NSLocalizedDescriptionKey: "Fixture scan failed."])
|
||||
|
||||
@@ -42,6 +42,50 @@ public struct AtlasWorkspaceState: Codable, Hashable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public enum AtlasWorkspaceStateSchemaVersion {
|
||||
public static let current = 1
|
||||
}
|
||||
|
||||
public struct AtlasPersistedWorkspaceState: Codable, Hashable, Sendable {
|
||||
public var schemaVersion: Int
|
||||
public var savedAt: Date
|
||||
public var snapshot: AtlasWorkspaceSnapshot
|
||||
public var currentPlan: ActionPlan
|
||||
public var settings: AtlasSettings
|
||||
|
||||
public init(
|
||||
schemaVersion: Int = AtlasWorkspaceStateSchemaVersion.current,
|
||||
savedAt: Date = Date(),
|
||||
snapshot: AtlasWorkspaceSnapshot,
|
||||
currentPlan: ActionPlan,
|
||||
settings: AtlasSettings
|
||||
) {
|
||||
self.schemaVersion = schemaVersion
|
||||
self.savedAt = savedAt
|
||||
self.snapshot = snapshot
|
||||
self.currentPlan = currentPlan
|
||||
self.settings = settings
|
||||
}
|
||||
|
||||
public init(
|
||||
schemaVersion: Int = AtlasWorkspaceStateSchemaVersion.current,
|
||||
savedAt: Date = Date(),
|
||||
state: AtlasWorkspaceState
|
||||
) {
|
||||
self.init(
|
||||
schemaVersion: schemaVersion,
|
||||
savedAt: savedAt,
|
||||
snapshot: state.snapshot,
|
||||
currentPlan: state.currentPlan,
|
||||
settings: state.settings
|
||||
)
|
||||
}
|
||||
|
||||
public var workspaceState: AtlasWorkspaceState {
|
||||
AtlasWorkspaceState(snapshot: snapshot, currentPlan: currentPlan, settings: settings)
|
||||
}
|
||||
}
|
||||
|
||||
public enum AtlasScaffoldWorkspace {
|
||||
public static func state(language: AtlasLanguage = AtlasL10n.currentLanguage) -> AtlasWorkspaceState {
|
||||
let snapshot = AtlasWorkspaceSnapshot(
|
||||
|
||||
@@ -355,14 +355,45 @@ public struct AtlasAppUninstallEvidence: Codable, Hashable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public enum AtlasRecoveryPayloadSchemaVersion {
|
||||
public static let current = 1
|
||||
}
|
||||
|
||||
public struct AtlasAppRecoveryPayload: Codable, Hashable, Sendable {
|
||||
public var schemaVersion: Int
|
||||
public var app: AppFootprint
|
||||
public var uninstallEvidence: AtlasAppUninstallEvidence
|
||||
|
||||
public init(app: AppFootprint, uninstallEvidence: AtlasAppUninstallEvidence) {
|
||||
public init(
|
||||
schemaVersion: Int = AtlasRecoveryPayloadSchemaVersion.current,
|
||||
app: AppFootprint,
|
||||
uninstallEvidence: AtlasAppUninstallEvidence
|
||||
) {
|
||||
self.schemaVersion = schemaVersion
|
||||
self.app = app
|
||||
self.uninstallEvidence = uninstallEvidence
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case schemaVersion
|
||||
case app
|
||||
case uninstallEvidence
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.schemaVersion = try container.decodeIfPresent(Int.self, forKey: .schemaVersion)
|
||||
?? AtlasRecoveryPayloadSchemaVersion.current
|
||||
self.app = try container.decode(AppFootprint.self, forKey: .app)
|
||||
self.uninstallEvidence = try container.decode(AtlasAppUninstallEvidence.self, forKey: .uninstallEvidence)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(schemaVersion, forKey: .schemaVersion)
|
||||
try container.encode(app, forKey: .app)
|
||||
try container.encode(uninstallEvidence, forKey: .uninstallEvidence)
|
||||
}
|
||||
}
|
||||
|
||||
public enum RecoveryPayload: Codable, Hashable, Sendable {
|
||||
|
||||
@@ -494,6 +494,16 @@
|
||||
"history.detail.recovery.deleted" = "Deleted";
|
||||
"history.detail.recovery.window" = "Retention window";
|
||||
"history.detail.recovery.window.open" = "Still recoverable";
|
||||
"history.detail.recovery.evidence.title" = "Recorded Recovery Evidence";
|
||||
"history.detail.recovery.evidence.subtitle" = "Atlas keeps the app payload, review-only groups, and restore path records together for this recovery item.";
|
||||
"history.detail.recovery.evidence.payload" = "Recorded payload";
|
||||
"history.detail.recovery.evidence.schema" = "Payload schema";
|
||||
"history.detail.recovery.evidence.reviewGroups" = "Review-only groups";
|
||||
"history.detail.recovery.evidence.reviewGroups.detail.one" = "%@ · 1 item";
|
||||
"history.detail.recovery.evidence.reviewGroups.detail.other" = "%1$@ · %2$d items";
|
||||
"history.detail.recovery.evidence.restorePaths" = "Restore path records";
|
||||
"history.detail.recovery.evidence.restorePaths.detail.one" = "1 original-path to Trash-path record was saved for this item.";
|
||||
"history.detail.recovery.evidence.restorePaths.detail.other" = "%d original-path to Trash-path records were saved for this item.";
|
||||
"history.detail.recovery.reviewOnly.title" = "Review-Only Leftover Evidence";
|
||||
"history.detail.recovery.reviewOnly.subtitle.one" = "1 related leftover item was recorded during uninstall.";
|
||||
"history.detail.recovery.reviewOnly.subtitle.other" = "%d related leftover items were recorded during uninstall.";
|
||||
|
||||
@@ -494,6 +494,16 @@
|
||||
"history.detail.recovery.deleted" = "删除时间";
|
||||
"history.detail.recovery.window" = "保留窗口";
|
||||
"history.detail.recovery.window.open" = "仍可恢复";
|
||||
"history.detail.recovery.evidence.title" = "恢复证据记录";
|
||||
"history.detail.recovery.evidence.subtitle" = "Atlas 会把这条恢复项的应用载荷、仅供复核分组和恢复路径记录一起保留下来。";
|
||||
"history.detail.recovery.evidence.payload" = "已记录载荷";
|
||||
"history.detail.recovery.evidence.schema" = "载荷版本";
|
||||
"history.detail.recovery.evidence.reviewGroups" = "仅供复核分组";
|
||||
"history.detail.recovery.evidence.reviewGroups.detail.one" = "%@ · 1 项";
|
||||
"history.detail.recovery.evidence.reviewGroups.detail.other" = "%1$@ · %2$d 项";
|
||||
"history.detail.recovery.evidence.restorePaths" = "恢复路径记录";
|
||||
"history.detail.recovery.evidence.restorePaths.detail.one" = "这条恢复项记录了 1 组原路径与废纸篓路径映射。";
|
||||
"history.detail.recovery.evidence.restorePaths.detail.other" = "这条恢复项记录了 %d 组原路径与废纸篓路径映射。";
|
||||
"history.detail.recovery.reviewOnly.title" = "仅供复核的残留证据";
|
||||
"history.detail.recovery.reviewOnly.subtitle.one" = "这次卸载还记录了 1 个相关残留项目。";
|
||||
"history.detail.recovery.reviewOnly.subtitle.other" = "这次卸载还记录了 %d 个相关残留项目。";
|
||||
|
||||
@@ -65,6 +65,7 @@ final class AtlasDomainTests: XCTestCase {
|
||||
return XCTFail("Expected app payload")
|
||||
}
|
||||
|
||||
XCTAssertEqual(appPayload.schemaVersion, AtlasRecoveryPayloadSchemaVersion.current)
|
||||
XCTAssertEqual(appPayload.app.name, "Legacy App")
|
||||
XCTAssertEqual(appPayload.app.leftoverItems, 2)
|
||||
XCTAssertEqual(appPayload.uninstallEvidence.reviewOnlyGroupCount, 0)
|
||||
|
||||
@@ -981,6 +981,54 @@ private struct HistoryRecoveryDetailView: View {
|
||||
.strokeBorder(AtlasColor.border, lineWidth: 1)
|
||||
)
|
||||
|
||||
if appRecoveryPayload != nil || item.hasPhysicalRestorePath {
|
||||
AtlasInfoCard(
|
||||
title: AtlasL10n.string("history.detail.recovery.evidence.title"),
|
||||
subtitle: AtlasL10n.string("history.detail.recovery.evidence.subtitle"),
|
||||
tone: item.hasPhysicalRestorePath ? .success : .neutral
|
||||
) {
|
||||
VStack(alignment: .leading, spacing: AtlasSpacing.md) {
|
||||
if let payload = appRecoveryPayload {
|
||||
AtlasKeyValueRow(
|
||||
title: AtlasL10n.string("history.detail.recovery.evidence.payload"),
|
||||
value: payload.app.name,
|
||||
detail: payload.app.bundleIdentifier
|
||||
)
|
||||
AtlasKeyValueRow(
|
||||
title: AtlasL10n.string("history.detail.recovery.evidence.schema"),
|
||||
value: "\(payload.schemaVersion)",
|
||||
detail: item.hasPhysicalRestorePath
|
||||
? AtlasL10n.string("history.detail.recovery.callout.available.fileBacked.title")
|
||||
: AtlasL10n.string("history.detail.recovery.callout.available.stateOnly.title")
|
||||
)
|
||||
|
||||
if payload.uninstallEvidence.reviewOnlyGroupCount > 0 {
|
||||
AtlasKeyValueRow(
|
||||
title: AtlasL10n.string("history.detail.recovery.evidence.reviewGroups"),
|
||||
value: "\(payload.uninstallEvidence.reviewOnlyGroupCount)",
|
||||
detail: appReviewOnlyGroupSummary(for: payload)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if let restoreMappings = item.restoreMappings, !restoreMappings.isEmpty {
|
||||
AtlasMachineTextBlock(
|
||||
title: AtlasL10n.string("history.detail.recovery.evidence.restorePaths"),
|
||||
value: restoreMappings
|
||||
.map { "\($0.originalPath)\n-> \($0.trashedPath)" }
|
||||
.joined(separator: "\n\n"),
|
||||
detail: AtlasL10n.string(
|
||||
restoreMappings.count == 1
|
||||
? "history.detail.recovery.evidence.restorePaths.detail.one"
|
||||
: "history.detail.recovery.evidence.restorePaths.detail.other",
|
||||
restoreMappings.count
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let payload = appRecoveryPayload, payload.uninstallEvidence.reviewOnlyItemCount > 0 {
|
||||
AtlasInfoCard(
|
||||
title: AtlasL10n.string("history.detail.recovery.reviewOnly.title"),
|
||||
@@ -1128,6 +1176,32 @@ private struct HistoryRecoveryDetailView: View {
|
||||
? AtlasL10n.string("history.restore.hint.fileBacked")
|
||||
: AtlasL10n.string("history.restore.hint.stateOnly")
|
||||
}
|
||||
|
||||
private func appReviewOnlyGroupSummary(for payload: AtlasAppRecoveryPayload) -> String {
|
||||
payload.uninstallEvidence.reviewOnlyGroups.map { group in
|
||||
let categoryLabel: String
|
||||
switch group.category {
|
||||
case .supportFiles:
|
||||
categoryLabel = AtlasL10n.string("infrastructure.plan.uninstall.review.supportFiles")
|
||||
case .caches:
|
||||
categoryLabel = AtlasL10n.string("infrastructure.plan.uninstall.review.caches")
|
||||
case .preferences:
|
||||
categoryLabel = AtlasL10n.string("infrastructure.plan.uninstall.review.preferences")
|
||||
case .logs:
|
||||
categoryLabel = AtlasL10n.string("infrastructure.plan.uninstall.review.logs")
|
||||
case .launchItems:
|
||||
categoryLabel = AtlasL10n.string("infrastructure.plan.uninstall.review.launchItems")
|
||||
}
|
||||
return AtlasL10n.string(
|
||||
group.items.count == 1
|
||||
? "history.detail.recovery.evidence.reviewGroups.detail.one"
|
||||
: "history.detail.recovery.evidence.reviewGroups.detail.other",
|
||||
categoryLabel,
|
||||
group.items.count
|
||||
)
|
||||
}
|
||||
.joined(separator: " • ")
|
||||
}
|
||||
}
|
||||
|
||||
private extension TaskRun {
|
||||
|
||||
@@ -189,10 +189,14 @@ public enum AtlasSmartCleanExecutionSupport {
|
||||
home + "/Library/Logs",
|
||||
home + "/Library/Suggestions",
|
||||
home + "/Library/Messages/Caches",
|
||||
home + "/Library/Developer/CoreSimulator/Caches",
|
||||
home + "/Library/Developer/CoreSimulator/tmp",
|
||||
home + "/Library/Developer/Xcode/DerivedData",
|
||||
home + "/Library/pnpm/store",
|
||||
home + "/.npm",
|
||||
home + "/.npm_cache",
|
||||
home + "/.gradle/caches",
|
||||
home + "/.ivy2/cache",
|
||||
home + "/.oh-my-zsh/cache",
|
||||
home + "/.cache",
|
||||
home + "/.pytest_cache",
|
||||
@@ -220,6 +224,7 @@ public enum AtlasSmartCleanExecutionSupport {
|
||||
home + "/.android/build-cache",
|
||||
home + "/.android/cache",
|
||||
home + "/.cache/swift-package-manager",
|
||||
home + "/.swiftpm/cache",
|
||||
home + "/.expo/expo-go",
|
||||
home + "/.expo/android-apk-cache",
|
||||
home + "/.expo/ios-simulator-app-cache",
|
||||
@@ -319,9 +324,10 @@ public struct AtlasWorkspaceRepository: Sendable {
|
||||
if FileManager.default.fileExists(atPath: stateFileURL.path) {
|
||||
do {
|
||||
let data = try Data(contentsOf: stateFileURL)
|
||||
let decoded = try decoder.decode(AtlasWorkspaceState.self, from: data)
|
||||
let decodedResult = try decodePersistedState(from: data, using: decoder)
|
||||
let decoded = decodedResult.state
|
||||
let normalized = normalizedState(decoded)
|
||||
if normalized != decoded {
|
||||
if decodedResult.usedLegacyShape || normalized != decoded {
|
||||
_ = try? saveState(normalized)
|
||||
}
|
||||
return normalized
|
||||
@@ -361,7 +367,12 @@ public struct AtlasWorkspaceRepository: Sendable {
|
||||
|
||||
let data: Data
|
||||
do {
|
||||
data = try encoder.encode(normalizedState)
|
||||
data = try encoder.encode(
|
||||
AtlasPersistedWorkspaceState(
|
||||
savedAt: nowProvider(),
|
||||
state: normalizedState
|
||||
)
|
||||
)
|
||||
} catch {
|
||||
throw AtlasWorkspaceRepositoryError.encodeFailed(error.localizedDescription)
|
||||
}
|
||||
@@ -403,6 +414,14 @@ public struct AtlasWorkspaceRepository: Sendable {
|
||||
return normalized
|
||||
}
|
||||
|
||||
private func decodePersistedState(from data: Data, using decoder: JSONDecoder) throws -> (state: AtlasWorkspaceState, usedLegacyShape: Bool) {
|
||||
if let persisted = try? decoder.decode(AtlasPersistedWorkspaceState.self, from: data) {
|
||||
return (persisted.workspaceState, false)
|
||||
}
|
||||
|
||||
return (try decoder.decode(AtlasWorkspaceState.self, from: data), true)
|
||||
}
|
||||
|
||||
private static var defaultStateFileURL: URL {
|
||||
if let explicit = ProcessInfo.processInfo.environment["ATLAS_STATE_FILE"], !explicit.isEmpty {
|
||||
return URL(fileURLWithPath: explicit)
|
||||
|
||||
@@ -18,6 +18,35 @@ final class AtlasInfrastructureTests: XCTestCase {
|
||||
XCTAssertEqual(loaded.snapshot.apps.count, state.snapshot.apps.count)
|
||||
}
|
||||
|
||||
func testRepositoryPersistsVersionedWorkspaceEnvelope() throws {
|
||||
let fileURL = temporaryStateFileURL()
|
||||
let repository = AtlasWorkspaceRepository(stateFileURL: fileURL)
|
||||
|
||||
XCTAssertNoThrow(try repository.saveState(AtlasScaffoldWorkspace.state()))
|
||||
|
||||
let data = try Data(contentsOf: fileURL)
|
||||
let persisted = try JSONDecoder().decode(AtlasPersistedWorkspaceState.self, from: data)
|
||||
|
||||
XCTAssertEqual(persisted.schemaVersion, AtlasWorkspaceStateSchemaVersion.current)
|
||||
XCTAssertFalse(persisted.snapshot.apps.isEmpty)
|
||||
}
|
||||
|
||||
func testRepositoryLoadsLegacyWorkspaceStateAndRewritesEnvelope() throws {
|
||||
let fileURL = temporaryStateFileURL()
|
||||
let legacyState = AtlasScaffoldWorkspace.state()
|
||||
try FileManager.default.createDirectory(at: fileURL.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||
try JSONEncoder().encode(legacyState).write(to: fileURL)
|
||||
|
||||
let repository = AtlasWorkspaceRepository(stateFileURL: fileURL)
|
||||
let loaded = repository.loadState()
|
||||
|
||||
XCTAssertEqual(loaded.snapshot.apps.count, legacyState.snapshot.apps.count)
|
||||
|
||||
let migratedData = try Data(contentsOf: fileURL)
|
||||
let persisted = try JSONDecoder().decode(AtlasPersistedWorkspaceState.self, from: migratedData)
|
||||
XCTAssertEqual(persisted.schemaVersion, AtlasWorkspaceStateSchemaVersion.current)
|
||||
XCTAssertEqual(persisted.snapshot.apps.count, legacyState.snapshot.apps.count)
|
||||
}
|
||||
|
||||
func testRepositorySaveStateThrowsForInvalidParentURL() {
|
||||
let repository = AtlasWorkspaceRepository(
|
||||
@@ -439,6 +468,40 @@ final class AtlasInfrastructureTests: XCTestCase {
|
||||
XCTAssertTrue(AtlasSmartCleanExecutionSupport.isFindingExecutionSupported(finding))
|
||||
}
|
||||
|
||||
func testGradleCacheTargetIsSupportedExecutionTarget() {
|
||||
let targetURL = FileManager.default.homeDirectoryForCurrentUser
|
||||
.appendingPathComponent(".gradle/caches/modules-2/files-2.1/atlas-fixture.bin")
|
||||
let finding = Finding(
|
||||
id: UUID(),
|
||||
title: "Gradle cache",
|
||||
detail: targetURL.path,
|
||||
bytes: 1,
|
||||
risk: .safe,
|
||||
category: "Developer tools",
|
||||
targetPaths: [targetURL.path]
|
||||
)
|
||||
|
||||
XCTAssertTrue(AtlasSmartCleanExecutionSupport.isSupportedExecutionTarget(targetURL))
|
||||
XCTAssertTrue(AtlasSmartCleanExecutionSupport.isFindingExecutionSupported(finding))
|
||||
}
|
||||
|
||||
func testCoreSimulatorCacheTargetIsSupportedExecutionTarget() {
|
||||
let targetURL = FileManager.default.homeDirectoryForCurrentUser
|
||||
.appendingPathComponent("Library/Developer/CoreSimulator/Caches/atlas-fixture/device-cache.db")
|
||||
let finding = Finding(
|
||||
id: UUID(),
|
||||
title: "CoreSimulator cache",
|
||||
detail: targetURL.path,
|
||||
bytes: 1,
|
||||
risk: .safe,
|
||||
category: "Developer tools",
|
||||
targetPaths: [targetURL.path]
|
||||
)
|
||||
|
||||
XCTAssertTrue(AtlasSmartCleanExecutionSupport.isSupportedExecutionTarget(targetURL))
|
||||
XCTAssertTrue(AtlasSmartCleanExecutionSupport.isFindingExecutionSupported(finding))
|
||||
}
|
||||
|
||||
func testContainerCacheTargetIsSupportedExecutionTarget() {
|
||||
let targetURL = FileManager.default.homeDirectoryForCurrentUser
|
||||
.appendingPathComponent("Library/Containers/com.example.preview/Data/Library/Caches/cache.db")
|
||||
@@ -661,6 +724,39 @@ final class AtlasInfrastructureTests: XCTestCase {
|
||||
XCTAssertEqual(secondScan.snapshot.reclaimableSpaceBytes, 0)
|
||||
}
|
||||
|
||||
func testScanExecuteRescanRemovesExecutedGradleCacheTargetFromRealResults() async throws {
|
||||
let repository = AtlasWorkspaceRepository(stateFileURL: temporaryStateFileURL())
|
||||
let home = FileManager.default.homeDirectoryForCurrentUser
|
||||
let targetDirectory = home.appendingPathComponent(".gradle/caches/AtlasExecutionTests/" + UUID().uuidString, isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: targetDirectory, withIntermediateDirectories: true)
|
||||
let targetFile = targetDirectory.appendingPathComponent("modules.bin")
|
||||
try Data("gradle-cache".utf8).write(to: targetFile)
|
||||
|
||||
let provider = FileBackedSmartCleanProvider(targetFileURL: targetFile, title: "Gradle cache")
|
||||
let worker = AtlasScaffoldWorkerService(
|
||||
repository: repository,
|
||||
smartCleanScanProvider: provider,
|
||||
allowProviderFailureFallback: false,
|
||||
allowStateOnlyCleanExecution: false
|
||||
)
|
||||
|
||||
let firstScan = try await worker.submit(AtlasRequestEnvelope(command: .startScan(taskID: UUID())))
|
||||
XCTAssertEqual(firstScan.snapshot.findings.count, 1)
|
||||
let planID = try XCTUnwrap(firstScan.previewPlan?.id)
|
||||
|
||||
let execute = try await worker.submit(AtlasRequestEnvelope(command: .executePlan(planID: planID)))
|
||||
if case let .accepted(task) = execute.response.response {
|
||||
XCTAssertEqual(task.kind, .executePlan)
|
||||
} else {
|
||||
XCTFail("Expected accepted execute-plan response")
|
||||
}
|
||||
XCTAssertFalse(FileManager.default.fileExists(atPath: targetFile.path))
|
||||
|
||||
let secondScan = try await worker.submit(AtlasRequestEnvelope(command: .startScan(taskID: UUID())))
|
||||
XCTAssertEqual(secondScan.snapshot.findings.count, 0)
|
||||
XCTAssertEqual(secondScan.snapshot.reclaimableSpaceBytes, 0)
|
||||
}
|
||||
|
||||
func testScanExecuteRescanRemovesExecutedContainerCacheTargetFromRealResults() async throws {
|
||||
let repository = AtlasWorkspaceRepository(stateFileURL: temporaryStateFileURL())
|
||||
let home = FileManager.default.homeDirectoryForCurrentUser
|
||||
|
||||
@@ -3,8 +3,59 @@ import AtlasDomain
|
||||
import AtlasProtocol
|
||||
import Foundation
|
||||
|
||||
public struct AtlasFixtureAppDescriptor: Hashable, Sendable {
|
||||
public let scenario: String
|
||||
public let appName: String
|
||||
public let bundleIdentifier: String
|
||||
public let hasLaunchAgent: Bool
|
||||
public let expectedReviewOnlyCategories: [String]
|
||||
|
||||
public init(
|
||||
scenario: String,
|
||||
appName: String,
|
||||
bundleIdentifier: String,
|
||||
hasLaunchAgent: Bool,
|
||||
expectedReviewOnlyCategories: [String]
|
||||
) {
|
||||
self.scenario = scenario
|
||||
self.appName = appName
|
||||
self.bundleIdentifier = bundleIdentifier
|
||||
self.hasLaunchAgent = hasLaunchAgent
|
||||
self.expectedReviewOnlyCategories = expectedReviewOnlyCategories
|
||||
}
|
||||
}
|
||||
|
||||
public enum AtlasTestingFixtures {
|
||||
public static let workspace = AtlasScaffoldWorkspace.snapshot()
|
||||
public static let request = AtlasRequestEnvelope(command: .inspectPermissions)
|
||||
public static let firstFinding = AtlasScaffoldFixtures.findings.first
|
||||
public static let appEvidenceFixtures: [AtlasFixtureAppDescriptor] = [
|
||||
AtlasFixtureAppDescriptor(
|
||||
scenario: "mainstream-gui",
|
||||
appName: "Atlas Fixture Browser",
|
||||
bundleIdentifier: "com.example.atlas.fixture.browser",
|
||||
hasLaunchAgent: false,
|
||||
expectedReviewOnlyCategories: ["support files", "caches", "preferences"]
|
||||
),
|
||||
AtlasFixtureAppDescriptor(
|
||||
scenario: "developer-heavy",
|
||||
appName: "Atlas Fixture Dev",
|
||||
bundleIdentifier: "com.example.atlas.fixture.dev",
|
||||
hasLaunchAgent: true,
|
||||
expectedReviewOnlyCategories: ["support files", "caches", "logs", "launch items"]
|
||||
),
|
||||
AtlasFixtureAppDescriptor(
|
||||
scenario: "sparse-leftovers",
|
||||
appName: "Atlas Fixture Sparse",
|
||||
bundleIdentifier: "com.example.atlas.fixture.sparse",
|
||||
hasLaunchAgent: false,
|
||||
expectedReviewOnlyCategories: ["saved state"]
|
||||
),
|
||||
]
|
||||
|
||||
public static let smartCleanSafeRoots: [String] = [
|
||||
"~/Library/Developer/CoreSimulator/Caches",
|
||||
"~/.gradle/caches",
|
||||
"~/.ivy2/cache",
|
||||
]
|
||||
}
|
||||
|
||||
50
scripts/atlas/apps-evidence-acceptance.sh
Executable file
50
scripts/atlas/apps-evidence-acceptance.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
FIXTURE_SCRIPT="$SCRIPT_DIR/apps-manual-fixtures.sh"
|
||||
|
||||
print_guide() {
|
||||
cat <<'EOF'
|
||||
Apps Evidence Acceptance Guide
|
||||
|
||||
1. Run Atlas and open the Apps screen.
|
||||
2. Verify these fixture apps appear:
|
||||
- Atlas Fixture Browser
|
||||
- Atlas Fixture Dev
|
||||
- Atlas Fixture Sparse
|
||||
3. For each fixture app, build the uninstall plan and confirm:
|
||||
- preview categories match the expected review-only evidence
|
||||
- recoverable bundle removal is separated from review-only evidence
|
||||
- observed paths are listed for review-only groups
|
||||
4. Execute uninstall for Atlas Fixture Dev and confirm:
|
||||
- completion summary mentions real removal and review-only categories
|
||||
- History shows the uninstall with review-only evidence still informational
|
||||
5. Restore the Atlas Fixture Dev recovery item and confirm:
|
||||
- the app reappears in Apps after the restore-driven inventory refresh
|
||||
- stale uninstall preview is cleared
|
||||
- History shows restore-path evidence when supported
|
||||
6. Re-run Apps refresh and verify leftover counts remain consistent with current disk state.
|
||||
7. Clean up fixtures when done.
|
||||
EOF
|
||||
}
|
||||
|
||||
case "${1:-guide}" in
|
||||
setup)
|
||||
"$FIXTURE_SCRIPT" create
|
||||
print_guide
|
||||
;;
|
||||
status)
|
||||
"$FIXTURE_SCRIPT" status
|
||||
;;
|
||||
cleanup)
|
||||
"$FIXTURE_SCRIPT" cleanup
|
||||
;;
|
||||
guide)
|
||||
print_guide
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [setup|status|cleanup|guide]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
178
scripts/atlas/apps-manual-fixtures.sh
Executable file
178
scripts/atlas/apps-manual-fixtures.sh
Executable file
@@ -0,0 +1,178 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
APPS_ROOT="$HOME/Applications"
|
||||
SUPPORT_ROOT="$HOME/Library/Application Support"
|
||||
CACHE_ROOT="$HOME/Library/Caches"
|
||||
PREFERENCES_ROOT="$HOME/Library/Preferences"
|
||||
LOG_ROOT="$HOME/Library/Logs"
|
||||
STATE_ROOT="$HOME/Library/Saved Application State"
|
||||
LAUNCH_AGENTS_ROOT="$HOME/Library/LaunchAgents"
|
||||
|
||||
FIXTURES=(
|
||||
"Atlas Fixture Browser|com.example.atlas.fixture.browser|support,caches,preferences"
|
||||
"Atlas Fixture Dev|com.example.atlas.fixture.dev|support,caches,logs,launch"
|
||||
"Atlas Fixture Sparse|com.example.atlas.fixture.sparse|saved-state"
|
||||
)
|
||||
|
||||
create_blob() {
|
||||
local path="$1"
|
||||
local size_kb="$2"
|
||||
mkdir -p "$(dirname "$path")"
|
||||
if command -v mkfile > /dev/null 2>&1; then
|
||||
mkfile "${size_kb}k" "$path"
|
||||
else
|
||||
dd if=/dev/zero of="$path" bs=1024 count="$size_kb" status=none
|
||||
fi
|
||||
}
|
||||
|
||||
write_info_plist() {
|
||||
local plist_path="$1"
|
||||
local bundle_id="$2"
|
||||
local app_name="$3"
|
||||
cat > "$plist_path" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${bundle_id}</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${app_name}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>fixture</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
}
|
||||
|
||||
create_app_bundle() {
|
||||
local app_name="$1"
|
||||
local bundle_id="$2"
|
||||
local bundle_path="$APPS_ROOT/${app_name}.app"
|
||||
local contents_path="$bundle_path/Contents"
|
||||
local executable_path="$contents_path/MacOS/fixture"
|
||||
local plist_path="$contents_path/Info.plist"
|
||||
|
||||
mkdir -p "$(dirname "$executable_path")"
|
||||
printf '#!/bin/sh\nexit 0\n' > "$executable_path"
|
||||
chmod +x "$executable_path"
|
||||
write_info_plist "$plist_path" "$bundle_id" "$app_name"
|
||||
create_blob "$bundle_path/Contents/Resources/fixture.dat" 128
|
||||
}
|
||||
|
||||
create_leftovers() {
|
||||
local app_name="$1"
|
||||
local bundle_id="$2"
|
||||
local categories="$3"
|
||||
|
||||
IFS=',' read -r -a parts <<< "$categories"
|
||||
for category in "${parts[@]}"; do
|
||||
case "$category" in
|
||||
support)
|
||||
create_blob "$SUPPORT_ROOT/$bundle_id/settings.json" 32
|
||||
;;
|
||||
caches)
|
||||
create_blob "$CACHE_ROOT/$bundle_id/cache.bin" 48
|
||||
;;
|
||||
preferences)
|
||||
create_blob "$PREFERENCES_ROOT/$bundle_id.plist" 4
|
||||
;;
|
||||
logs)
|
||||
create_blob "$LOG_ROOT/$bundle_id/runtime.log" 24
|
||||
;;
|
||||
launch)
|
||||
mkdir -p "$LAUNCH_AGENTS_ROOT"
|
||||
cat > "$LAUNCH_AGENTS_ROOT/$bundle_id.plist" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>${bundle_id}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/bin/true</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
;;
|
||||
saved-state)
|
||||
create_blob "$STATE_ROOT/$bundle_id.savedState/data.data" 8
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
cleanup_fixture() {
|
||||
local app_name="$1"
|
||||
local bundle_id="$2"
|
||||
|
||||
rm -rf \
|
||||
"$APPS_ROOT/${app_name}.app" \
|
||||
"$SUPPORT_ROOT/$bundle_id" \
|
||||
"$CACHE_ROOT/$bundle_id" \
|
||||
"$PREFERENCES_ROOT/$bundle_id.plist" \
|
||||
"$LOG_ROOT/$bundle_id" \
|
||||
"$STATE_ROOT/$bundle_id.savedState" \
|
||||
"$LAUNCH_AGENTS_ROOT/$bundle_id.plist"
|
||||
}
|
||||
|
||||
print_status() {
|
||||
local found=false
|
||||
for fixture in "${FIXTURES[@]}"; do
|
||||
IFS='|' read -r app_name bundle_id categories <<< "$fixture"
|
||||
local bundle_path="$APPS_ROOT/${app_name}.app"
|
||||
if [[ -d "$bundle_path" ]]; then
|
||||
found=true
|
||||
echo "Fixture: $app_name ($bundle_id)"
|
||||
du -sh "$bundle_path" "$SUPPORT_ROOT/$bundle_id" "$CACHE_ROOT/$bundle_id" \
|
||||
"$PREFERENCES_ROOT/$bundle_id.plist" "$LOG_ROOT/$bundle_id" \
|
||||
"$STATE_ROOT/$bundle_id.savedState" "$LAUNCH_AGENTS_ROOT/$bundle_id.plist" 2> /dev/null || true
|
||||
echo "Expected review-only categories: $categories"
|
||||
echo ""
|
||||
fi
|
||||
done
|
||||
if [[ "$found" == false ]]; then
|
||||
echo "No Apps manual fixtures found."
|
||||
fi
|
||||
}
|
||||
|
||||
create_fixtures() {
|
||||
for fixture in "${FIXTURES[@]}"; do
|
||||
IFS='|' read -r app_name bundle_id categories <<< "$fixture"
|
||||
cleanup_fixture "$app_name" "$bundle_id"
|
||||
create_app_bundle "$app_name" "$bundle_id"
|
||||
create_leftovers "$app_name" "$bundle_id" "$categories"
|
||||
done
|
||||
|
||||
echo "Created Apps manual fixtures:"
|
||||
print_status
|
||||
}
|
||||
|
||||
cleanup_fixtures() {
|
||||
for fixture in "${FIXTURES[@]}"; do
|
||||
IFS='|' read -r app_name bundle_id _ <<< "$fixture"
|
||||
cleanup_fixture "$app_name" "$bundle_id"
|
||||
done
|
||||
echo "Removed Apps manual fixtures."
|
||||
}
|
||||
|
||||
case "${1:-create}" in
|
||||
create)
|
||||
create_fixtures
|
||||
;;
|
||||
status)
|
||||
print_status
|
||||
;;
|
||||
cleanup)
|
||||
cleanup_fixtures
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [create|status|cleanup]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -34,35 +34,40 @@ run_ui_acceptance() {
|
||||
return 1
|
||||
}
|
||||
|
||||
echo "[1/10] Shared package tests"
|
||||
echo "[1/11] Shared package tests"
|
||||
swift test --package-path Packages
|
||||
|
||||
echo "[2/10] App package tests"
|
||||
echo "[2/11] App package tests"
|
||||
swift test --package-path Apps
|
||||
|
||||
echo "[3/10] Worker and helper builds"
|
||||
echo "[3/11] Worker and helper builds"
|
||||
swift build --package-path XPC
|
||||
swift test --package-path Helpers
|
||||
swift build --package-path Testing
|
||||
|
||||
echo "[4/10] Native packaging"
|
||||
echo "[4/11] Fixture automation scripts"
|
||||
bash -n ./scripts/atlas/smart-clean-manual-fixtures.sh
|
||||
bash -n ./scripts/atlas/apps-manual-fixtures.sh
|
||||
bash -n ./scripts/atlas/apps-evidence-acceptance.sh
|
||||
|
||||
echo "[5/11] Native packaging"
|
||||
./scripts/atlas/package-native.sh
|
||||
|
||||
echo "[5/10] Bundle structure verification"
|
||||
echo "[6/11] Bundle structure verification"
|
||||
./scripts/atlas/verify-bundle-contents.sh
|
||||
|
||||
echo "[6/10] DMG install verification"
|
||||
echo "[7/11] DMG install verification"
|
||||
KEEP_INSTALLED_APP=1 ./scripts/atlas/verify-dmg-install.sh
|
||||
|
||||
echo "[7/10] Installed app launch smoke"
|
||||
echo "[8/11] Installed app launch smoke"
|
||||
./scripts/atlas/verify-app-launch.sh
|
||||
|
||||
echo "[8/10] Native UI automation"
|
||||
echo "[9/11] Native UI automation"
|
||||
run_ui_acceptance
|
||||
|
||||
echo "[9/10] Signing preflight"
|
||||
echo "[10/11] Signing preflight"
|
||||
./scripts/atlas/signing-preflight.sh || true
|
||||
|
||||
echo "[10/10] Acceptance summary"
|
||||
echo "[11/11] Acceptance summary"
|
||||
echo "Artifacts available in dist/native"
|
||||
ls -lah dist/native
|
||||
|
||||
@@ -4,8 +4,11 @@ set -euo pipefail
|
||||
CACHE_ROOT="$HOME/Library/Caches/AtlasExecutionFixturesCache"
|
||||
LOG_ROOT="$HOME/Library/Logs/AtlasExecutionFixturesLogs"
|
||||
DERIVED_ROOT="$HOME/Library/Developer/Xcode/DerivedData/AtlasExecutionFixturesDerivedData"
|
||||
CORESIM_ROOT="$HOME/Library/Developer/CoreSimulator/Caches/AtlasExecutionFixturesCoreSimulator"
|
||||
PYCACHE_ROOT="$HOME/Library/Caches/AtlasExecutionFixturesPycache"
|
||||
PNPM_ROOT="$HOME/Library/pnpm/store/v3/files/AtlasExecutionFixturesPnpm"
|
||||
GRADLE_ROOT="$HOME/.gradle/caches/AtlasExecutionFixturesGradle"
|
||||
IVY_ROOT="$HOME/.ivy2/cache/AtlasExecutionFixturesIvy"
|
||||
|
||||
create_blob() {
|
||||
local path="$1"
|
||||
@@ -20,7 +23,7 @@ create_blob() {
|
||||
|
||||
print_status() {
|
||||
local existing=false
|
||||
for path in "$CACHE_ROOT" "$LOG_ROOT" "$DERIVED_ROOT" "$PYCACHE_ROOT" "$PNPM_ROOT"; do
|
||||
for path in "$CACHE_ROOT" "$LOG_ROOT" "$DERIVED_ROOT" "$CORESIM_ROOT" "$PYCACHE_ROOT" "$PNPM_ROOT" "$GRADLE_ROOT" "$IVY_ROOT"; do
|
||||
if [[ -e "$path" ]]; then
|
||||
existing=true
|
||||
du -sh "$path"
|
||||
@@ -39,9 +42,12 @@ create_fixtures() {
|
||||
create_blob "$CACHE_ROOT/cache-b.bin" 12
|
||||
create_blob "$LOG_ROOT/app.log" 8
|
||||
create_blob "$DERIVED_ROOT/Build/Logs/build-products.bin" 16
|
||||
create_blob "$CORESIM_ROOT/device-cache.db" 6
|
||||
mkdir -p "$PYCACHE_ROOT/project/__pycache__"
|
||||
create_blob "$PYCACHE_ROOT/project/__pycache__/sample.cpython-312.pyc" 4
|
||||
create_blob "$PNPM_ROOT/package.tgz" 10
|
||||
create_blob "$GRADLE_ROOT/modules.bin" 10
|
||||
create_blob "$IVY_ROOT/artifact.bin" 6
|
||||
|
||||
echo "Created Smart Clean manual fixtures:"
|
||||
print_status
|
||||
@@ -50,7 +56,7 @@ create_fixtures() {
|
||||
}
|
||||
|
||||
cleanup_fixtures() {
|
||||
rm -rf "$CACHE_ROOT" "$LOG_ROOT" "$DERIVED_ROOT" "$PYCACHE_ROOT" "$PNPM_ROOT"
|
||||
rm -rf "$CACHE_ROOT" "$LOG_ROOT" "$DERIVED_ROOT" "$CORESIM_ROOT" "$PYCACHE_ROOT" "$PNPM_ROOT" "$GRADLE_ROOT" "$IVY_ROOT"
|
||||
echo "Removed Smart Clean manual fixtures."
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user