-
Notifications
You must be signed in to change notification settings - Fork 42
Expand file tree
/
Copy pathhandle-patch-cleanup.mts
More file actions
166 lines (145 loc) · 4.69 KB
/
handle-patch-cleanup.mts
File metadata and controls
166 lines (145 loc) · 4.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import { promises as fs } from 'node:fs'
import path from 'node:path'
import { UTF8 } from '@socketsecurity/lib/constants/encoding'
import { getDefaultLogger } from '@socketsecurity/lib/logger'
import { DOT_SOCKET_DIR } from '@socketsecurity/lib/paths/dirnames'
import { MANIFEST_JSON } from '@socketsecurity/lib/paths/filenames'
import { normalizePath } from '@socketsecurity/lib/paths/normalize'
import { pluralize } from '@socketsecurity/lib/words'
import { PatchManifestSchema } from './manifest-schema.mts'
import { outputPatchCleanupResult } from './output-patch-cleanup-result.mts'
import { getErrorCause } from '../../utils/error/errors.mjs'
import {
cleanupBackups,
listAllPatches,
} from '../../utils/manifest/patch-backup.mts'
import type { OutputKind } from '../../types.mts'
import type { Spinner } from '@socketsecurity/lib/spinner'
const logger = getDefaultLogger()
export interface PatchCleanupData {
cleaned: string[]
}
export interface HandlePatchCleanupConfig {
all: boolean
cwd: string
outputKind: OutputKind
spinner: Spinner | null
uuid: string | undefined
}
export async function handlePatchCleanup({
all,
cwd,
outputKind,
spinner,
uuid,
}: HandlePatchCleanupConfig): Promise<void> {
try {
const cleaned: string[] = []
if (uuid) {
// Clean up specific UUID.
spinner?.start(`Cleaning up backups for ${uuid}`)
await cleanupBackups(uuid)
cleaned.push(uuid)
spinner?.stop()
if (outputKind === 'text') {
logger.log(`Cleaned up backups for ${uuid}`)
}
} else if (all) {
// Clean up all backups.
spinner?.start('Finding all patch backups')
const allPatchUuids = await listAllPatches()
if (allPatchUuids.length === 0) {
spinner?.stop()
if (outputKind === 'text') {
logger.log('No patch backups found')
}
} else {
spinner?.text(
`Cleaning up ${allPatchUuids.length} ${pluralize('patch', { count: allPatchUuids.length })}`,
)
for (const patchUuid of allPatchUuids) {
// eslint-disable-next-line no-await-in-loop
await cleanupBackups(patchUuid)
cleaned.push(patchUuid)
}
spinner?.stop()
if (outputKind === 'text') {
logger.log(
`Cleaned up backups for ${cleaned.length} ${pluralize('patch', { count: cleaned.length })}`,
)
}
}
} else {
// Clean up orphaned backups (not in manifest).
spinner?.start('Reading patch manifest')
const dotSocketDirPath = normalizePath(path.join(cwd, DOT_SOCKET_DIR))
const manifestPath = normalizePath(
path.join(dotSocketDirPath, MANIFEST_JSON),
)
const manifestContent = await fs.readFile(manifestPath, UTF8)
const manifestData = JSON.parse(manifestContent)
const validated = PatchManifestSchema.parse(manifestData)
// Get UUIDs from manifest.
const manifestUuids = new Set<string>()
for (const patch of Object.values(validated.patches)) {
if (patch.uuid) {
manifestUuids.add(patch.uuid)
}
}
spinner?.text('Finding all patch backups')
const allPatchUuids = await listAllPatches()
// Find orphaned UUIDs (in backups but not in manifest).
const orphanedUuids = allPatchUuids.filter(
patchUuid => !manifestUuids.has(patchUuid),
)
if (orphanedUuids.length === 0) {
spinner?.stop()
if (outputKind === 'text') {
logger.log('No orphaned patch backups found')
}
} else {
spinner?.text(
`Cleaning up ${orphanedUuids.length} orphaned ${pluralize('backup', { count: orphanedUuids.length })}`,
)
for (const patchUuid of orphanedUuids) {
// eslint-disable-next-line no-await-in-loop
await cleanupBackups(patchUuid)
cleaned.push(patchUuid)
}
spinner?.stop()
if (outputKind === 'text') {
logger.log(
`Cleaned up ${cleaned.length} orphaned ${pluralize('backup', { count: cleaned.length })}`,
)
}
}
}
await outputPatchCleanupResult(
{
ok: true,
data: { cleaned },
},
outputKind,
)
} catch (e) {
spinner?.stop()
let message = 'Failed to clean up patch backups'
let cause = getErrorCause(e)
if (e instanceof SyntaxError) {
message = `Invalid JSON in ${MANIFEST_JSON}`
cause = e.message
} else if (e instanceof Error && 'issues' in e) {
message = 'Schema validation failed'
cause = String(e)
}
await outputPatchCleanupResult(
{
ok: false,
code: 1,
message,
cause,
},
outputKind,
)
}
}