-
Notifications
You must be signed in to change notification settings - Fork 42
Expand file tree
/
Copy pathhandle-patch-get.mts
More file actions
136 lines (113 loc) · 3.74 KB
/
handle-patch-get.mts
File metadata and controls
136 lines (113 loc) · 3.74 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
import { existsSync, promises as fs } from 'node:fs'
import path from 'node:path'
import { UTF8 } from '@socketsecurity/lib/constants/encoding'
import { safeMkdir } from '@socketsecurity/lib/fs'
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 { PatchManifestSchema } from './manifest-schema.mts'
import { outputPatchGetResult } from './output-patch-get-result.mts'
import { getErrorCause, InputError } from '../../utils/error/errors.mjs'
import { normalizePurl } from '../../utils/purl/parse.mjs'
import type { OutputKind } from '../../types.mts'
import type { Spinner } from '@socketsecurity/lib/spinner'
export interface PatchGetData {
files: string[]
outputDir: string
purl: string
}
export interface HandlePatchGetConfig {
cwd: string
outputDir: string | undefined
outputKind: OutputKind
purl: string
spinner: Spinner | null
}
export async function handlePatchGet({
cwd,
outputDir,
outputKind,
purl,
spinner,
}: HandlePatchGetConfig): Promise<void> {
try {
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)
const normalizedPurl = normalizePurl(purl)
const patch = validated.patches[normalizedPurl]
if (!patch) {
spinner?.stop()
throw new InputError(`Patch not found for PURL: ${purl}`)
}
const targetDir = outputDir
? path.resolve(process.cwd(), outputDir)
: path.join(cwd, 'patches', normalizedPurl.replace(/[/:@]/g, '_'))
// Create output directory if it doesn't exist.
if (!existsSync(targetDir)) {
await safeMkdir(targetDir, { recursive: true })
}
spinner?.text('Copying patch files')
const copiedFiles: string[] = []
const blobsDir = normalizePath(path.join(dotSocketDirPath, 'blobs'))
for (const { 0: fileName, 1: fileInfo } of Object.entries(patch.files)) {
const blobPath = normalizePath(path.join(blobsDir, fileInfo.afterHash))
if (!existsSync(blobPath)) {
spinner?.stop()
throw new InputError(
`Patch file not found: ${fileInfo.afterHash} for ${fileName}`,
)
}
const targetFilePath = normalizePath(path.join(targetDir, fileName))
const targetFileDir = path.dirname(targetFilePath)
// Create subdirectories if needed.
if (!existsSync(targetFileDir)) {
// eslint-disable-next-line no-await-in-loop
await safeMkdir(targetFileDir, { recursive: true })
}
// eslint-disable-next-line no-await-in-loop
await fs.copyFile(blobPath, targetFilePath)
copiedFiles.push(fileName)
}
spinner?.stop()
await outputPatchGetResult(
{
ok: true,
data: {
files: copiedFiles,
outputDir: targetDir,
purl: normalizedPurl,
},
},
outputKind,
)
} catch (e) {
spinner?.stop()
if (e instanceof InputError) {
throw e
}
let message = 'Failed to get patch files'
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 outputPatchGetResult(
{
ok: false,
code: 1,
message,
cause,
},
outputKind,
)
}
}