@vltpkg/query 1.0.0-rc.31 → 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.
Files changed (214) hide show
  1. package/dist/pseudo/cve.d.ts +6 -2
  2. package/dist/pseudo/cve.js +29 -11
  3. package/dist/pseudo/cwe.d.ts +6 -2
  4. package/dist/pseudo/cwe.js +29 -10
  5. package/dist/pseudo/dist.d.ts +22 -0
  6. package/dist/pseudo/dist.js +110 -0
  7. package/dist/pseudo/hostname.js +3 -1
  8. package/dist/pseudo/scripts.js +1 -1
  9. package/dist/pseudo/semver.d.ts +3 -1
  10. package/dist/pseudo/semver.js +43 -6
  11. package/dist/pseudo/vulnerable.d.ts +8 -0
  12. package/dist/pseudo/vulnerable.js +17 -0
  13. package/dist/pseudo.js +5 -0
  14. package/package.json +19 -10
  15. package/skills/dss-query/REFERENCE.md +119 -0
  16. package/skills/dss-query/SKILL.md +190 -0
  17. package/skills/dss-query/evals/README.md +79 -0
  18. package/skills/dss-query/evals/evals.json +89 -0
  19. package/skills/dss-query/evals/grade.mjs +480 -0
  20. package/src/attribute.ts +186 -0
  21. package/src/combinator.ts +132 -0
  22. package/src/id.ts +46 -0
  23. package/src/index.ts +613 -0
  24. package/src/parser.ts +121 -0
  25. package/src/pseudo/abandoned.ts +9 -0
  26. package/src/pseudo/attr.ts +92 -0
  27. package/src/pseudo/built.ts +19 -0
  28. package/src/pseudo/confused.ts +29 -0
  29. package/src/pseudo/cve.ts +93 -0
  30. package/src/pseudo/cwe.ts +97 -0
  31. package/src/pseudo/debug.ts +9 -0
  32. package/src/pseudo/deprecated.ts +9 -0
  33. package/src/pseudo/dev.ts +18 -0
  34. package/src/pseudo/diff.ts +90 -0
  35. package/src/pseudo/dist.ts +144 -0
  36. package/src/pseudo/dynamic.ts +9 -0
  37. package/src/pseudo/empty.ts +15 -0
  38. package/src/pseudo/entropic.ts +9 -0
  39. package/src/pseudo/env.ts +6 -0
  40. package/src/pseudo/eval.ts +9 -0
  41. package/src/pseudo/fs.ts +9 -0
  42. package/src/pseudo/helpers.ts +106 -0
  43. package/src/pseudo/host.ts +111 -0
  44. package/src/pseudo/hostname.ts +156 -0
  45. package/src/pseudo/license.ts +134 -0
  46. package/src/pseudo/link.ts +30 -0
  47. package/src/pseudo/malware.ts +249 -0
  48. package/src/pseudo/minified.ts +9 -0
  49. package/src/pseudo/missing.ts +16 -0
  50. package/src/pseudo/native.ts +9 -0
  51. package/src/pseudo/network.ts +9 -0
  52. package/src/pseudo/obfuscated.ts +9 -0
  53. package/src/pseudo/optional.ts +18 -0
  54. package/src/pseudo/outdated.ts +305 -0
  55. package/src/pseudo/overridden.ts +20 -0
  56. package/src/pseudo/path.ts +146 -0
  57. package/src/pseudo/peer.ts +18 -0
  58. package/src/pseudo/prerelease.ts +47 -0
  59. package/src/pseudo/private.ts +19 -0
  60. package/src/pseudo/prod.ts +18 -0
  61. package/src/pseudo/published.ts +248 -0
  62. package/src/pseudo/registry.ts +29 -0
  63. package/src/pseudo/root.ts +19 -0
  64. package/src/pseudo/scanned.ts +20 -0
  65. package/src/pseudo/score.ts +186 -0
  66. package/src/pseudo/scripts.ts +54 -0
  67. package/src/pseudo/semver.ts +320 -0
  68. package/src/pseudo/severity.ts +231 -0
  69. package/src/pseudo/shell.ts +9 -0
  70. package/src/pseudo/shrinkwrap.ts +9 -0
  71. package/src/pseudo/spec.ts +131 -0
  72. package/src/pseudo/squat.ts +225 -0
  73. package/src/pseudo/suspicious.ts +9 -0
  74. package/src/pseudo/tracker.ts +9 -0
  75. package/src/pseudo/trivial.ts +9 -0
  76. package/src/pseudo/type.ts +26 -0
  77. package/src/pseudo/undesirable.ts +9 -0
  78. package/src/pseudo/unknown.ts +9 -0
  79. package/src/pseudo/unmaintained.ts +9 -0
  80. package/src/pseudo/unpopular.ts +9 -0
  81. package/src/pseudo/unstable.ts +9 -0
  82. package/src/pseudo/vulnerable.ts +26 -0
  83. package/src/pseudo/workspace.ts +22 -0
  84. package/src/pseudo.ts +413 -0
  85. package/src/types.ts +164 -0
  86. package/tap-snapshots/test/attribute.ts.test.cjs +304 -0
  87. package/tap-snapshots/test/combinator.ts.test.cjs +333 -0
  88. package/tap-snapshots/test/id.ts.test.cjs +118 -0
  89. package/tap-snapshots/test/parser.ts.test.cjs +344 -0
  90. package/tap-snapshots/test/pseudo/abandoned.ts.test.cjs +18 -0
  91. package/tap-snapshots/test/pseudo/attr.ts.test.cjs +161 -0
  92. package/tap-snapshots/test/pseudo/built.ts.test.cjs +70 -0
  93. package/tap-snapshots/test/pseudo/confused.ts.test.cjs +32 -0
  94. package/tap-snapshots/test/pseudo/cve.ts.test.cjs +49 -0
  95. package/tap-snapshots/test/pseudo/cwe.ts.test.cjs +49 -0
  96. package/tap-snapshots/test/pseudo/debug.ts.test.cjs +18 -0
  97. package/tap-snapshots/test/pseudo/deprecated.ts.test.cjs +18 -0
  98. package/tap-snapshots/test/pseudo/dist.ts.test.cjs +30 -0
  99. package/tap-snapshots/test/pseudo/dynamic.ts.test.cjs +18 -0
  100. package/tap-snapshots/test/pseudo/empty.ts.test.cjs +50 -0
  101. package/tap-snapshots/test/pseudo/entropic.ts.test.cjs +18 -0
  102. package/tap-snapshots/test/pseudo/env.ts.test.cjs +18 -0
  103. package/tap-snapshots/test/pseudo/eval.ts.test.cjs +18 -0
  104. package/tap-snapshots/test/pseudo/fs.ts.test.cjs +18 -0
  105. package/tap-snapshots/test/pseudo/helpers.ts.test.cjs +18 -0
  106. package/tap-snapshots/test/pseudo/hostname.ts.test.cjs +148 -0
  107. package/tap-snapshots/test/pseudo/license.ts.test.cjs +41 -0
  108. package/tap-snapshots/test/pseudo/link.ts.test.cjs +36 -0
  109. package/tap-snapshots/test/pseudo/malware.ts.test.cjs +191 -0
  110. package/tap-snapshots/test/pseudo/minified.ts.test.cjs +18 -0
  111. package/tap-snapshots/test/pseudo/missing.ts.test.cjs +30 -0
  112. package/tap-snapshots/test/pseudo/native.ts.test.cjs +18 -0
  113. package/tap-snapshots/test/pseudo/network.ts.test.cjs +18 -0
  114. package/tap-snapshots/test/pseudo/obfuscated.ts.test.cjs +18 -0
  115. package/tap-snapshots/test/pseudo/outdated.ts.test.cjs +130 -0
  116. package/tap-snapshots/test/pseudo/overridden.ts.test.cjs +75 -0
  117. package/tap-snapshots/test/pseudo/prerelease.ts.test.cjs +101 -0
  118. package/tap-snapshots/test/pseudo/private.ts.test.cjs +38 -0
  119. package/tap-snapshots/test/pseudo/published.ts.test.cjs +171 -0
  120. package/tap-snapshots/test/pseudo/registry.ts.test.cjs +68 -0
  121. package/tap-snapshots/test/pseudo/root.ts.test.cjs +24 -0
  122. package/tap-snapshots/test/pseudo/scanned.ts.test.cjs +18 -0
  123. package/tap-snapshots/test/pseudo/score.ts.test.cjs +239 -0
  124. package/tap-snapshots/test/pseudo/scripts.ts.test.cjs +61 -0
  125. package/tap-snapshots/test/pseudo/semver.ts.test.cjs +575 -0
  126. package/tap-snapshots/test/pseudo/severity.ts.test.cjs +95 -0
  127. package/tap-snapshots/test/pseudo/shell.ts.test.cjs +18 -0
  128. package/tap-snapshots/test/pseudo/shrinkwrap.ts.test.cjs +18 -0
  129. package/tap-snapshots/test/pseudo/spec.ts.test.cjs +148 -0
  130. package/tap-snapshots/test/pseudo/squat.ts.test.cjs +129 -0
  131. package/tap-snapshots/test/pseudo/suspicious.ts.test.cjs +18 -0
  132. package/tap-snapshots/test/pseudo/tracker.ts.test.cjs +18 -0
  133. package/tap-snapshots/test/pseudo/trivial.ts.test.cjs +18 -0
  134. package/tap-snapshots/test/pseudo/type.ts.test.cjs +51 -0
  135. package/tap-snapshots/test/pseudo/undesirable.ts.test.cjs +18 -0
  136. package/tap-snapshots/test/pseudo/unknown.ts.test.cjs +18 -0
  137. package/tap-snapshots/test/pseudo/unmaintained.ts.test.cjs +18 -0
  138. package/tap-snapshots/test/pseudo/unpopular.ts.test.cjs +18 -0
  139. package/tap-snapshots/test/pseudo/unstable.ts.test.cjs +18 -0
  140. package/tap-snapshots/test/pseudo/vulnerable.ts.test.cjs +18 -0
  141. package/tap-snapshots/test/pseudo/workspace.ts.test.cjs +19 -0
  142. package/tap-snapshots/test/pseudo.ts.test.cjs +1247 -0
  143. package/test/attribute.ts +266 -0
  144. package/test/combinator.ts +168 -0
  145. package/test/fixtures/graph.ts +999 -0
  146. package/test/fixtures/selector.ts +103 -0
  147. package/test/fixtures/types.ts +7 -0
  148. package/test/id.ts +102 -0
  149. package/test/index.ts +992 -0
  150. package/test/parser.ts +117 -0
  151. package/test/pseudo/abandoned.ts +98 -0
  152. package/test/pseudo/attr.ts +298 -0
  153. package/test/pseudo/built.ts +214 -0
  154. package/test/pseudo/confused.ts +160 -0
  155. package/test/pseudo/cve.ts +249 -0
  156. package/test/pseudo/cwe.ts +255 -0
  157. package/test/pseudo/debug.ts +98 -0
  158. package/test/pseudo/deprecated.ts +98 -0
  159. package/test/pseudo/dev.ts +61 -0
  160. package/test/pseudo/diff.ts +417 -0
  161. package/test/pseudo/dist.ts +215 -0
  162. package/test/pseudo/dynamic.ts +98 -0
  163. package/test/pseudo/empty.ts +108 -0
  164. package/test/pseudo/entropic.ts +101 -0
  165. package/test/pseudo/env.ts +98 -0
  166. package/test/pseudo/eval.ts +98 -0
  167. package/test/pseudo/fs.ts +98 -0
  168. package/test/pseudo/helpers.ts +370 -0
  169. package/test/pseudo/host-context.ts +276 -0
  170. package/test/pseudo/hostname.ts +314 -0
  171. package/test/pseudo/license.ts +265 -0
  172. package/test/pseudo/link.ts +94 -0
  173. package/test/pseudo/malware.ts +431 -0
  174. package/test/pseudo/minified.ts +98 -0
  175. package/test/pseudo/missing.ts +111 -0
  176. package/test/pseudo/native.ts +98 -0
  177. package/test/pseudo/network.ts +98 -0
  178. package/test/pseudo/obfuscated.ts +98 -0
  179. package/test/pseudo/optional.ts +73 -0
  180. package/test/pseudo/outdated.ts +331 -0
  181. package/test/pseudo/overridden.ts +317 -0
  182. package/test/pseudo/path.ts +680 -0
  183. package/test/pseudo/peer.ts +101 -0
  184. package/test/pseudo/prerelease.ts +279 -0
  185. package/test/pseudo/private.ts +108 -0
  186. package/test/pseudo/prod.ts +61 -0
  187. package/test/pseudo/published.ts +557 -0
  188. package/test/pseudo/registry.ts +122 -0
  189. package/test/pseudo/root.ts +66 -0
  190. package/test/pseudo/scanned.ts +78 -0
  191. package/test/pseudo/score.ts +591 -0
  192. package/test/pseudo/scripts.ts +294 -0
  193. package/test/pseudo/semver.ts +822 -0
  194. package/test/pseudo/severity.ts +310 -0
  195. package/test/pseudo/shell.ts +98 -0
  196. package/test/pseudo/shrinkwrap.ts +98 -0
  197. package/test/pseudo/spec.ts +525 -0
  198. package/test/pseudo/squat.ts +565 -0
  199. package/test/pseudo/suspicious.ts +98 -0
  200. package/test/pseudo/tracker.ts +98 -0
  201. package/test/pseudo/trivial.ts +98 -0
  202. package/test/pseudo/type.ts +105 -0
  203. package/test/pseudo/undesirable.ts +98 -0
  204. package/test/pseudo/unknown.ts +98 -0
  205. package/test/pseudo/unmaintained.ts +98 -0
  206. package/test/pseudo/unpopular.ts +98 -0
  207. package/test/pseudo/unstable.ts +98 -0
  208. package/test/pseudo/vulnerable.ts +130 -0
  209. package/test/pseudo/workspace.ts +111 -0
  210. package/test/pseudo.ts +558 -0
  211. package/tsconfig.json +3 -0
  212. package/tsconfig.publish.json +19 -0
  213. package/tsconfig.publish.tsbuildinfo +1 -0
  214. 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,9 @@
1
+ import { createSecuritySelectorFilter } from './helpers.ts'
2
+
3
+ /**
4
+ * Filters out any node that does not have a **shellAccess** report alert.
5
+ */
6
+ export const shell = createSecuritySelectorFilter(
7
+ 'shell',
8
+ 'shellAccess',
9
+ )
@@ -0,0 +1,9 @@
1
+ import { createSecuritySelectorFilter } from './helpers.ts'
2
+
3
+ /**
4
+ * Filters out any node that does not have a **shrinkwrap** report alert.
5
+ */
6
+ export const shrinkwrap = createSecuritySelectorFilter(
7
+ 'shrinkwrap',
8
+ 'shrinkwrap',
9
+ )
@@ -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
+ }