@vltpkg/query 1.0.0-rc.32 → 1.0.0-rc.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/pseudo/hostname.js +3 -1
- package/dist/pseudo/scripts.js +1 -1
- package/package.json +19 -10
- package/skills/dss-query/REFERENCE.md +119 -0
- package/skills/dss-query/SKILL.md +190 -0
- package/skills/dss-query/evals/README.md +79 -0
- package/skills/dss-query/evals/evals.json +89 -0
- package/skills/dss-query/evals/grade.mjs +480 -0
- package/src/attribute.ts +186 -0
- package/src/combinator.ts +132 -0
- package/src/id.ts +46 -0
- package/src/index.ts +613 -0
- package/src/parser.ts +121 -0
- package/src/pseudo/abandoned.ts +9 -0
- package/src/pseudo/attr.ts +92 -0
- package/src/pseudo/built.ts +19 -0
- package/src/pseudo/confused.ts +29 -0
- package/src/pseudo/cve.ts +93 -0
- package/src/pseudo/cwe.ts +97 -0
- package/src/pseudo/debug.ts +9 -0
- package/src/pseudo/deprecated.ts +9 -0
- package/src/pseudo/dev.ts +18 -0
- package/src/pseudo/diff.ts +90 -0
- package/src/pseudo/dist.ts +144 -0
- package/src/pseudo/dynamic.ts +9 -0
- package/src/pseudo/empty.ts +15 -0
- package/src/pseudo/entropic.ts +9 -0
- package/src/pseudo/env.ts +6 -0
- package/src/pseudo/eval.ts +9 -0
- package/src/pseudo/fs.ts +9 -0
- package/src/pseudo/helpers.ts +106 -0
- package/src/pseudo/host.ts +111 -0
- package/src/pseudo/hostname.ts +156 -0
- package/src/pseudo/license.ts +134 -0
- package/src/pseudo/link.ts +30 -0
- package/src/pseudo/malware.ts +249 -0
- package/src/pseudo/minified.ts +9 -0
- package/src/pseudo/missing.ts +16 -0
- package/src/pseudo/native.ts +9 -0
- package/src/pseudo/network.ts +9 -0
- package/src/pseudo/obfuscated.ts +9 -0
- package/src/pseudo/optional.ts +18 -0
- package/src/pseudo/outdated.ts +305 -0
- package/src/pseudo/overridden.ts +20 -0
- package/src/pseudo/path.ts +146 -0
- package/src/pseudo/peer.ts +18 -0
- package/src/pseudo/prerelease.ts +47 -0
- package/src/pseudo/private.ts +19 -0
- package/src/pseudo/prod.ts +18 -0
- package/src/pseudo/published.ts +248 -0
- package/src/pseudo/registry.ts +29 -0
- package/src/pseudo/root.ts +19 -0
- package/src/pseudo/scanned.ts +20 -0
- package/src/pseudo/score.ts +186 -0
- package/src/pseudo/scripts.ts +54 -0
- package/src/pseudo/semver.ts +320 -0
- package/src/pseudo/severity.ts +231 -0
- package/src/pseudo/shell.ts +9 -0
- package/src/pseudo/shrinkwrap.ts +9 -0
- package/src/pseudo/spec.ts +131 -0
- package/src/pseudo/squat.ts +225 -0
- package/src/pseudo/suspicious.ts +9 -0
- package/src/pseudo/tracker.ts +9 -0
- package/src/pseudo/trivial.ts +9 -0
- package/src/pseudo/type.ts +26 -0
- package/src/pseudo/undesirable.ts +9 -0
- package/src/pseudo/unknown.ts +9 -0
- package/src/pseudo/unmaintained.ts +9 -0
- package/src/pseudo/unpopular.ts +9 -0
- package/src/pseudo/unstable.ts +9 -0
- package/src/pseudo/vulnerable.ts +26 -0
- package/src/pseudo/workspace.ts +22 -0
- package/src/pseudo.ts +413 -0
- package/src/types.ts +164 -0
- package/tap-snapshots/test/attribute.ts.test.cjs +304 -0
- package/tap-snapshots/test/combinator.ts.test.cjs +333 -0
- package/tap-snapshots/test/id.ts.test.cjs +118 -0
- package/tap-snapshots/test/parser.ts.test.cjs +344 -0
- package/tap-snapshots/test/pseudo/abandoned.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/attr.ts.test.cjs +161 -0
- package/tap-snapshots/test/pseudo/built.ts.test.cjs +70 -0
- package/tap-snapshots/test/pseudo/confused.ts.test.cjs +32 -0
- package/tap-snapshots/test/pseudo/cve.ts.test.cjs +49 -0
- package/tap-snapshots/test/pseudo/cwe.ts.test.cjs +49 -0
- package/tap-snapshots/test/pseudo/debug.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/deprecated.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/dist.ts.test.cjs +30 -0
- package/tap-snapshots/test/pseudo/dynamic.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/empty.ts.test.cjs +50 -0
- package/tap-snapshots/test/pseudo/entropic.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/env.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/eval.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/fs.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/helpers.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/hostname.ts.test.cjs +148 -0
- package/tap-snapshots/test/pseudo/license.ts.test.cjs +41 -0
- package/tap-snapshots/test/pseudo/link.ts.test.cjs +36 -0
- package/tap-snapshots/test/pseudo/malware.ts.test.cjs +191 -0
- package/tap-snapshots/test/pseudo/minified.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/missing.ts.test.cjs +30 -0
- package/tap-snapshots/test/pseudo/native.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/network.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/obfuscated.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/outdated.ts.test.cjs +130 -0
- package/tap-snapshots/test/pseudo/overridden.ts.test.cjs +75 -0
- package/tap-snapshots/test/pseudo/prerelease.ts.test.cjs +101 -0
- package/tap-snapshots/test/pseudo/private.ts.test.cjs +38 -0
- package/tap-snapshots/test/pseudo/published.ts.test.cjs +171 -0
- package/tap-snapshots/test/pseudo/registry.ts.test.cjs +68 -0
- package/tap-snapshots/test/pseudo/root.ts.test.cjs +24 -0
- package/tap-snapshots/test/pseudo/scanned.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/score.ts.test.cjs +239 -0
- package/tap-snapshots/test/pseudo/scripts.ts.test.cjs +61 -0
- package/tap-snapshots/test/pseudo/semver.ts.test.cjs +575 -0
- package/tap-snapshots/test/pseudo/severity.ts.test.cjs +95 -0
- package/tap-snapshots/test/pseudo/shell.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/shrinkwrap.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/spec.ts.test.cjs +148 -0
- package/tap-snapshots/test/pseudo/squat.ts.test.cjs +129 -0
- package/tap-snapshots/test/pseudo/suspicious.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/tracker.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/trivial.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/type.ts.test.cjs +51 -0
- package/tap-snapshots/test/pseudo/undesirable.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/unknown.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/unmaintained.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/unpopular.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/unstable.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/vulnerable.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/workspace.ts.test.cjs +19 -0
- package/tap-snapshots/test/pseudo.ts.test.cjs +1247 -0
- package/test/attribute.ts +266 -0
- package/test/combinator.ts +168 -0
- package/test/fixtures/graph.ts +999 -0
- package/test/fixtures/selector.ts +103 -0
- package/test/fixtures/types.ts +7 -0
- package/test/id.ts +102 -0
- package/test/index.ts +992 -0
- package/test/parser.ts +117 -0
- package/test/pseudo/abandoned.ts +98 -0
- package/test/pseudo/attr.ts +298 -0
- package/test/pseudo/built.ts +214 -0
- package/test/pseudo/confused.ts +160 -0
- package/test/pseudo/cve.ts +249 -0
- package/test/pseudo/cwe.ts +255 -0
- package/test/pseudo/debug.ts +98 -0
- package/test/pseudo/deprecated.ts +98 -0
- package/test/pseudo/dev.ts +61 -0
- package/test/pseudo/diff.ts +417 -0
- package/test/pseudo/dist.ts +215 -0
- package/test/pseudo/dynamic.ts +98 -0
- package/test/pseudo/empty.ts +108 -0
- package/test/pseudo/entropic.ts +101 -0
- package/test/pseudo/env.ts +98 -0
- package/test/pseudo/eval.ts +98 -0
- package/test/pseudo/fs.ts +98 -0
- package/test/pseudo/helpers.ts +370 -0
- package/test/pseudo/host-context.ts +276 -0
- package/test/pseudo/hostname.ts +314 -0
- package/test/pseudo/license.ts +265 -0
- package/test/pseudo/link.ts +94 -0
- package/test/pseudo/malware.ts +431 -0
- package/test/pseudo/minified.ts +98 -0
- package/test/pseudo/missing.ts +111 -0
- package/test/pseudo/native.ts +98 -0
- package/test/pseudo/network.ts +98 -0
- package/test/pseudo/obfuscated.ts +98 -0
- package/test/pseudo/optional.ts +73 -0
- package/test/pseudo/outdated.ts +331 -0
- package/test/pseudo/overridden.ts +317 -0
- package/test/pseudo/path.ts +680 -0
- package/test/pseudo/peer.ts +101 -0
- package/test/pseudo/prerelease.ts +279 -0
- package/test/pseudo/private.ts +108 -0
- package/test/pseudo/prod.ts +61 -0
- package/test/pseudo/published.ts +557 -0
- package/test/pseudo/registry.ts +122 -0
- package/test/pseudo/root.ts +66 -0
- package/test/pseudo/scanned.ts +78 -0
- package/test/pseudo/score.ts +591 -0
- package/test/pseudo/scripts.ts +294 -0
- package/test/pseudo/semver.ts +822 -0
- package/test/pseudo/severity.ts +310 -0
- package/test/pseudo/shell.ts +98 -0
- package/test/pseudo/shrinkwrap.ts +98 -0
- package/test/pseudo/spec.ts +525 -0
- package/test/pseudo/squat.ts +565 -0
- package/test/pseudo/suspicious.ts +98 -0
- package/test/pseudo/tracker.ts +98 -0
- package/test/pseudo/trivial.ts +98 -0
- package/test/pseudo/type.ts +105 -0
- package/test/pseudo/undesirable.ts +98 -0
- package/test/pseudo/unknown.ts +98 -0
- package/test/pseudo/unmaintained.ts +98 -0
- package/test/pseudo/unpopular.ts +98 -0
- package/test/pseudo/unstable.ts +98 -0
- package/test/pseudo/vulnerable.ts +130 -0
- package/test/pseudo/workspace.ts +111 -0
- package/test/pseudo.ts +558 -0
- package/tsconfig.json +3 -0
- package/tsconfig.publish.json +19 -0
- package/tsconfig.publish.tsbuildinfo +1 -0
- package/typedoc.mjs +2 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import {
|
|
2
|
+
satisfies,
|
|
3
|
+
gt,
|
|
4
|
+
gte,
|
|
5
|
+
lt,
|
|
6
|
+
lte,
|
|
7
|
+
eq,
|
|
8
|
+
neq,
|
|
9
|
+
intersects,
|
|
10
|
+
subset,
|
|
11
|
+
parse,
|
|
12
|
+
parseRange,
|
|
13
|
+
} from '@vltpkg/semver'
|
|
14
|
+
import type { Version } from '@vltpkg/semver'
|
|
15
|
+
import { error } from '@vltpkg/error-cause'
|
|
16
|
+
import { asError } from '@vltpkg/types'
|
|
17
|
+
import { parseInternals as parseAttrInternals } from './attr.ts'
|
|
18
|
+
import type { AttrInternals } from './attr.ts'
|
|
19
|
+
import { getManifestPropertyValues } from '../attribute.ts'
|
|
20
|
+
import {
|
|
21
|
+
asAttributeNode,
|
|
22
|
+
asPostcssNodeWithChildren,
|
|
23
|
+
asPseudoNode,
|
|
24
|
+
asStringNode,
|
|
25
|
+
asTagNode,
|
|
26
|
+
isAttributeNode,
|
|
27
|
+
isPseudoNode,
|
|
28
|
+
isStringNode,
|
|
29
|
+
isTagNode,
|
|
30
|
+
} from '@vltpkg/dss-parser'
|
|
31
|
+
import {
|
|
32
|
+
removeEdge,
|
|
33
|
+
removeNode,
|
|
34
|
+
removeQuotes,
|
|
35
|
+
removeUnlinkedNodes,
|
|
36
|
+
} from './helpers.ts'
|
|
37
|
+
import type { ParserState } from '../types.ts'
|
|
38
|
+
import type { PostcssNode } from '@vltpkg/dss-parser'
|
|
39
|
+
|
|
40
|
+
export type SemverInternals = {
|
|
41
|
+
semverValue: string
|
|
42
|
+
semverFunction: SemverComparatorFn
|
|
43
|
+
semverRangeFunction: SemverRangeComparatorFn | undefined
|
|
44
|
+
compareAttribute: SemverCompareAttribute
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type SemverFunctionNames =
|
|
48
|
+
| 'satisfies'
|
|
49
|
+
| 'gt'
|
|
50
|
+
| 'gte'
|
|
51
|
+
| 'lt'
|
|
52
|
+
| 'lte'
|
|
53
|
+
| 'eq'
|
|
54
|
+
| 'neq'
|
|
55
|
+
| 'intersects'
|
|
56
|
+
| 'subset'
|
|
57
|
+
export type SemverComparatorFn = (
|
|
58
|
+
version: Version | string,
|
|
59
|
+
range: string,
|
|
60
|
+
) => boolean
|
|
61
|
+
export type SemverRangeComparatorFn = (
|
|
62
|
+
r1: string,
|
|
63
|
+
r2: string,
|
|
64
|
+
) => boolean
|
|
65
|
+
export type SemverCompareAttribute =
|
|
66
|
+
Pick<AttrInternals, 'attribute' | 'properties'> | undefined
|
|
67
|
+
|
|
68
|
+
const semverFunctionNames = new Set([
|
|
69
|
+
'satisfies',
|
|
70
|
+
'gt',
|
|
71
|
+
'gte',
|
|
72
|
+
'lt',
|
|
73
|
+
'lte',
|
|
74
|
+
'eq',
|
|
75
|
+
'neq',
|
|
76
|
+
'intersects',
|
|
77
|
+
'subset',
|
|
78
|
+
])
|
|
79
|
+
export const isSemverFunctionName = (
|
|
80
|
+
name: string,
|
|
81
|
+
): name is SemverFunctionNames => semverFunctionNames.has(name)
|
|
82
|
+
|
|
83
|
+
export const asSemverFunctionName = (
|
|
84
|
+
name: string,
|
|
85
|
+
): SemverFunctionNames => {
|
|
86
|
+
if (!isSemverFunctionName(name)) {
|
|
87
|
+
throw error('Invalid semver function name', {
|
|
88
|
+
found: name,
|
|
89
|
+
validOptions: Array.from(semverFunctionNames),
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
return name
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const semverFunctions = new Map<
|
|
96
|
+
SemverFunctionNames,
|
|
97
|
+
SemverComparatorFn
|
|
98
|
+
>([
|
|
99
|
+
['satisfies', satisfies],
|
|
100
|
+
['gt', gt],
|
|
101
|
+
['gte', gte],
|
|
102
|
+
['lt', lt],
|
|
103
|
+
['lte', lte],
|
|
104
|
+
['eq', eq],
|
|
105
|
+
['neq', neq],
|
|
106
|
+
])
|
|
107
|
+
|
|
108
|
+
const semverRangeFunctions = new Map<
|
|
109
|
+
SemverFunctionNames,
|
|
110
|
+
SemverRangeComparatorFn
|
|
111
|
+
>([
|
|
112
|
+
['intersects', intersects],
|
|
113
|
+
['subset', subset],
|
|
114
|
+
])
|
|
115
|
+
|
|
116
|
+
export const parseInternals = (
|
|
117
|
+
nodes: PostcssNode[],
|
|
118
|
+
loose: boolean,
|
|
119
|
+
): SemverInternals => {
|
|
120
|
+
// tries to parse the first param as a string node, otherwise defaults
|
|
121
|
+
// to reading all postcss nodes as just strings, since it just means
|
|
122
|
+
// the value was defined as an unquoted string
|
|
123
|
+
let semverValue = ''
|
|
124
|
+
try {
|
|
125
|
+
semverValue = removeQuotes(
|
|
126
|
+
asStringNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])
|
|
127
|
+
.value,
|
|
128
|
+
)
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if (
|
|
131
|
+
asError(err).message === 'Mismatching query node' &&
|
|
132
|
+
isTagNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])
|
|
133
|
+
) {
|
|
134
|
+
// Handle tag node (unquoted values like >=2.0.0)
|
|
135
|
+
const tagNode = asTagNode(
|
|
136
|
+
asPostcssNodeWithChildren(nodes[0]).nodes[0],
|
|
137
|
+
)
|
|
138
|
+
semverValue = tagNode.value
|
|
139
|
+
} else {
|
|
140
|
+
throw err
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// second param is the function name
|
|
145
|
+
let fnName: SemverFunctionNames = 'satisfies'
|
|
146
|
+
try {
|
|
147
|
+
// if there is a second node defined, try to parse it as a string node
|
|
148
|
+
// first and if that fails, then parse it as a tag node which just means
|
|
149
|
+
// it was defined as an unquoted string
|
|
150
|
+
if (nodes[1]) {
|
|
151
|
+
try {
|
|
152
|
+
fnName = asSemverFunctionName(
|
|
153
|
+
removeQuotes(
|
|
154
|
+
asStringNode(asPostcssNodeWithChildren(nodes[1]).nodes[0])
|
|
155
|
+
.value,
|
|
156
|
+
),
|
|
157
|
+
)
|
|
158
|
+
} catch (err) {
|
|
159
|
+
if (asError(err).message === 'Mismatching query node') {
|
|
160
|
+
fnName = asSemverFunctionName(
|
|
161
|
+
asTagNode(asPostcssNodeWithChildren(nodes[1]).nodes[0])
|
|
162
|
+
.value,
|
|
163
|
+
)
|
|
164
|
+
} else {
|
|
165
|
+
throw err
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} catch (e) {
|
|
170
|
+
// allow invalid semver function names in loose mode, defaults to satisfies
|
|
171
|
+
if (!loose) {
|
|
172
|
+
throw e
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const semverFunction = semverFunctions.get(fnName)
|
|
177
|
+
const semverRangeFunction = semverRangeFunctions.get(fnName)
|
|
178
|
+
// the following should never happen as long as the semver function names
|
|
179
|
+
// type and Set are correctly mirroring each other values
|
|
180
|
+
/* c8 ignore start */
|
|
181
|
+
if (!semverFunction && !semverRangeFunction) {
|
|
182
|
+
throw error('Invalid semver function name', {
|
|
183
|
+
found: fnName,
|
|
184
|
+
validOptions: [
|
|
185
|
+
...Array.from(semverFunctions.keys()),
|
|
186
|
+
...Array.from(semverRangeFunctions.keys()),
|
|
187
|
+
],
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
/* c8 ignore stop */
|
|
191
|
+
|
|
192
|
+
// optional third param is the compare value
|
|
193
|
+
let compareAttribute: SemverCompareAttribute
|
|
194
|
+
if (nodes[2]) {
|
|
195
|
+
const parentNode = asPostcssNodeWithChildren(nodes[2])
|
|
196
|
+
const currNode = parentNode.nodes[0]
|
|
197
|
+
if (isAttributeNode(currNode)) {
|
|
198
|
+
const { attribute } = asAttributeNode(currNode)
|
|
199
|
+
compareAttribute = {
|
|
200
|
+
attribute,
|
|
201
|
+
properties: [attribute],
|
|
202
|
+
}
|
|
203
|
+
} else if (isPseudoNode(currNode)) {
|
|
204
|
+
compareAttribute = parseAttrInternals(
|
|
205
|
+
asPseudoNode(currNode).nodes,
|
|
206
|
+
)
|
|
207
|
+
} else if (isStringNode(currNode)) {
|
|
208
|
+
const attribute = removeQuotes(asStringNode(currNode).value)
|
|
209
|
+
compareAttribute = {
|
|
210
|
+
attribute,
|
|
211
|
+
properties: [attribute],
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
semverValue,
|
|
218
|
+
semverFunction: semverFunction ?? satisfies,
|
|
219
|
+
semverRangeFunction,
|
|
220
|
+
compareAttribute,
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export const semverParser = async (state: ParserState) => {
|
|
225
|
+
let internals
|
|
226
|
+
try {
|
|
227
|
+
internals = parseInternals(
|
|
228
|
+
asPostcssNodeWithChildren(state.current).nodes,
|
|
229
|
+
!!state.loose,
|
|
230
|
+
)
|
|
231
|
+
} catch (err) {
|
|
232
|
+
throw error('Failed to parse :semver selector', {
|
|
233
|
+
cause: err,
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const {
|
|
238
|
+
semverValue,
|
|
239
|
+
semverFunction,
|
|
240
|
+
semverRangeFunction,
|
|
241
|
+
compareAttribute,
|
|
242
|
+
} = internals
|
|
243
|
+
|
|
244
|
+
// Range-vs-range functions (subset, intersects) operate on edges by default
|
|
245
|
+
if (semverRangeFunction) {
|
|
246
|
+
if (compareAttribute) {
|
|
247
|
+
// Compare the semverValue against a manifest property value as ranges
|
|
248
|
+
for (const node of state.partial.nodes) {
|
|
249
|
+
const compareValues = getManifestPropertyValues(
|
|
250
|
+
node,
|
|
251
|
+
compareAttribute.properties,
|
|
252
|
+
compareAttribute.attribute,
|
|
253
|
+
)
|
|
254
|
+
const compareValue = compareValues?.[0]
|
|
255
|
+
if (
|
|
256
|
+
!compareValue ||
|
|
257
|
+
!semverRangeFunction(compareValue, semverValue)
|
|
258
|
+
) {
|
|
259
|
+
removeNode(state, node)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
// Default: operate on edges using bareSpec
|
|
264
|
+
for (const edge of state.partial.edges) {
|
|
265
|
+
const edgeRange = edge.spec.semver ?? edge.spec.bareSpec
|
|
266
|
+
if (
|
|
267
|
+
!edgeRange ||
|
|
268
|
+
!semverRangeFunction(edgeRange, semverValue)
|
|
269
|
+
) {
|
|
270
|
+
removeEdge(state, edge)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
removeUnlinkedNodes(state)
|
|
274
|
+
}
|
|
275
|
+
return state
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
for (const node of state.partial.nodes) {
|
|
279
|
+
if (compareAttribute) {
|
|
280
|
+
const compareValues = getManifestPropertyValues(
|
|
281
|
+
node,
|
|
282
|
+
compareAttribute.properties,
|
|
283
|
+
compareAttribute.attribute,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
// if the provided semver value is a fixed semver version and the
|
|
287
|
+
// compare attribute is resolving to a range value, then we flip the
|
|
288
|
+
// order of comparison, in case it's a "satisfies" function check
|
|
289
|
+
const compareValue = compareValues?.[0]
|
|
290
|
+
const semverValueVersion = parse(semverValue)
|
|
291
|
+
const compareValueRange =
|
|
292
|
+
compareValue && parseRange(compareValue)
|
|
293
|
+
if (
|
|
294
|
+
semverFunction === satisfies &&
|
|
295
|
+
semverValueVersion &&
|
|
296
|
+
compareValueRange
|
|
297
|
+
) {
|
|
298
|
+
if (!satisfies(semverValueVersion, compareValueRange)) {
|
|
299
|
+
removeNode(state, node)
|
|
300
|
+
}
|
|
301
|
+
// otherwise just compares the read attribute to the semver value
|
|
302
|
+
} else if (
|
|
303
|
+
!compareValue ||
|
|
304
|
+
!semverFunction(compareValue, semverValue)
|
|
305
|
+
) {
|
|
306
|
+
removeNode(state, node)
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
const manifestVersion = node.manifest?.version
|
|
310
|
+
if (
|
|
311
|
+
!manifestVersion ||
|
|
312
|
+
!semverFunction(manifestVersion, semverValue)
|
|
313
|
+
) {
|
|
314
|
+
removeNode(state, node)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return state
|
|
320
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { error } from '@vltpkg/error-cause'
|
|
2
|
+
import {
|
|
3
|
+
asPostcssNodeWithChildren,
|
|
4
|
+
asStringNode,
|
|
5
|
+
asTagNode,
|
|
6
|
+
isStringNode,
|
|
7
|
+
isTagNode,
|
|
8
|
+
} from '@vltpkg/dss-parser'
|
|
9
|
+
import {
|
|
10
|
+
assertSecurityArchive,
|
|
11
|
+
removeDanglingEdges,
|
|
12
|
+
removeNode,
|
|
13
|
+
removeQuotes,
|
|
14
|
+
} from './helpers.ts'
|
|
15
|
+
import type { ParserState } from '../types.ts'
|
|
16
|
+
import type { PostcssNode } from '@vltpkg/dss-parser'
|
|
17
|
+
|
|
18
|
+
export type SeverityKinds =
|
|
19
|
+
| '0'
|
|
20
|
+
| '1'
|
|
21
|
+
| '2'
|
|
22
|
+
| '3'
|
|
23
|
+
| 'critical'
|
|
24
|
+
| 'high'
|
|
25
|
+
| 'medium'
|
|
26
|
+
| 'low'
|
|
27
|
+
| undefined
|
|
28
|
+
|
|
29
|
+
export type SeverityAlertTypes =
|
|
30
|
+
| 'criticalCVE'
|
|
31
|
+
| 'cve'
|
|
32
|
+
| 'potentialVulnerability'
|
|
33
|
+
| 'mildCVE'
|
|
34
|
+
| undefined
|
|
35
|
+
|
|
36
|
+
export type SeverityComparator = '>' | '<' | '>=' | '<=' | undefined
|
|
37
|
+
|
|
38
|
+
const kindsMap = new Map<SeverityKinds, SeverityAlertTypes>([
|
|
39
|
+
['critical', 'criticalCVE'],
|
|
40
|
+
['high', 'cve'],
|
|
41
|
+
['medium', 'potentialVulnerability'],
|
|
42
|
+
['low', 'mildCVE'],
|
|
43
|
+
['0', 'criticalCVE'],
|
|
44
|
+
['1', 'cve'],
|
|
45
|
+
['2', 'potentialVulnerability'],
|
|
46
|
+
['3', 'mildCVE'],
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
// Map numerical values to their respective kinds for comparison operations
|
|
50
|
+
const kindLevelMap = new Map<SeverityKinds, number>([
|
|
51
|
+
['critical', 0],
|
|
52
|
+
['high', 1],
|
|
53
|
+
['medium', 2],
|
|
54
|
+
['low', 3],
|
|
55
|
+
['0', 0],
|
|
56
|
+
['1', 1],
|
|
57
|
+
['2', 2],
|
|
58
|
+
['3', 3],
|
|
59
|
+
])
|
|
60
|
+
|
|
61
|
+
const kinds = new Set(kindsMap.keys())
|
|
62
|
+
|
|
63
|
+
export const isSeverityKind = (
|
|
64
|
+
value?: string,
|
|
65
|
+
): value is SeverityKinds => kinds.has(value as SeverityKinds)
|
|
66
|
+
|
|
67
|
+
export const asSeverityKind = (value?: string): SeverityKinds => {
|
|
68
|
+
if (!isSeverityKind(value)) {
|
|
69
|
+
throw error('Expected a valid severity kind', {
|
|
70
|
+
found: value,
|
|
71
|
+
validOptions: Array.from(kinds),
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
return value
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const parseInternals = (
|
|
78
|
+
nodes: PostcssNode[],
|
|
79
|
+
): {
|
|
80
|
+
kind: SeverityKinds
|
|
81
|
+
comparator: SeverityComparator
|
|
82
|
+
} => {
|
|
83
|
+
let kind: SeverityKinds
|
|
84
|
+
let comparator: SeverityComparator
|
|
85
|
+
|
|
86
|
+
if (nodes.length === 0) {
|
|
87
|
+
throw error('Missing severity kind parameter')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let kindValue = ''
|
|
91
|
+
if (isStringNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])) {
|
|
92
|
+
kindValue = removeQuotes(
|
|
93
|
+
asStringNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])
|
|
94
|
+
.value,
|
|
95
|
+
)
|
|
96
|
+
} else if (
|
|
97
|
+
isTagNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])
|
|
98
|
+
) {
|
|
99
|
+
kindValue = asTagNode(
|
|
100
|
+
asPostcssNodeWithChildren(nodes[0]).nodes[0],
|
|
101
|
+
).value
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Extract comparator if present
|
|
105
|
+
if (kindValue.startsWith('>=')) {
|
|
106
|
+
comparator = '>='
|
|
107
|
+
kindValue = kindValue.substring(2)
|
|
108
|
+
} else if (kindValue.startsWith('<=')) {
|
|
109
|
+
comparator = '<='
|
|
110
|
+
kindValue = kindValue.substring(2)
|
|
111
|
+
} else if (kindValue.startsWith('>')) {
|
|
112
|
+
comparator = '>'
|
|
113
|
+
kindValue = kindValue.substring(1)
|
|
114
|
+
} else if (kindValue.startsWith('<')) {
|
|
115
|
+
comparator = '<'
|
|
116
|
+
kindValue = kindValue.substring(1)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Parse kind value
|
|
120
|
+
if (kindValue) {
|
|
121
|
+
if (isSeverityKind(kindValue)) {
|
|
122
|
+
kind = kindValue
|
|
123
|
+
} else {
|
|
124
|
+
throw error(
|
|
125
|
+
'Expected a valid severity kind or number between 0-3',
|
|
126
|
+
{
|
|
127
|
+
found: kindValue,
|
|
128
|
+
},
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { kind, comparator }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export const severity = async (state: ParserState) => {
|
|
137
|
+
assertSecurityArchive(state, 'severity')
|
|
138
|
+
|
|
139
|
+
let internals
|
|
140
|
+
try {
|
|
141
|
+
internals = parseInternals(
|
|
142
|
+
asPostcssNodeWithChildren(state.current).nodes,
|
|
143
|
+
)
|
|
144
|
+
} catch (err) {
|
|
145
|
+
throw error('Failed to parse :severity selector', { cause: err })
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const { kind, comparator } = internals
|
|
149
|
+
|
|
150
|
+
for (const node of state.partial.nodes) {
|
|
151
|
+
const report = state.securityArchive.get(node.id)
|
|
152
|
+
// Always exclude nodes that don't have security data or alerts
|
|
153
|
+
if (!report?.alerts || report.alerts.length === 0) {
|
|
154
|
+
removeNode(state, node)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for (const node of state.partial.nodes) {
|
|
159
|
+
const report = state.securityArchive.get(node.id)
|
|
160
|
+
let exclude = true
|
|
161
|
+
|
|
162
|
+
if (report) {
|
|
163
|
+
if (comparator) {
|
|
164
|
+
// retrieve the value to compare against
|
|
165
|
+
const kindLevel = kindLevelMap.get(kind)
|
|
166
|
+
// the kindLevel value has already been validated at this point
|
|
167
|
+
// and thus can never return an undefined/falsy value but ts doesn't
|
|
168
|
+
// know about that, so we have the extra check here
|
|
169
|
+
/* c8 ignore next - impossible */
|
|
170
|
+
if (!kindLevel) break
|
|
171
|
+
|
|
172
|
+
// Check each alert to find any that match our comparison criteria
|
|
173
|
+
for (const alert of report.alerts) {
|
|
174
|
+
// Get the numerical value of the alert type
|
|
175
|
+
const alertType = alert.type
|
|
176
|
+
|
|
177
|
+
// retrieve a key to the current alert level to be compared against
|
|
178
|
+
const currentAlertLevelKey = [...kindsMap.entries()].find(
|
|
179
|
+
([_, alertValue]) => alertValue === alertType,
|
|
180
|
+
)?.[0]
|
|
181
|
+
|
|
182
|
+
// perform the comparison based on the user-provided kindLevel
|
|
183
|
+
if (currentAlertLevelKey) {
|
|
184
|
+
const currentAlertLevel = kindLevelMap.get(
|
|
185
|
+
currentAlertLevelKey,
|
|
186
|
+
)
|
|
187
|
+
/* c8 ignore next - impossible but ts doesn't know */
|
|
188
|
+
if (currentAlertLevel == null) continue
|
|
189
|
+
|
|
190
|
+
switch (comparator) {
|
|
191
|
+
case '>':
|
|
192
|
+
if (currentAlertLevel > kindLevel) {
|
|
193
|
+
exclude = false
|
|
194
|
+
}
|
|
195
|
+
break
|
|
196
|
+
case '<':
|
|
197
|
+
if (currentAlertLevel < kindLevel) {
|
|
198
|
+
exclude = false
|
|
199
|
+
}
|
|
200
|
+
break
|
|
201
|
+
case '>=':
|
|
202
|
+
if (currentAlertLevel >= kindLevel) {
|
|
203
|
+
exclude = false
|
|
204
|
+
}
|
|
205
|
+
break
|
|
206
|
+
case '<=':
|
|
207
|
+
if (currentAlertLevel <= kindLevel) {
|
|
208
|
+
exclude = false
|
|
209
|
+
}
|
|
210
|
+
break
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
// Original exact match behavior
|
|
216
|
+
const alertName = kindsMap.get(kind)
|
|
217
|
+
exclude = !report.alerts.some(
|
|
218
|
+
alert => alert.type === alertName,
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (exclude) {
|
|
224
|
+
removeNode(state, node)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
removeDanglingEdges(state)
|
|
229
|
+
|
|
230
|
+
return state
|
|
231
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { error } from '@vltpkg/error-cause'
|
|
2
|
+
import { asError } from '@vltpkg/types'
|
|
3
|
+
import {
|
|
4
|
+
asPostcssNodeWithChildren,
|
|
5
|
+
asStringNode,
|
|
6
|
+
asTagNode,
|
|
7
|
+
isTagNode,
|
|
8
|
+
} from '@vltpkg/dss-parser'
|
|
9
|
+
import {
|
|
10
|
+
removeEdge,
|
|
11
|
+
removeUnlinkedNodes,
|
|
12
|
+
removeQuotes,
|
|
13
|
+
} from './helpers.ts'
|
|
14
|
+
import type { ParserState } from '../types.ts'
|
|
15
|
+
import type { PostcssNode } from '@vltpkg/dss-parser'
|
|
16
|
+
|
|
17
|
+
export type SpecInternals = {
|
|
18
|
+
specValue: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const parseInternals = (
|
|
22
|
+
nodes: PostcssNode[],
|
|
23
|
+
): SpecInternals => {
|
|
24
|
+
// tries to parse the first param as a string node, otherwise defaults
|
|
25
|
+
// to reading all postcss nodes as just strings, since it just means
|
|
26
|
+
// the value was defined as an unquoted string
|
|
27
|
+
let specValue = ''
|
|
28
|
+
|
|
29
|
+
if (nodes.length === 0) {
|
|
30
|
+
throw new Error('No nodes provided to parseInternals')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const firstNode = asPostcssNodeWithChildren(nodes[0])
|
|
34
|
+
|
|
35
|
+
if (firstNode.nodes.length === 0) {
|
|
36
|
+
throw new Error('First node has no child nodes')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Try to parse as a quoted string first (single string node)
|
|
40
|
+
if (firstNode.nodes.length === 1) {
|
|
41
|
+
const targetNode = firstNode.nodes[0]
|
|
42
|
+
try {
|
|
43
|
+
specValue = removeQuotes(asStringNode(targetNode).value)
|
|
44
|
+
return { specValue }
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (
|
|
47
|
+
asError(err).message === 'Mismatching query node' &&
|
|
48
|
+
isTagNode(targetNode)
|
|
49
|
+
/* c8 ignore start */
|
|
50
|
+
) {
|
|
51
|
+
// Handle simple unquoted single-token values like "1"
|
|
52
|
+
const tagNode = asTagNode(targetNode)
|
|
53
|
+
specValue = tagNode.value
|
|
54
|
+
return { specValue }
|
|
55
|
+
/* c8 ignore stop */
|
|
56
|
+
} else {
|
|
57
|
+
throw err
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Handle unquoted complex values that get parsed into multiple nodes
|
|
63
|
+
// This happens when unquoted values contain dots, like ^1.0.0 which becomes:
|
|
64
|
+
// - Tag: "^1"
|
|
65
|
+
// - ClassName: "0" (from .0)
|
|
66
|
+
// - ClassName: "0" (from .0)
|
|
67
|
+
// We need to reconstruct the original value by concatenating all nodes
|
|
68
|
+
const parts: string[] = []
|
|
69
|
+
|
|
70
|
+
for (const node of firstNode.nodes) {
|
|
71
|
+
switch (node.type) {
|
|
72
|
+
case 'tag':
|
|
73
|
+
parts.push(asTagNode(node).value)
|
|
74
|
+
break
|
|
75
|
+
case 'class':
|
|
76
|
+
// Class nodes represent .className in CSS, so we need to add the dot back
|
|
77
|
+
parts.push('.' + node.value)
|
|
78
|
+
break
|
|
79
|
+
case 'id':
|
|
80
|
+
// ID nodes represent #idName in CSS, so we need to add the hash back
|
|
81
|
+
parts.push('#' + node.value)
|
|
82
|
+
break
|
|
83
|
+
case 'string':
|
|
84
|
+
parts.push(removeQuotes(asStringNode(node).value))
|
|
85
|
+
break
|
|
86
|
+
default:
|
|
87
|
+
// For other node types, try to get the value property or convert to string
|
|
88
|
+
/* c8 ignore next */
|
|
89
|
+
parts.push(node.value ?? String(node))
|
|
90
|
+
break
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
specValue = parts.join('')
|
|
95
|
+
return { specValue }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* :spec Pseudo-Selector, matches edges where edge.spec.bareSpec equals the provided value.
|
|
100
|
+
*
|
|
101
|
+
* Examples:
|
|
102
|
+
* - :spec("*") matches edges with a package specifier equal to "*"
|
|
103
|
+
* - :spec(^1.0.0) matches edges with a package specifier equal to "^1.0.0"
|
|
104
|
+
* - :spec("catalog:") matches edges with a package specifier equal to "catalog:"
|
|
105
|
+
* - :spec("workspace:dev") matches edges with a package specifier equal to "workspace:dev"
|
|
106
|
+
*/
|
|
107
|
+
export const spec = async (state: ParserState) => {
|
|
108
|
+
let internals
|
|
109
|
+
try {
|
|
110
|
+
internals = parseInternals(
|
|
111
|
+
asPostcssNodeWithChildren(state.current).nodes,
|
|
112
|
+
)
|
|
113
|
+
} catch (err) {
|
|
114
|
+
throw error('Failed to parse :spec selector', {
|
|
115
|
+
cause: err,
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const { specValue } = internals
|
|
120
|
+
|
|
121
|
+
for (const edge of state.partial.edges) {
|
|
122
|
+
if (edge.spec.bareSpec !== specValue) {
|
|
123
|
+
removeEdge(state, edge)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Clean up unlinked nodes after removing edges
|
|
128
|
+
removeUnlinkedNodes(state)
|
|
129
|
+
|
|
130
|
+
return state
|
|
131
|
+
}
|