@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.
Files changed (203) hide show
  1. package/dist/pseudo/hostname.js +3 -1
  2. package/dist/pseudo/scripts.js +1 -1
  3. package/package.json +19 -10
  4. package/skills/dss-query/REFERENCE.md +119 -0
  5. package/skills/dss-query/SKILL.md +190 -0
  6. package/skills/dss-query/evals/README.md +79 -0
  7. package/skills/dss-query/evals/evals.json +89 -0
  8. package/skills/dss-query/evals/grade.mjs +480 -0
  9. package/src/attribute.ts +186 -0
  10. package/src/combinator.ts +132 -0
  11. package/src/id.ts +46 -0
  12. package/src/index.ts +613 -0
  13. package/src/parser.ts +121 -0
  14. package/src/pseudo/abandoned.ts +9 -0
  15. package/src/pseudo/attr.ts +92 -0
  16. package/src/pseudo/built.ts +19 -0
  17. package/src/pseudo/confused.ts +29 -0
  18. package/src/pseudo/cve.ts +93 -0
  19. package/src/pseudo/cwe.ts +97 -0
  20. package/src/pseudo/debug.ts +9 -0
  21. package/src/pseudo/deprecated.ts +9 -0
  22. package/src/pseudo/dev.ts +18 -0
  23. package/src/pseudo/diff.ts +90 -0
  24. package/src/pseudo/dist.ts +144 -0
  25. package/src/pseudo/dynamic.ts +9 -0
  26. package/src/pseudo/empty.ts +15 -0
  27. package/src/pseudo/entropic.ts +9 -0
  28. package/src/pseudo/env.ts +6 -0
  29. package/src/pseudo/eval.ts +9 -0
  30. package/src/pseudo/fs.ts +9 -0
  31. package/src/pseudo/helpers.ts +106 -0
  32. package/src/pseudo/host.ts +111 -0
  33. package/src/pseudo/hostname.ts +156 -0
  34. package/src/pseudo/license.ts +134 -0
  35. package/src/pseudo/link.ts +30 -0
  36. package/src/pseudo/malware.ts +249 -0
  37. package/src/pseudo/minified.ts +9 -0
  38. package/src/pseudo/missing.ts +16 -0
  39. package/src/pseudo/native.ts +9 -0
  40. package/src/pseudo/network.ts +9 -0
  41. package/src/pseudo/obfuscated.ts +9 -0
  42. package/src/pseudo/optional.ts +18 -0
  43. package/src/pseudo/outdated.ts +305 -0
  44. package/src/pseudo/overridden.ts +20 -0
  45. package/src/pseudo/path.ts +146 -0
  46. package/src/pseudo/peer.ts +18 -0
  47. package/src/pseudo/prerelease.ts +47 -0
  48. package/src/pseudo/private.ts +19 -0
  49. package/src/pseudo/prod.ts +18 -0
  50. package/src/pseudo/published.ts +248 -0
  51. package/src/pseudo/registry.ts +29 -0
  52. package/src/pseudo/root.ts +19 -0
  53. package/src/pseudo/scanned.ts +20 -0
  54. package/src/pseudo/score.ts +186 -0
  55. package/src/pseudo/scripts.ts +54 -0
  56. package/src/pseudo/semver.ts +320 -0
  57. package/src/pseudo/severity.ts +231 -0
  58. package/src/pseudo/shell.ts +9 -0
  59. package/src/pseudo/shrinkwrap.ts +9 -0
  60. package/src/pseudo/spec.ts +131 -0
  61. package/src/pseudo/squat.ts +225 -0
  62. package/src/pseudo/suspicious.ts +9 -0
  63. package/src/pseudo/tracker.ts +9 -0
  64. package/src/pseudo/trivial.ts +9 -0
  65. package/src/pseudo/type.ts +26 -0
  66. package/src/pseudo/undesirable.ts +9 -0
  67. package/src/pseudo/unknown.ts +9 -0
  68. package/src/pseudo/unmaintained.ts +9 -0
  69. package/src/pseudo/unpopular.ts +9 -0
  70. package/src/pseudo/unstable.ts +9 -0
  71. package/src/pseudo/vulnerable.ts +26 -0
  72. package/src/pseudo/workspace.ts +22 -0
  73. package/src/pseudo.ts +413 -0
  74. package/src/types.ts +164 -0
  75. package/tap-snapshots/test/attribute.ts.test.cjs +304 -0
  76. package/tap-snapshots/test/combinator.ts.test.cjs +333 -0
  77. package/tap-snapshots/test/id.ts.test.cjs +118 -0
  78. package/tap-snapshots/test/parser.ts.test.cjs +344 -0
  79. package/tap-snapshots/test/pseudo/abandoned.ts.test.cjs +18 -0
  80. package/tap-snapshots/test/pseudo/attr.ts.test.cjs +161 -0
  81. package/tap-snapshots/test/pseudo/built.ts.test.cjs +70 -0
  82. package/tap-snapshots/test/pseudo/confused.ts.test.cjs +32 -0
  83. package/tap-snapshots/test/pseudo/cve.ts.test.cjs +49 -0
  84. package/tap-snapshots/test/pseudo/cwe.ts.test.cjs +49 -0
  85. package/tap-snapshots/test/pseudo/debug.ts.test.cjs +18 -0
  86. package/tap-snapshots/test/pseudo/deprecated.ts.test.cjs +18 -0
  87. package/tap-snapshots/test/pseudo/dist.ts.test.cjs +30 -0
  88. package/tap-snapshots/test/pseudo/dynamic.ts.test.cjs +18 -0
  89. package/tap-snapshots/test/pseudo/empty.ts.test.cjs +50 -0
  90. package/tap-snapshots/test/pseudo/entropic.ts.test.cjs +18 -0
  91. package/tap-snapshots/test/pseudo/env.ts.test.cjs +18 -0
  92. package/tap-snapshots/test/pseudo/eval.ts.test.cjs +18 -0
  93. package/tap-snapshots/test/pseudo/fs.ts.test.cjs +18 -0
  94. package/tap-snapshots/test/pseudo/helpers.ts.test.cjs +18 -0
  95. package/tap-snapshots/test/pseudo/hostname.ts.test.cjs +148 -0
  96. package/tap-snapshots/test/pseudo/license.ts.test.cjs +41 -0
  97. package/tap-snapshots/test/pseudo/link.ts.test.cjs +36 -0
  98. package/tap-snapshots/test/pseudo/malware.ts.test.cjs +191 -0
  99. package/tap-snapshots/test/pseudo/minified.ts.test.cjs +18 -0
  100. package/tap-snapshots/test/pseudo/missing.ts.test.cjs +30 -0
  101. package/tap-snapshots/test/pseudo/native.ts.test.cjs +18 -0
  102. package/tap-snapshots/test/pseudo/network.ts.test.cjs +18 -0
  103. package/tap-snapshots/test/pseudo/obfuscated.ts.test.cjs +18 -0
  104. package/tap-snapshots/test/pseudo/outdated.ts.test.cjs +130 -0
  105. package/tap-snapshots/test/pseudo/overridden.ts.test.cjs +75 -0
  106. package/tap-snapshots/test/pseudo/prerelease.ts.test.cjs +101 -0
  107. package/tap-snapshots/test/pseudo/private.ts.test.cjs +38 -0
  108. package/tap-snapshots/test/pseudo/published.ts.test.cjs +171 -0
  109. package/tap-snapshots/test/pseudo/registry.ts.test.cjs +68 -0
  110. package/tap-snapshots/test/pseudo/root.ts.test.cjs +24 -0
  111. package/tap-snapshots/test/pseudo/scanned.ts.test.cjs +18 -0
  112. package/tap-snapshots/test/pseudo/score.ts.test.cjs +239 -0
  113. package/tap-snapshots/test/pseudo/scripts.ts.test.cjs +61 -0
  114. package/tap-snapshots/test/pseudo/semver.ts.test.cjs +575 -0
  115. package/tap-snapshots/test/pseudo/severity.ts.test.cjs +95 -0
  116. package/tap-snapshots/test/pseudo/shell.ts.test.cjs +18 -0
  117. package/tap-snapshots/test/pseudo/shrinkwrap.ts.test.cjs +18 -0
  118. package/tap-snapshots/test/pseudo/spec.ts.test.cjs +148 -0
  119. package/tap-snapshots/test/pseudo/squat.ts.test.cjs +129 -0
  120. package/tap-snapshots/test/pseudo/suspicious.ts.test.cjs +18 -0
  121. package/tap-snapshots/test/pseudo/tracker.ts.test.cjs +18 -0
  122. package/tap-snapshots/test/pseudo/trivial.ts.test.cjs +18 -0
  123. package/tap-snapshots/test/pseudo/type.ts.test.cjs +51 -0
  124. package/tap-snapshots/test/pseudo/undesirable.ts.test.cjs +18 -0
  125. package/tap-snapshots/test/pseudo/unknown.ts.test.cjs +18 -0
  126. package/tap-snapshots/test/pseudo/unmaintained.ts.test.cjs +18 -0
  127. package/tap-snapshots/test/pseudo/unpopular.ts.test.cjs +18 -0
  128. package/tap-snapshots/test/pseudo/unstable.ts.test.cjs +18 -0
  129. package/tap-snapshots/test/pseudo/vulnerable.ts.test.cjs +18 -0
  130. package/tap-snapshots/test/pseudo/workspace.ts.test.cjs +19 -0
  131. package/tap-snapshots/test/pseudo.ts.test.cjs +1247 -0
  132. package/test/attribute.ts +266 -0
  133. package/test/combinator.ts +168 -0
  134. package/test/fixtures/graph.ts +999 -0
  135. package/test/fixtures/selector.ts +103 -0
  136. package/test/fixtures/types.ts +7 -0
  137. package/test/id.ts +102 -0
  138. package/test/index.ts +992 -0
  139. package/test/parser.ts +117 -0
  140. package/test/pseudo/abandoned.ts +98 -0
  141. package/test/pseudo/attr.ts +298 -0
  142. package/test/pseudo/built.ts +214 -0
  143. package/test/pseudo/confused.ts +160 -0
  144. package/test/pseudo/cve.ts +249 -0
  145. package/test/pseudo/cwe.ts +255 -0
  146. package/test/pseudo/debug.ts +98 -0
  147. package/test/pseudo/deprecated.ts +98 -0
  148. package/test/pseudo/dev.ts +61 -0
  149. package/test/pseudo/diff.ts +417 -0
  150. package/test/pseudo/dist.ts +215 -0
  151. package/test/pseudo/dynamic.ts +98 -0
  152. package/test/pseudo/empty.ts +108 -0
  153. package/test/pseudo/entropic.ts +101 -0
  154. package/test/pseudo/env.ts +98 -0
  155. package/test/pseudo/eval.ts +98 -0
  156. package/test/pseudo/fs.ts +98 -0
  157. package/test/pseudo/helpers.ts +370 -0
  158. package/test/pseudo/host-context.ts +276 -0
  159. package/test/pseudo/hostname.ts +314 -0
  160. package/test/pseudo/license.ts +265 -0
  161. package/test/pseudo/link.ts +94 -0
  162. package/test/pseudo/malware.ts +431 -0
  163. package/test/pseudo/minified.ts +98 -0
  164. package/test/pseudo/missing.ts +111 -0
  165. package/test/pseudo/native.ts +98 -0
  166. package/test/pseudo/network.ts +98 -0
  167. package/test/pseudo/obfuscated.ts +98 -0
  168. package/test/pseudo/optional.ts +73 -0
  169. package/test/pseudo/outdated.ts +331 -0
  170. package/test/pseudo/overridden.ts +317 -0
  171. package/test/pseudo/path.ts +680 -0
  172. package/test/pseudo/peer.ts +101 -0
  173. package/test/pseudo/prerelease.ts +279 -0
  174. package/test/pseudo/private.ts +108 -0
  175. package/test/pseudo/prod.ts +61 -0
  176. package/test/pseudo/published.ts +557 -0
  177. package/test/pseudo/registry.ts +122 -0
  178. package/test/pseudo/root.ts +66 -0
  179. package/test/pseudo/scanned.ts +78 -0
  180. package/test/pseudo/score.ts +591 -0
  181. package/test/pseudo/scripts.ts +294 -0
  182. package/test/pseudo/semver.ts +822 -0
  183. package/test/pseudo/severity.ts +310 -0
  184. package/test/pseudo/shell.ts +98 -0
  185. package/test/pseudo/shrinkwrap.ts +98 -0
  186. package/test/pseudo/spec.ts +525 -0
  187. package/test/pseudo/squat.ts +565 -0
  188. package/test/pseudo/suspicious.ts +98 -0
  189. package/test/pseudo/tracker.ts +98 -0
  190. package/test/pseudo/trivial.ts +98 -0
  191. package/test/pseudo/type.ts +105 -0
  192. package/test/pseudo/undesirable.ts +98 -0
  193. package/test/pseudo/unknown.ts +98 -0
  194. package/test/pseudo/unmaintained.ts +98 -0
  195. package/test/pseudo/unpopular.ts +98 -0
  196. package/test/pseudo/unstable.ts +98 -0
  197. package/test/pseudo/vulnerable.ts +130 -0
  198. package/test/pseudo/workspace.ts +111 -0
  199. package/test/pseudo.ts +558 -0
  200. package/tsconfig.json +3 -0
  201. package/tsconfig.publish.json +19 -0
  202. package/tsconfig.publish.tsbuildinfo +1 -0
  203. package/typedoc.mjs +2 -0
@@ -0,0 +1,305 @@
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
+ compare,
7
+ gt,
8
+ major,
9
+ minor,
10
+ patch,
11
+ satisfies,
12
+ } from '@vltpkg/semver'
13
+ import {
14
+ asPostcssNodeWithChildren,
15
+ asStringNode,
16
+ asTagNode,
17
+ isStringNode,
18
+ isTagNode,
19
+ } from '@vltpkg/dss-parser'
20
+ import { removeNode, removeQuotes } from './helpers.ts'
21
+ import type { NodeLike, Packument } from '@vltpkg/types'
22
+ import type { ParserState } from '../types.ts'
23
+ import type { PostcssNode } from '@vltpkg/dss-parser'
24
+
25
+ /**
26
+ * The possible values accepted by the :outdated() pseudo selector.
27
+ */
28
+ export type OutdatedKinds =
29
+ 'any' | 'major' | 'minor' | 'patch' | 'in-range' | 'out-of-range'
30
+
31
+ /**
32
+ * Result of the internal parsing of the :outdated() pseudo selector.
33
+ */
34
+ export type OutdatedInternals = {
35
+ kind: OutdatedKinds
36
+ }
37
+
38
+ /**
39
+ * Extracts a semver type from a version string.
40
+ */
41
+ export type SemverTypeExtraction = (
42
+ version: string,
43
+ ) => number | undefined
44
+
45
+ const kinds = new Set<OutdatedKinds>([
46
+ 'any',
47
+ 'major',
48
+ 'minor',
49
+ 'patch',
50
+ 'in-range',
51
+ 'out-of-range',
52
+ ])
53
+
54
+ /**
55
+ * Checks if a string is a valid {@link OutdatedKinds}.
56
+ */
57
+ export const isOutdatedKind = (
58
+ value: string,
59
+ ): value is OutdatedKinds => kinds.has(value as OutdatedKinds)
60
+
61
+ /**
62
+ * Asserts that a string is a valid {@link OutdatedKinds}.
63
+ */
64
+ export const asOutdatedKind = (value: string): OutdatedKinds => {
65
+ if (!isOutdatedKind(value)) {
66
+ throw error('Expected a valid outdated kind', {
67
+ found: value,
68
+ validOptions: Array.from(kinds),
69
+ })
70
+ }
71
+ return value
72
+ }
73
+
74
+ /**
75
+ * Fetches the available versions of a package from the npm registry.
76
+ */
77
+ export const retrieveRemoteVersions = async (
78
+ node: NodeLike,
79
+ signal?: AbortSignal,
80
+ ): Promise<string[]> => {
81
+ const spec = hydrate(node.id, String(node.name), node.options)
82
+ if (!spec.registry || !node.name) {
83
+ return []
84
+ }
85
+
86
+ const url = new URL(spec.registry)
87
+ url.pathname = `/${node.name}`
88
+
89
+ const response = await fetch(String(url), {
90
+ headers: {
91
+ Accept: 'application/vnd.npm.install-v1+json',
92
+ },
93
+ signal,
94
+ })
95
+ // on missing valid auth or API, it should abort the retry logic
96
+ if (response.status === 404) {
97
+ throw new AbortError('Missing API')
98
+ }
99
+ if (!response.ok) {
100
+ throw error('Failed to fetch packument', {
101
+ name: node.name,
102
+ spec,
103
+ response,
104
+ })
105
+ }
106
+ const packument = (await response.json()) as Packument
107
+ return Object.keys(packument.versions).sort(compare)
108
+ }
109
+
110
+ /**
111
+ * Retrieves what kind of check the :outdated selector should perform.
112
+ */
113
+ export const parseInternals = (
114
+ nodes: PostcssNode[],
115
+ ): OutdatedInternals => {
116
+ let kind: OutdatedKinds = 'any'
117
+
118
+ if (isStringNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])) {
119
+ kind = asOutdatedKind(
120
+ removeQuotes(
121
+ asStringNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])
122
+ .value,
123
+ ),
124
+ )
125
+ } else if (
126
+ isTagNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])
127
+ ) {
128
+ kind = asOutdatedKind(
129
+ asTagNode(asPostcssNodeWithChildren(nodes[0]).nodes[0]).value,
130
+ )
131
+ }
132
+
133
+ return { kind }
134
+ }
135
+
136
+ /**
137
+ * Filter nodes by queueing up for removal those that are not outdated.
138
+ */
139
+ export const queueNode = async (
140
+ state: ParserState,
141
+ node: NodeLike,
142
+ kind: OutdatedKinds,
143
+ ): Promise<NodeLike | undefined> => {
144
+ if (!node.name || !node.version) {
145
+ return node
146
+ }
147
+
148
+ const nodeVersion: string = node.version
149
+ let versions: string[]
150
+ try {
151
+ versions = await pRetry(
152
+ () => retrieveRemoteVersions(node, state.signal),
153
+ {
154
+ retries: state.retries,
155
+ signal: state.signal,
156
+ },
157
+ )
158
+ } catch (err) {
159
+ // eslint-disable-next-line no-console
160
+ console.warn(
161
+ error('Could not retrieve registry versions', {
162
+ name: node.name,
163
+ cause: err,
164
+ }),
165
+ )
166
+ versions = []
167
+ }
168
+
169
+ const greaterVersions = versions.filter((version: string) =>
170
+ gt(version, nodeVersion),
171
+ )
172
+
173
+ // if there are no greater versions, then the node is not outdated
174
+ if (!greaterVersions.length) {
175
+ return node
176
+ }
177
+
178
+ const checkKind = new Map<OutdatedKinds, SemverTypeExtraction>([
179
+ ['major', major],
180
+ ['minor', minor],
181
+ ['patch', patch],
182
+ ])
183
+
184
+ switch (kind) {
185
+ case 'any':
186
+ return
187
+ case 'major':
188
+ case 'minor':
189
+ case 'patch': {
190
+ return (
191
+ greaterVersions.some((version: string) => {
192
+ const va = checkKind.get(kind)?.(version)
193
+ const vb = checkKind.get(kind)?.(nodeVersion)
194
+ /* c8 ignore next - impossible but typescript doesn't know */
195
+ if (va === undefined || vb === undefined) return false
196
+ return va > vb
197
+ })
198
+ ) ?
199
+ undefined
200
+ : node
201
+ }
202
+ // the node should be part of the result as long as it has at least
203
+ // one parent node that has a spec definition that satisfies one
204
+ // of the available remove versions
205
+ case 'in-range': {
206
+ for (const edge of node.edgesIn) {
207
+ // if the edge is not part of the partial results, skip it
208
+ /* c8 ignore next */
209
+ if (!state.partial.edges.has(edge)) continue
210
+
211
+ if (
212
+ greaterVersions.some(
213
+ version =>
214
+ edge.spec.final.range &&
215
+ satisfies(version, edge.spec.final.range),
216
+ )
217
+ ) {
218
+ return
219
+ }
220
+ }
221
+ return node
222
+ }
223
+ // the node is part of the result as long as none of its parents has
224
+ // a spec definition that satisfies one of the available remote versions
225
+ case 'out-of-range': {
226
+ for (const edge of node.edgesIn) {
227
+ // if the edge is not part of the partial results, skip it
228
+ /* c8 ignore next */
229
+ if (!state.partial.edges.has(edge)) continue
230
+
231
+ if (
232
+ greaterVersions.some(
233
+ version =>
234
+ edge.spec.final.range &&
235
+ satisfies(version, edge.spec.final.range),
236
+ )
237
+ ) {
238
+ return node
239
+ }
240
+ }
241
+ return
242
+ }
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Filters out nodes that are not outdated.
248
+ *
249
+ * The :outdated() pseudo selector supports one `type` argument,
250
+ * possible values are the following:
251
+ *
252
+ * - `any`: Selects all nodes that have a greater version available.
253
+ * - `major`: Selects all nodes that have a greater major version available.
254
+ * - `minor`: Selects all nodes that have a greater minor version available.
255
+ * - `patch`: Selects all nodes that have a greater patch version available.
256
+ * - `in-range`: Selects all nodes that have a parent node with a spec definition
257
+ * that satisfies one of the available remote versions.
258
+ * - `out-of-range`: Selects all nodes that have a parent node with a spec definition
259
+ * that does not satisfy any of the available remote versions.
260
+ */
261
+ export const outdated = async (state: ParserState) => {
262
+ let internals
263
+ try {
264
+ internals = parseInternals(
265
+ asPostcssNodeWithChildren(state.current).nodes,
266
+ )
267
+ } catch (err) {
268
+ if (asError(err).message === 'Expected a query node') {
269
+ internals = { kind: 'any' } satisfies OutdatedInternals
270
+ } else {
271
+ throw error('Failed to parse :outdated selector', {
272
+ cause: err,
273
+ })
274
+ }
275
+ }
276
+
277
+ const { kind } = internals
278
+ const queue = []
279
+
280
+ for (const node of state.partial.nodes) {
281
+ // filter out nodes that are always ignored by the outdated selector
282
+ if (
283
+ node.mainImporter ||
284
+ node.manifest?.private ||
285
+ splitDepID(node.id)[0] !== 'registry'
286
+ ) {
287
+ removeNode(state, node)
288
+ continue
289
+ }
290
+
291
+ // fetchs outdated info and performs checks to define
292
+ // whether or not a node should be filtered out
293
+ queue.push(queueNode(state, node, kind))
294
+ }
295
+
296
+ // nodes queued for removal are then finally removed
297
+ const removeNodeQueue = await Promise.all(queue)
298
+ for (const node of removeNodeQueue) {
299
+ if (node) {
300
+ removeNode(state, node)
301
+ }
302
+ }
303
+
304
+ return state
305
+ }
@@ -0,0 +1,20 @@
1
+ import type { ParserState } from '../types.ts'
2
+ import { removeEdge, removeUnlinkedNodes } from './helpers.ts'
3
+
4
+ /**
5
+ * :overridden Pseudo-Selector, matches only edges that have a truthy overridden property in their spec.
6
+ * It filters out any edges that don't have edge.spec.overridden set to a truthy value
7
+ * and removes any unlinked nodes from the result.
8
+ */
9
+ export const overridden = async (state: ParserState) => {
10
+ // filter edges that don't have a truthy overridden property
11
+ for (const edge of state.partial.edges) {
12
+ if (!edge.spec.overridden) {
13
+ removeEdge(state, edge)
14
+ }
15
+ }
16
+
17
+ removeUnlinkedNodes(state)
18
+
19
+ return state
20
+ }
@@ -0,0 +1,146 @@
1
+ import { minimatch } from 'minimatch'
2
+ import { error } from '@vltpkg/error-cause'
3
+ import { splitDepID } from '@vltpkg/dep-id/browser'
4
+ import { asError } from '@vltpkg/types'
5
+ import {
6
+ asPostcssNodeWithChildren,
7
+ asStringNode,
8
+ isStringNode,
9
+ } from '@vltpkg/dss-parser'
10
+ import {
11
+ removeNode,
12
+ removeDanglingEdges,
13
+ removeQuotes,
14
+ clear,
15
+ } from './helpers.ts'
16
+ import type { ParserState } from '../types.ts'
17
+
18
+ /**
19
+ * Normalizes a path by trimming whitespace and removing leading './' or '.' prefix.
20
+ * This ensures consistent path comparison regardless of how paths are specified.
21
+ */
22
+ export function normalizePath(path: string): string {
23
+ const trimmed = path.trim().replaceAll('\\', '/')
24
+
25
+ if (trimmed === '.') {
26
+ return ''
27
+ }
28
+
29
+ if (trimmed.startsWith('./')) {
30
+ return trimmed.slice(2)
31
+ }
32
+
33
+ return trimmed
34
+ }
35
+
36
+ /**
37
+ * Creates a path matcher function that tests if
38
+ * a given path matches a glob pattern.
39
+ */
40
+ export function createPathMatcher(pattern: string, loose = false) {
41
+ const normalizedPattern = normalizePath(pattern)
42
+ const isRoot = normalizedPattern === '' || pattern.trim() === '.'
43
+
44
+ return (path: string) => {
45
+ const normalizedPath = normalizePath(path)
46
+
47
+ if (isRoot) {
48
+ return normalizedPath === '' || normalizedPath === '.'
49
+ }
50
+ try {
51
+ return minimatch(normalizedPath, normalizedPattern, {
52
+ dot: true,
53
+ nocase: false,
54
+ matchBase: normalizedPattern === '*',
55
+ })
56
+ } catch (err) {
57
+ // In loose mode, return false for invalid patterns
58
+ if (loose) {
59
+ return false
60
+ }
61
+ // In strict mode, throw an error
62
+ throw error(
63
+ 'Invalid glob pattern in :path selector',
64
+ asError(err),
65
+ )
66
+ }
67
+ }
68
+ }
69
+
70
+ /**
71
+ * :path("glob") Pseudo-Selector will match only workspace & file
72
+ * nodes whose file path matches the provided glob pattern relative
73
+ * to the project root. Path patterns must be quoted strings to avoid
74
+ * parser conflicts with special characters.
75
+ */
76
+ export const path = async (state: ParserState) => {
77
+ // Extract path container and handle empty parameters
78
+ const pathContainer = asPostcssNodeWithChildren(state.current)
79
+ if (!pathContainer.nodes[0]) {
80
+ return clear(state)
81
+ }
82
+
83
+ // Extract the selector node and handle empty selectors
84
+ const selector = asPostcssNodeWithChildren(pathContainer.nodes[0])
85
+ if (!selector.nodes[0]) {
86
+ return clear(state)
87
+ }
88
+
89
+ // Extract and validate the path pattern
90
+ let pathPattern = ''
91
+ if (isStringNode(selector.nodes[0])) {
92
+ pathPattern = removeQuotes(asStringNode(selector.nodes[0]).value)
93
+ } else {
94
+ if (state.loose) {
95
+ return clear(state)
96
+ }
97
+ throw error(
98
+ 'Failed to parse path pattern in :path selector',
99
+ new Error('Path pattern must be a quoted string'),
100
+ )
101
+ }
102
+
103
+ // If no pattern provided, match nothing
104
+ if (!pathPattern) {
105
+ return clear(state)
106
+ }
107
+
108
+ // Check for unmatched brackets
109
+ if (
110
+ pathPattern === '[' ||
111
+ (pathPattern.includes('[') && !pathPattern.includes(']'))
112
+ ) {
113
+ if (state.loose) {
114
+ return clear(state)
115
+ }
116
+ throw error(
117
+ 'Invalid glob pattern in :path selector',
118
+ new Error(`Unmatched bracket in pattern: ${pathPattern}`),
119
+ )
120
+ }
121
+
122
+ // Create path matcher function
123
+ const matchPath = createPathMatcher(pathPattern, state.loose)
124
+
125
+ // Filter nodes by path
126
+ for (const node of state.partial.nodes) {
127
+ /* c8 ignore next */
128
+ const nodePath = node.location || ''
129
+ const [type] = splitDepID(node.id)
130
+
131
+ // Special case for main importer with root path pattern
132
+ if (pathPattern === '.' && node.mainImporter) {
133
+ continue // Keep main importer node for root path pattern
134
+ }
135
+
136
+ // Only match workspace or file nodes with matching paths
137
+ const pathBased = type === 'workspace' || type === 'file'
138
+ if (!pathBased || !matchPath(nodePath)) {
139
+ removeNode(state, node)
140
+ }
141
+ }
142
+
143
+ // Clean up any dangling edges
144
+ removeDanglingEdges(state)
145
+ return state
146
+ }
@@ -0,0 +1,18 @@
1
+ import type { ParserState } from '../types.ts'
2
+ import { removeEdge, removeUnlinkedNodes } from './helpers.ts'
3
+
4
+ /**
5
+ * :peer Pseudo-Selector will only match peer dependencies.
6
+ */
7
+ export const peer = async (state: ParserState) => {
8
+ // filter edges that aren't marked as peer
9
+ for (const edge of state.partial.edges) {
10
+ if (!edge.peer) {
11
+ removeEdge(state, edge)
12
+ }
13
+ }
14
+
15
+ removeUnlinkedNodes(state)
16
+
17
+ return state
18
+ }
@@ -0,0 +1,47 @@
1
+ import { parse } from '@vltpkg/semver'
2
+ import { removeNode, removeDanglingEdges } from './helpers.ts'
3
+ import type { ParserState } from '../types.ts'
4
+
5
+ /**
6
+ * :prerelease Pseudo-selector, matches only nodes that have prerelease
7
+ * elements in their parsed semver value.
8
+ *
9
+ * Examples of versions that should match:
10
+ * - 19.2.0-canary-fa3feba6-20250623
11
+ * - 1.0.0-beta.0
12
+ * - 0.0.0-16
13
+ * - 1.2.3-rc.1+rev.2
14
+ *
15
+ * Examples of versions that should NOT match:
16
+ * - 1.0.0
17
+ * - 99.9.9
18
+ * - 0.0.1
19
+ */
20
+ export const prerelease = async (state: ParserState) => {
21
+ for (const node of state.partial.nodes) {
22
+ const version = node.manifest?.version
23
+
24
+ // Skip nodes without a version (like root/importer nodes)
25
+ if (!version) {
26
+ removeNode(state, node)
27
+ continue
28
+ }
29
+
30
+ const parsedVersion = parse(version)
31
+ if (!parsedVersion) {
32
+ // Remove nodes with invalid semver versions
33
+ removeNode(state, node)
34
+ continue
35
+ }
36
+
37
+ // Check if the version has prerelease identifiers
38
+ if (!parsedVersion.prerelease?.length) {
39
+ // Remove nodes that don't have prerelease identifiers
40
+ removeNode(state, node)
41
+ }
42
+ }
43
+
44
+ removeDanglingEdges(state)
45
+
46
+ return state
47
+ }
@@ -0,0 +1,19 @@
1
+ import { asManifest } from '@vltpkg/types'
2
+ import type { ParserState } from '../types.ts'
3
+ import { removeNode, removeDanglingEdges } from './helpers.ts'
4
+
5
+ /**
6
+ * :private Pseudo-Selector will only match packages that have
7
+ * a `private: true` key set in their `package.json` metadata.
8
+ */
9
+ export const privateParser = async (state: ParserState) => {
10
+ for (const node of state.partial.nodes) {
11
+ if (!node.manifest || !asManifest(node.manifest).private) {
12
+ removeNode(state, node)
13
+ }
14
+ }
15
+
16
+ removeDanglingEdges(state)
17
+
18
+ return state
19
+ }
@@ -0,0 +1,18 @@
1
+ import type { ParserState } from '../types.ts'
2
+ import { removeEdge, removeUnlinkedNodes } from './helpers.ts'
3
+
4
+ /**
5
+ * :prod Pseudo-Selector will only match production dependencies.
6
+ */
7
+ export const prod = async (state: ParserState) => {
8
+ // filter edges that don't have type 'prod'
9
+ for (const edge of state.partial.edges) {
10
+ if (edge.type !== 'prod') {
11
+ removeEdge(state, edge)
12
+ }
13
+ }
14
+
15
+ removeUnlinkedNodes(state)
16
+
17
+ return state
18
+ }