@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.
- package/dist/index.js +10 -6
- package/dist/parser.js +2 -1
- package/dist/pseudo/hostname.js +3 -1
- package/dist/pseudo/malware.d.ts +3 -16
- package/dist/pseudo/malware.js +11 -171
- package/dist/pseudo/scripts.js +1 -1
- package/dist/pseudo/vuln.d.ts +22 -0
- package/dist/pseudo/vuln.js +220 -0
- package/dist/pseudo.js +3 -3
- package/dist/types.d.ts +3 -2
- package/package.json +19 -10
- package/skills/dss-query/REFERENCE.md +123 -0
- package/skills/dss-query/SKILL.md +190 -0
- package/skills/dss-query/evals/README.md +79 -0
- package/skills/dss-query/evals/evals.json +89 -0
- package/skills/dss-query/evals/grade.mjs +480 -0
- package/src/attribute.ts +186 -0
- package/src/combinator.ts +132 -0
- package/src/id.ts +46 -0
- package/src/index.ts +620 -0
- package/src/parser.ts +122 -0
- package/src/pseudo/abandoned.ts +9 -0
- package/src/pseudo/attr.ts +92 -0
- package/src/pseudo/built.ts +19 -0
- package/src/pseudo/confused.ts +29 -0
- package/src/pseudo/cve.ts +93 -0
- package/src/pseudo/cwe.ts +97 -0
- package/src/pseudo/debug.ts +9 -0
- package/src/pseudo/deprecated.ts +9 -0
- package/src/pseudo/dev.ts +18 -0
- package/src/pseudo/diff.ts +90 -0
- package/src/pseudo/dist.ts +144 -0
- package/src/pseudo/dynamic.ts +9 -0
- package/src/pseudo/empty.ts +15 -0
- package/src/pseudo/entropic.ts +9 -0
- package/src/pseudo/env.ts +6 -0
- package/src/pseudo/eval.ts +9 -0
- package/src/pseudo/fs.ts +9 -0
- package/src/pseudo/helpers.ts +106 -0
- package/src/pseudo/host.ts +111 -0
- package/src/pseudo/hostname.ts +156 -0
- package/src/pseudo/license.ts +134 -0
- package/src/pseudo/link.ts +30 -0
- package/src/pseudo/malware.ts +41 -0
- package/src/pseudo/minified.ts +9 -0
- package/src/pseudo/missing.ts +16 -0
- package/src/pseudo/native.ts +9 -0
- package/src/pseudo/network.ts +9 -0
- package/src/pseudo/obfuscated.ts +9 -0
- package/src/pseudo/optional.ts +18 -0
- package/src/pseudo/outdated.ts +305 -0
- package/src/pseudo/overridden.ts +20 -0
- package/src/pseudo/path.ts +146 -0
- package/src/pseudo/peer.ts +18 -0
- package/src/pseudo/prerelease.ts +47 -0
- package/src/pseudo/private.ts +19 -0
- package/src/pseudo/prod.ts +18 -0
- package/src/pseudo/published.ts +248 -0
- package/src/pseudo/registry.ts +29 -0
- package/src/pseudo/root.ts +19 -0
- package/src/pseudo/scanned.ts +20 -0
- package/src/pseudo/score.ts +186 -0
- package/src/pseudo/scripts.ts +54 -0
- package/src/pseudo/semver.ts +320 -0
- package/src/pseudo/severity.ts +231 -0
- package/src/pseudo/shell.ts +9 -0
- package/src/pseudo/shrinkwrap.ts +9 -0
- package/src/pseudo/spec.ts +131 -0
- package/src/pseudo/squat.ts +225 -0
- package/src/pseudo/suspicious.ts +9 -0
- package/src/pseudo/tracker.ts +9 -0
- package/src/pseudo/trivial.ts +9 -0
- package/src/pseudo/type.ts +26 -0
- package/src/pseudo/undesirable.ts +9 -0
- package/src/pseudo/unknown.ts +9 -0
- package/src/pseudo/unmaintained.ts +9 -0
- package/src/pseudo/unpopular.ts +9 -0
- package/src/pseudo/unstable.ts +9 -0
- package/src/pseudo/vuln.ts +273 -0
- package/src/pseudo/workspace.ts +22 -0
- package/src/pseudo.ts +413 -0
- package/src/types.ts +165 -0
- package/tap-snapshots/test/attribute.ts.test.cjs +304 -0
- package/tap-snapshots/test/combinator.ts.test.cjs +333 -0
- package/tap-snapshots/test/id.ts.test.cjs +118 -0
- package/tap-snapshots/test/parser.ts.test.cjs +344 -0
- package/tap-snapshots/test/pseudo/abandoned.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/attr.ts.test.cjs +161 -0
- package/tap-snapshots/test/pseudo/built.ts.test.cjs +70 -0
- package/tap-snapshots/test/pseudo/confused.ts.test.cjs +32 -0
- package/tap-snapshots/test/pseudo/cve.ts.test.cjs +49 -0
- package/tap-snapshots/test/pseudo/cwe.ts.test.cjs +49 -0
- package/tap-snapshots/test/pseudo/debug.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/deprecated.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/dist.ts.test.cjs +30 -0
- package/tap-snapshots/test/pseudo/dynamic.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/empty.ts.test.cjs +50 -0
- package/tap-snapshots/test/pseudo/entropic.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/env.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/eval.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/fs.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/helpers.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/hostname.ts.test.cjs +148 -0
- package/tap-snapshots/test/pseudo/license.ts.test.cjs +41 -0
- package/tap-snapshots/test/pseudo/link.ts.test.cjs +36 -0
- package/tap-snapshots/test/pseudo/malware.ts.test.cjs +22 -0
- package/tap-snapshots/test/pseudo/minified.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/missing.ts.test.cjs +30 -0
- package/tap-snapshots/test/pseudo/native.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/network.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/obfuscated.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/outdated.ts.test.cjs +130 -0
- package/tap-snapshots/test/pseudo/overridden.ts.test.cjs +75 -0
- package/tap-snapshots/test/pseudo/prerelease.ts.test.cjs +101 -0
- package/tap-snapshots/test/pseudo/private.ts.test.cjs +38 -0
- package/tap-snapshots/test/pseudo/published.ts.test.cjs +171 -0
- package/tap-snapshots/test/pseudo/registry.ts.test.cjs +68 -0
- package/tap-snapshots/test/pseudo/root.ts.test.cjs +24 -0
- package/tap-snapshots/test/pseudo/scanned.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/score.ts.test.cjs +239 -0
- package/tap-snapshots/test/pseudo/scripts.ts.test.cjs +61 -0
- package/tap-snapshots/test/pseudo/semver.ts.test.cjs +575 -0
- package/tap-snapshots/test/pseudo/severity.ts.test.cjs +95 -0
- package/tap-snapshots/test/pseudo/shell.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/shrinkwrap.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/spec.ts.test.cjs +148 -0
- package/tap-snapshots/test/pseudo/squat.ts.test.cjs +129 -0
- package/tap-snapshots/test/pseudo/suspicious.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/tracker.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/trivial.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/type.ts.test.cjs +51 -0
- package/tap-snapshots/test/pseudo/undesirable.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/unknown.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/unmaintained.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/unpopular.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/unstable.ts.test.cjs +18 -0
- package/tap-snapshots/test/pseudo/vuln.ts.test.cjs +208 -0
- package/tap-snapshots/test/pseudo/vulnerable.ts.test.cjs +20 -0
- package/tap-snapshots/test/pseudo/workspace.ts.test.cjs +19 -0
- package/tap-snapshots/test/pseudo.ts.test.cjs +1247 -0
- package/test/attribute.ts +266 -0
- package/test/combinator.ts +168 -0
- package/test/fixtures/graph.ts +999 -0
- package/test/fixtures/selector.ts +103 -0
- package/test/fixtures/types.ts +7 -0
- package/test/id.ts +102 -0
- package/test/index.ts +1000 -0
- package/test/parser.ts +117 -0
- package/test/pseudo/abandoned.ts +98 -0
- package/test/pseudo/attr.ts +298 -0
- package/test/pseudo/built.ts +214 -0
- package/test/pseudo/confused.ts +160 -0
- package/test/pseudo/cve.ts +249 -0
- package/test/pseudo/cwe.ts +255 -0
- package/test/pseudo/debug.ts +98 -0
- package/test/pseudo/deprecated.ts +98 -0
- package/test/pseudo/dev.ts +61 -0
- package/test/pseudo/diff.ts +417 -0
- package/test/pseudo/dist.ts +215 -0
- package/test/pseudo/dynamic.ts +98 -0
- package/test/pseudo/empty.ts +108 -0
- package/test/pseudo/entropic.ts +101 -0
- package/test/pseudo/env.ts +98 -0
- package/test/pseudo/eval.ts +98 -0
- package/test/pseudo/fs.ts +98 -0
- package/test/pseudo/helpers.ts +370 -0
- package/test/pseudo/host-context.ts +276 -0
- package/test/pseudo/hostname.ts +314 -0
- package/test/pseudo/license.ts +265 -0
- package/test/pseudo/link.ts +94 -0
- package/test/pseudo/malware.ts +184 -0
- package/test/pseudo/minified.ts +98 -0
- package/test/pseudo/missing.ts +111 -0
- package/test/pseudo/native.ts +98 -0
- package/test/pseudo/network.ts +98 -0
- package/test/pseudo/obfuscated.ts +98 -0
- package/test/pseudo/optional.ts +73 -0
- package/test/pseudo/outdated.ts +331 -0
- package/test/pseudo/overridden.ts +317 -0
- package/test/pseudo/path.ts +680 -0
- package/test/pseudo/peer.ts +101 -0
- package/test/pseudo/prerelease.ts +279 -0
- package/test/pseudo/private.ts +108 -0
- package/test/pseudo/prod.ts +61 -0
- package/test/pseudo/published.ts +557 -0
- package/test/pseudo/registry.ts +122 -0
- package/test/pseudo/root.ts +66 -0
- package/test/pseudo/scanned.ts +78 -0
- package/test/pseudo/score.ts +591 -0
- package/test/pseudo/scripts.ts +294 -0
- package/test/pseudo/semver.ts +822 -0
- package/test/pseudo/severity.ts +310 -0
- package/test/pseudo/shell.ts +98 -0
- package/test/pseudo/shrinkwrap.ts +98 -0
- package/test/pseudo/spec.ts +525 -0
- package/test/pseudo/squat.ts +565 -0
- package/test/pseudo/suspicious.ts +98 -0
- package/test/pseudo/tracker.ts +98 -0
- package/test/pseudo/trivial.ts +98 -0
- package/test/pseudo/type.ts +105 -0
- package/test/pseudo/undesirable.ts +98 -0
- package/test/pseudo/unknown.ts +98 -0
- package/test/pseudo/unmaintained.ts +98 -0
- package/test/pseudo/unpopular.ts +98 -0
- package/test/pseudo/unstable.ts +98 -0
- package/test/pseudo/vuln.ts +625 -0
- package/test/pseudo/vulnerable.ts +137 -0
- package/test/pseudo/workspace.ts +111 -0
- package/test/pseudo.ts +558 -0
- package/tsconfig.json +3 -0
- package/tsconfig.publish.json +19 -0
- package/tsconfig.publish.tsbuildinfo +1 -0
- package/typedoc.mjs +2 -0
- package/dist/pseudo/vulnerable.d.ts +0 -8
- 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
|
+
}
|