@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,248 @@
1
+ import pRetry, { AbortError } from 'p-retry'
2
+ import { hydrate, splitDepID } from '@vltpkg/dep-id/browser'
3
+ import { error } from '@vltpkg/error-cause'
4
+ import { asError } from '@vltpkg/types'
5
+ import {
6
+ asPostcssNodeWithChildren,
7
+ asStringNode,
8
+ asTagNode,
9
+ isStringNode,
10
+ isTagNode,
11
+ } from '@vltpkg/dss-parser'
12
+ import {
13
+ removeDanglingEdges,
14
+ removeNode,
15
+ removeQuotes,
16
+ } from './helpers.ts'
17
+ import type { NodeLike, Packument } from '@vltpkg/types'
18
+ import type { ParserState } from '../types.ts'
19
+ import type { PostcssNode } from '@vltpkg/dss-parser'
20
+
21
+ /**
22
+ * The possible comparator values accepted by the :published() pseudo selector.
23
+ */
24
+ export type PublishedComparator = '>' | '<' | '>=' | '<=' | undefined
25
+
26
+ /**
27
+ * Result of the internal parsing of the :published() pseudo selector.
28
+ */
29
+ export type PublishedInternals = {
30
+ relativeDate: string
31
+ comparator: PublishedComparator
32
+ }
33
+
34
+ /**
35
+ * Fetches the published date of a package version from the npm registry.
36
+ */
37
+ export const retrieveRemoteDate = async (
38
+ node: NodeLike,
39
+ signal?: AbortSignal,
40
+ ): Promise<string | undefined> => {
41
+ const spec = hydrate(node.id, String(node.name), node.options)
42
+ if (!spec.registry || !node.name || !node.version) {
43
+ return undefined
44
+ }
45
+
46
+ const url = new URL(spec.registry)
47
+ url.pathname = `/${node.name}`
48
+
49
+ const response = await fetch(String(url), {
50
+ signal,
51
+ })
52
+ // on missing valid auth or API, it should abort the retry logic
53
+ if (response.status === 404) {
54
+ throw new AbortError('Missing API')
55
+ }
56
+ if (!response.ok) {
57
+ throw error('Failed to fetch packument', {
58
+ name: node.name,
59
+ spec,
60
+ response,
61
+ })
62
+ }
63
+ const packument = (await response.json()) as Packument
64
+ const res = packument.time?.[node.version]
65
+ return res
66
+ }
67
+
68
+ /**
69
+ * Retrieves what kind of check the :published selector should perform.
70
+ */
71
+ export const parseInternals = (
72
+ nodes: PostcssNode[],
73
+ ): PublishedInternals => {
74
+ let value = ''
75
+
76
+ if (isStringNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])) {
77
+ value = removeQuotes(
78
+ asStringNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])
79
+ .value,
80
+ )
81
+ } else if (
82
+ isTagNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])
83
+ ) {
84
+ const tagNode = asTagNode(
85
+ asPostcssNodeWithChildren(nodes[0]).nodes[0],
86
+ )
87
+ value = tagNode.value
88
+ }
89
+
90
+ // Check if the value starts with a comparator
91
+ let comparator: PublishedComparator
92
+ let relativeDate = value
93
+
94
+ if (value.startsWith('>=')) {
95
+ comparator = '>='
96
+ relativeDate = value.slice(2)
97
+ } else if (value.startsWith('<=')) {
98
+ comparator = '<='
99
+ relativeDate = value.slice(2)
100
+ } else if (value.startsWith('>')) {
101
+ comparator = '>'
102
+ relativeDate = value.slice(1)
103
+ } else if (value.startsWith('<')) {
104
+ comparator = '<'
105
+ relativeDate = value.slice(1)
106
+ }
107
+
108
+ return { relativeDate, comparator }
109
+ }
110
+
111
+ /**
112
+ * Filter nodes by queueing up for removal those that don't match the date criteria.
113
+ */
114
+ export const queueNode = async (
115
+ state: ParserState,
116
+ node: NodeLike,
117
+ relativeDate: string,
118
+ comparator: PublishedComparator,
119
+ ): Promise<NodeLike | undefined> => {
120
+ if (!node.name || !node.version) {
121
+ return node
122
+ }
123
+
124
+ let publishedDate: string | undefined
125
+ try {
126
+ publishedDate = await pRetry(
127
+ () => retrieveRemoteDate(node, state.signal),
128
+ {
129
+ retries: state.retries,
130
+ signal: state.signal,
131
+ },
132
+ )
133
+ } catch (err) {
134
+ // eslint-disable-next-line no-console
135
+ console.warn(
136
+ error('Could not retrieve registry publish date', {
137
+ name: node.name,
138
+ cause: err,
139
+ }),
140
+ )
141
+ return node
142
+ }
143
+
144
+ if (!publishedDate) {
145
+ return node
146
+ }
147
+
148
+ // only matches the same amount of date information
149
+ // as provided in the relative date
150
+ const nodeDate = new Date(
151
+ publishedDate.slice(0, relativeDate.length),
152
+ )
153
+ const compareDate = new Date(relativeDate)
154
+
155
+ switch (comparator) {
156
+ case '>':
157
+ return nodeDate > compareDate ? undefined : node
158
+ case '<':
159
+ return nodeDate < compareDate ? undefined : node
160
+ case '>=':
161
+ return nodeDate >= compareDate ? undefined : node
162
+ case '<=':
163
+ return nodeDate <= compareDate ? undefined : node
164
+ default:
165
+ return nodeDate.getTime() === compareDate.getTime() ?
166
+ undefined
167
+ : node
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Filters out nodes that don't match the published date criteria.
173
+ *
174
+ * The :published() pseudo selector supports a date parameter that can be prefixed
175
+ * with a comparator (>, <, >=, <=). If no comparator is provided, it will match
176
+ * exact dates.
177
+ *
178
+ * Examples:
179
+ * - :published("2024-01-01") - Matches packages published exactly on January 1st, 2024
180
+ * - :published(">2024-01-01") - Matches packages published after January 1st, 2024
181
+ * - :published("<=2023-12-31") - Matches packages published on or before December 31st, 2023
182
+ */
183
+ export const published = async (state: ParserState) => {
184
+ let internals
185
+ try {
186
+ internals = parseInternals(
187
+ asPostcssNodeWithChildren(state.current).nodes,
188
+ )
189
+ } catch (err) {
190
+ if (asError(err).message === 'Expected a query node') {
191
+ // No parameters provided - pseudo state form: match ANY published metadata
192
+ for (const node of state.partial.nodes) {
193
+ // filter out nodes that are always ignored by the published selector
194
+ if (
195
+ node.mainImporter ||
196
+ node.manifest?.private ||
197
+ splitDepID(node.id)[0] !== 'registry'
198
+ ) {
199
+ removeNode(state, node)
200
+ continue
201
+ }
202
+
203
+ // For pseudo state form, we just check if the node has published metadata
204
+ // This is equivalent to checking if it's a registry package with version info
205
+ if (!node.name || !node.version) {
206
+ removeNode(state, node)
207
+ }
208
+ }
209
+ removeDanglingEdges(state)
210
+ return state
211
+ } else {
212
+ throw error('Failed to parse :published selector', {
213
+ cause: err,
214
+ })
215
+ }
216
+ }
217
+
218
+ const { relativeDate, comparator } = internals
219
+ const queue = []
220
+
221
+ for (const node of state.partial.nodes) {
222
+ // filter out nodes that are always ignored by the published selector
223
+ if (
224
+ node.mainImporter ||
225
+ node.manifest?.private ||
226
+ splitDepID(node.id)[0] !== 'registry'
227
+ ) {
228
+ removeNode(state, node)
229
+ continue
230
+ }
231
+
232
+ // fetch published date info and perform checks to define
233
+ // whether or not a node should be filtered out
234
+ queue.push(queueNode(state, node, relativeDate, comparator))
235
+ }
236
+
237
+ // nodes queued for removal are then finally removed
238
+ const removeNodeQueue = await Promise.all(queue)
239
+ for (const node of removeNodeQueue) {
240
+ if (node) {
241
+ removeNode(state, node)
242
+ }
243
+ }
244
+
245
+ removeDanglingEdges(state)
246
+
247
+ return state
248
+ }
@@ -0,0 +1,29 @@
1
+ import { splitDepID } from '@vltpkg/dep-id/browser'
2
+ import {
3
+ asPostcssNodeWithChildren,
4
+ asTagNode,
5
+ } from '@vltpkg/dss-parser'
6
+ import { removeDanglingEdges, removeNode } from './helpers.ts'
7
+ import type { ParserState } from '../types.ts'
8
+
9
+ /**
10
+ * :registry(name) Pseudo-Selector, matches only nodes that
11
+ * belong to the specified registry configuration alias.
12
+ *
13
+ * For example, `:registry(npm)` matches deps from the default
14
+ * npm registry, and `:registry(custom)` matches deps from a
15
+ * custom-named registry.
16
+ */
17
+ export const registry = async (state: ParserState) => {
18
+ const top = asPostcssNodeWithChildren(state.current)
19
+ const selector = asPostcssNodeWithChildren(top.nodes[0])
20
+ const name = asTagNode(selector.nodes[0]).value
21
+ for (const node of state.partial.nodes) {
22
+ const tuple = splitDepID(node.id)
23
+ if (tuple[0] !== 'registry' || tuple[1] !== name) {
24
+ removeNode(state, node)
25
+ }
26
+ }
27
+ removeDanglingEdges(state)
28
+ return state
29
+ }
@@ -0,0 +1,19 @@
1
+ import type { ParserState } from '../types.ts'
2
+
3
+ /**
4
+ * :root Pseudo-Element will return the project root node for the graph.
5
+ * It matches only nodes marked as `mainImporter`.
6
+ */
7
+ export const root = async (state: ParserState) => {
8
+ for (const edge of state.partial.edges) {
9
+ if (!edge.to?.mainImporter) {
10
+ state.partial.edges.delete(edge)
11
+ }
12
+ }
13
+ for (const node of state.partial.nodes) {
14
+ if (!node.mainImporter) {
15
+ state.partial.nodes.delete(node)
16
+ }
17
+ }
18
+ return state
19
+ }
@@ -0,0 +1,20 @@
1
+ import { removeDanglingEdges, removeNode } from './helpers.ts'
2
+ import type { ParserState } from '../types.ts'
3
+
4
+ /**
5
+ * :scanned pseudo selector.
6
+ *
7
+ * Remove all nodes that do not have available metadata
8
+ * in the security archive.
9
+ */
10
+ export const scanned = async (state: ParserState) => {
11
+ for (const node of state.partial.nodes) {
12
+ if (!state.securityArchive?.has(node.id)) {
13
+ removeNode(state, node)
14
+ }
15
+ }
16
+
17
+ removeDanglingEdges(state)
18
+
19
+ return state
20
+ }
@@ -0,0 +1,186 @@
1
+ import { error } from '@vltpkg/error-cause'
2
+ import { asError } from '@vltpkg/types'
3
+ import {
4
+ asPostcssNodeWithChildren,
5
+ asStringNode,
6
+ asTagNode,
7
+ isStringNode,
8
+ isTagNode,
9
+ } from '@vltpkg/dss-parser'
10
+ import {
11
+ assertSecurityArchive,
12
+ removeDanglingEdges,
13
+ removeNode,
14
+ removeQuotes,
15
+ } from './helpers.ts'
16
+ import type { PackageScore } from '@vltpkg/security-archive'
17
+ import type { ParserState } from '../types.ts'
18
+ import type { PostcssNode } from '@vltpkg/dss-parser'
19
+
20
+ export type ScoreKinds = keyof PackageScore
21
+
22
+ export type ScoreComparator =
23
+ '>' | '<' | '>=' | '<=' | '=' | undefined
24
+
25
+ const kinds = new Set<ScoreKinds | undefined>([
26
+ 'overall',
27
+ 'license',
28
+ 'maintenance',
29
+ 'quality',
30
+ 'supplyChain',
31
+ 'vulnerability',
32
+ undefined,
33
+ ])
34
+
35
+ export const isScoreKind = (value?: string): value is ScoreKinds =>
36
+ kinds.has(value as ScoreKinds)
37
+
38
+ export const asScoreKind = (value?: string): ScoreKinds => {
39
+ if (!isScoreKind(value)) {
40
+ throw error('Expected a valid score kind', {
41
+ found: value,
42
+ validOptions: Array.from(kinds),
43
+ })
44
+ }
45
+ return value
46
+ }
47
+
48
+ export const parseInternals = (
49
+ nodes: PostcssNode[],
50
+ ): {
51
+ comparator: ScoreComparator
52
+ rate: number
53
+ kind: ScoreKinds
54
+ } => {
55
+ let rateStr = ''
56
+ let comparator: ScoreComparator = '='
57
+ let kind: ScoreKinds = 'overall'
58
+
59
+ // Parse the first parameter (rate with optional comparator)
60
+ if (isStringNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])) {
61
+ rateStr = removeQuotes(
62
+ asStringNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])
63
+ .value,
64
+ )
65
+ } else if (
66
+ isTagNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])
67
+ ) {
68
+ const tagNode = asTagNode(
69
+ asPostcssNodeWithChildren(nodes[0]).nodes[0],
70
+ )
71
+ rateStr = tagNode.value
72
+ }
73
+
74
+ // Extract comparator if present
75
+ if (rateStr.startsWith('>=')) {
76
+ comparator = '>='
77
+ rateStr = rateStr.substring(2)
78
+ } else if (rateStr.startsWith('<=')) {
79
+ comparator = '<='
80
+ rateStr = rateStr.substring(2)
81
+ } else if (rateStr.startsWith('>')) {
82
+ comparator = '>'
83
+ rateStr = rateStr.substring(1)
84
+ } else if (rateStr.startsWith('<')) {
85
+ comparator = '<'
86
+ rateStr = rateStr.substring(1)
87
+ }
88
+
89
+ // Parse rate as number
90
+ let rate = parseFloat(rateStr)
91
+
92
+ // Normalize to 0-1 range if needed
93
+ if (rate > 1) {
94
+ rate = rate / 100
95
+ }
96
+
97
+ // Validate rate is in acceptable range
98
+ if (rate < 0 || rate > 1) {
99
+ throw error('Expected rate to be between 0 and 100', {
100
+ found: rateStr,
101
+ })
102
+ }
103
+
104
+ // Parse the second parameter (kind) if present
105
+ if (nodes.length > 1) {
106
+ if (isStringNode(asPostcssNodeWithChildren(nodes[1]).nodes[0])) {
107
+ kind = asScoreKind(
108
+ removeQuotes(
109
+ asStringNode(asPostcssNodeWithChildren(nodes[1]).nodes[0])
110
+ .value,
111
+ ),
112
+ )
113
+ } else if (
114
+ isTagNode(asPostcssNodeWithChildren(nodes[1]).nodes[0])
115
+ ) {
116
+ kind = asScoreKind(
117
+ asTagNode(asPostcssNodeWithChildren(nodes[1]).nodes[0]).value,
118
+ )
119
+ }
120
+ }
121
+
122
+ return { comparator, rate, kind }
123
+ }
124
+
125
+ export const score = async (state: ParserState) => {
126
+ assertSecurityArchive(state, 'score')
127
+
128
+ let internals
129
+ try {
130
+ internals = parseInternals(
131
+ asPostcssNodeWithChildren(state.current).nodes,
132
+ )
133
+ } catch (err) {
134
+ if (asError(err).message === 'Expected a query node') {
135
+ // No parameters provided - pseudo state form: match ANY score (scanned packages)
136
+ for (const node of state.partial.nodes) {
137
+ const report = state.securityArchive.get(node.id)
138
+ if (!report) {
139
+ removeNode(state, node)
140
+ }
141
+ }
142
+ removeDanglingEdges(state)
143
+ return state
144
+ } else {
145
+ throw error('Failed to parse :score selector', { cause: err })
146
+ }
147
+ }
148
+
149
+ const { comparator, rate, kind } = internals
150
+ for (const node of state.partial.nodes) {
151
+ const report = state.securityArchive.get(node.id)
152
+ if (!report) {
153
+ removeNode(state, node)
154
+ continue
155
+ }
156
+
157
+ const scoreValue = report.score[kind]
158
+
159
+ let exclude = false
160
+ switch (comparator) {
161
+ case '>':
162
+ exclude = scoreValue <= rate
163
+ break
164
+ case '<':
165
+ exclude = scoreValue >= rate
166
+ break
167
+ case '>=':
168
+ exclude = scoreValue < rate
169
+ break
170
+ case '<=':
171
+ exclude = scoreValue > rate
172
+ break
173
+ default: // '='
174
+ exclude = scoreValue !== rate
175
+ break
176
+ }
177
+
178
+ if (exclude) {
179
+ removeNode(state, node)
180
+ }
181
+ }
182
+
183
+ removeDanglingEdges(state)
184
+
185
+ return state
186
+ }
@@ -0,0 +1,54 @@
1
+ import type { NodeLike } from '@vltpkg/types'
2
+ import type { ParserState } from '../types.ts'
3
+ import { removeNode, removeDanglingEdges } from './helpers.ts'
4
+
5
+ /**
6
+ * Checks if a node needs to be built based on the conditions from the reify build process:
7
+ * 1. Has install lifecycle scripts (install, preinstall, postinstall)
8
+ * 2. Is an importer or git dependency with prepare scripts (prepare, preprepare, postprepare)
9
+ */
10
+ const nodeNeedsBuild = (node: NodeLike): boolean => {
11
+ const { manifest } = node
12
+ /* c8 ignore next */
13
+ if (!manifest) return false
14
+
15
+ const { scripts = {} } = manifest
16
+
17
+ // Check for install lifecycle scripts
18
+ const runInstall = !!(
19
+ scripts.install ||
20
+ scripts.preinstall ||
21
+ scripts.postinstall
22
+ )
23
+ if (runInstall) return true
24
+
25
+ // Check for prepare scripts on importers or git dependencies
26
+ const prepable = node.id.startsWith('git') || node.importer
27
+ const runPrepare =
28
+ !!(
29
+ scripts.prepare || scripts.preprepare || scripts.postprepare
30
+ /* c8 ignore next 2 */
31
+ ) && prepable
32
+ if (runPrepare) return true
33
+
34
+ return false
35
+ }
36
+
37
+ /**
38
+ * :scripts Pseudo-Selector filters nodes based on whether they need to be built.
39
+ *
40
+ * A node needs to be built if it has:
41
+ * - Install lifecycle scripts (install, preinstall, postinstall)
42
+ * - Prepare scripts on importers or git dependencies (prepare, preprepare, postprepare)
43
+ */
44
+ export const scripts = async (state: ParserState) => {
45
+ for (const node of state.partial.nodes) {
46
+ if (!nodeNeedsBuild(node)) {
47
+ removeNode(state, node)
48
+ }
49
+ }
50
+
51
+ removeDanglingEdges(state)
52
+
53
+ return state
54
+ }