@vltpkg/query 1.0.0-rc.32 → 1.0.0-rc.34

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