@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,680 @@
|
|
|
1
|
+
import t from 'tap'
|
|
2
|
+
import { parse } from '@vltpkg/dss-parser'
|
|
3
|
+
import {
|
|
4
|
+
getPathBasedGraph,
|
|
5
|
+
getSimpleGraph,
|
|
6
|
+
} from '../fixtures/graph.ts'
|
|
7
|
+
import type { ParserState } from '../../src/types.ts'
|
|
8
|
+
import type { PostcssNode as _PostcssNode } from '@vltpkg/dss-parser'
|
|
9
|
+
import { joinDepIDTuple, splitDepID } from '@vltpkg/dep-id/browser'
|
|
10
|
+
|
|
11
|
+
const getState = (query: string, graph = getPathBasedGraph()) => {
|
|
12
|
+
const ast = parse(query)
|
|
13
|
+
const current = ast.first.first
|
|
14
|
+
const state: ParserState = {
|
|
15
|
+
comment: '',
|
|
16
|
+
current,
|
|
17
|
+
initial: {
|
|
18
|
+
edges: new Set(graph.edges.values()),
|
|
19
|
+
nodes: new Set(graph.nodes.values()),
|
|
20
|
+
},
|
|
21
|
+
partial: {
|
|
22
|
+
edges: new Set(graph.edges.values()),
|
|
23
|
+
nodes: new Set(graph.nodes.values()),
|
|
24
|
+
},
|
|
25
|
+
collect: {
|
|
26
|
+
edges: new Set(),
|
|
27
|
+
nodes: new Set(),
|
|
28
|
+
},
|
|
29
|
+
cancellable: async () => {},
|
|
30
|
+
walk: async i => i,
|
|
31
|
+
retries: 0,
|
|
32
|
+
securityArchive: undefined,
|
|
33
|
+
importers: new Set(graph.importers),
|
|
34
|
+
signal: new AbortController().signal,
|
|
35
|
+
specificity: { idCounter: 0, commonCounter: 0 },
|
|
36
|
+
loose: false,
|
|
37
|
+
}
|
|
38
|
+
return state
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Create a custom state for testing edge cases
|
|
42
|
+
const createCustomState = (options: {
|
|
43
|
+
withEmptyNodes?: boolean
|
|
44
|
+
withEmptySelector?: boolean
|
|
45
|
+
withNonStringSelector?: boolean
|
|
46
|
+
withBrokenSelector?: boolean
|
|
47
|
+
}) => {
|
|
48
|
+
const state = getState(':path("*")')
|
|
49
|
+
|
|
50
|
+
if (options.withEmptyNodes) {
|
|
51
|
+
// Remove nodes completely
|
|
52
|
+
// @ts-expect-error - we're deliberately breaking things for testing
|
|
53
|
+
state.current.nodes = []
|
|
54
|
+
} else if (options.withEmptySelector) {
|
|
55
|
+
// Make the selector have empty nodes
|
|
56
|
+
// @ts-expect-error - we're deliberately breaking things for testing
|
|
57
|
+
if (state.current.nodes?.[0]) {
|
|
58
|
+
// @ts-expect-error - we're deliberately breaking things for testing
|
|
59
|
+
state.current.nodes[0].nodes = []
|
|
60
|
+
}
|
|
61
|
+
} else if (options.withNonStringSelector) {
|
|
62
|
+
// Replace string node with non-string node
|
|
63
|
+
// @ts-expect-error - we're deliberately breaking things for testing
|
|
64
|
+
if (state.current.nodes?.[0]?.nodes) {
|
|
65
|
+
// @ts-expect-error - we're deliberately breaking things for testing
|
|
66
|
+
state.current.nodes[0].nodes[0].type = 'comment'
|
|
67
|
+
}
|
|
68
|
+
} else if (options.withBrokenSelector) {
|
|
69
|
+
// Remove the nodes array from the selector
|
|
70
|
+
// @ts-expect-error - we're deliberately breaking things for testing
|
|
71
|
+
if (state.current.nodes?.[0]) {
|
|
72
|
+
// @ts-expect-error - we're deliberately breaking things for testing
|
|
73
|
+
delete state.current.nodes[0].nodes
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return state
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
t.test('createPathMatcher function', async t => {
|
|
81
|
+
// Import the module inside the test so we can mock it
|
|
82
|
+
const { createPathMatcher } = await t.mockImport<
|
|
83
|
+
typeof import('../../src/pseudo/path.ts')
|
|
84
|
+
>('../../src/pseudo/path.ts', {
|
|
85
|
+
minimatch: {
|
|
86
|
+
minimatch: (
|
|
87
|
+
path: string,
|
|
88
|
+
pattern: string,
|
|
89
|
+
options: any = {},
|
|
90
|
+
) => {
|
|
91
|
+
if (pattern === 'throw-error') {
|
|
92
|
+
throw new Error('Minimatch error')
|
|
93
|
+
}
|
|
94
|
+
// Special handling for * pattern with matchBase option
|
|
95
|
+
if (pattern === '*' && options.matchBase) {
|
|
96
|
+
return true // Always match for * with matchBase
|
|
97
|
+
}
|
|
98
|
+
return path.includes(pattern)
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
await t.test('handles root path pattern', async t => {
|
|
104
|
+
const matcher = createPathMatcher('.')
|
|
105
|
+
t.equal(matcher('.'), true, 'should match dot path')
|
|
106
|
+
t.equal(matcher(''), true, 'should match empty path')
|
|
107
|
+
t.equal(matcher('foo'), false, 'should not match other paths')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
await t.test('creates glob matcher for non-root paths', async t => {
|
|
111
|
+
const matcher = createPathMatcher('packages')
|
|
112
|
+
t.equal(matcher('packages/a'), true, 'should match glob pattern')
|
|
113
|
+
t.equal(matcher('packages/b'), true, 'should match glob pattern')
|
|
114
|
+
t.equal(
|
|
115
|
+
matcher('other/c'),
|
|
116
|
+
false,
|
|
117
|
+
'should not match non-matching paths',
|
|
118
|
+
)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
await t.test(
|
|
122
|
+
'handles invalid glob patterns in loose mode',
|
|
123
|
+
async t => {
|
|
124
|
+
const matcher = createPathMatcher('throw-error', true)
|
|
125
|
+
t.equal(
|
|
126
|
+
matcher('any-path'),
|
|
127
|
+
false,
|
|
128
|
+
'should return false for any path',
|
|
129
|
+
)
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
await t.test(
|
|
134
|
+
'throws for invalid glob patterns in strict mode',
|
|
135
|
+
async t => {
|
|
136
|
+
t.throws(
|
|
137
|
+
() => createPathMatcher('throw-error')('any-path'),
|
|
138
|
+
/Invalid glob pattern in :path selector/,
|
|
139
|
+
'should throw with correct message',
|
|
140
|
+
)
|
|
141
|
+
},
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
await t.test(
|
|
145
|
+
'creates matcher with matchBase option for * pattern',
|
|
146
|
+
async t => {
|
|
147
|
+
const matcher = createPathMatcher('*')
|
|
148
|
+
t.equal(matcher('file.txt'), true, 'should match any file')
|
|
149
|
+
t.equal(
|
|
150
|
+
matcher('dir/file.txt'),
|
|
151
|
+
true,
|
|
152
|
+
'should match files in subdirectories',
|
|
153
|
+
)
|
|
154
|
+
},
|
|
155
|
+
)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
t.test(':path selector', async t => {
|
|
159
|
+
// Import the real module for the path selector tests
|
|
160
|
+
const { path, normalizePath, createPathMatcher } =
|
|
161
|
+
await import('../../src/pseudo/path.ts')
|
|
162
|
+
|
|
163
|
+
await t.test('normalizePath function', async t => {
|
|
164
|
+
await t.test('trims whitespace', async t => {
|
|
165
|
+
t.equal(
|
|
166
|
+
normalizePath(' packages/a '),
|
|
167
|
+
'packages/a',
|
|
168
|
+
'should trim leading and trailing whitespace',
|
|
169
|
+
)
|
|
170
|
+
t.equal(
|
|
171
|
+
normalizePath('\tpackages/b\n'),
|
|
172
|
+
'packages/b',
|
|
173
|
+
'should trim tabs and newlines',
|
|
174
|
+
)
|
|
175
|
+
t.equal(
|
|
176
|
+
normalizePath(' ./packages/c '),
|
|
177
|
+
'packages/c',
|
|
178
|
+
'should trim whitespace and remove ./ prefix',
|
|
179
|
+
)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
await t.test('removes leading ./ prefix', async t => {
|
|
183
|
+
t.equal(
|
|
184
|
+
normalizePath('./packages/a'),
|
|
185
|
+
'packages/a',
|
|
186
|
+
'should remove ./ prefix',
|
|
187
|
+
)
|
|
188
|
+
t.equal(
|
|
189
|
+
normalizePath('./x'),
|
|
190
|
+
'x',
|
|
191
|
+
'should remove ./ prefix from simple paths',
|
|
192
|
+
)
|
|
193
|
+
t.equal(
|
|
194
|
+
normalizePath('./packages/nested/deep'),
|
|
195
|
+
'packages/nested/deep',
|
|
196
|
+
'should remove ./ prefix from nested paths',
|
|
197
|
+
)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
await t.test('handles root path normalization', async t => {
|
|
201
|
+
t.equal(
|
|
202
|
+
normalizePath('.'),
|
|
203
|
+
'',
|
|
204
|
+
'should normalize . to empty string',
|
|
205
|
+
)
|
|
206
|
+
t.equal(
|
|
207
|
+
normalizePath(' . '),
|
|
208
|
+
'',
|
|
209
|
+
'should normalize trimmed . to empty string',
|
|
210
|
+
)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
await t.test('preserves other dot patterns', async t => {
|
|
214
|
+
t.equal(
|
|
215
|
+
normalizePath('.config'),
|
|
216
|
+
'.config',
|
|
217
|
+
'should preserve .config paths',
|
|
218
|
+
)
|
|
219
|
+
t.equal(
|
|
220
|
+
normalizePath('.github/workflows'),
|
|
221
|
+
'.github/workflows',
|
|
222
|
+
'should preserve .github paths',
|
|
223
|
+
)
|
|
224
|
+
t.equal(
|
|
225
|
+
normalizePath('packages/.hidden'),
|
|
226
|
+
'packages/.hidden',
|
|
227
|
+
'should preserve hidden files in subdirectories',
|
|
228
|
+
)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
await t.test('handles edge cases', async t => {
|
|
232
|
+
t.equal(normalizePath(''), '', 'should handle empty string')
|
|
233
|
+
t.equal(
|
|
234
|
+
normalizePath(' '),
|
|
235
|
+
'',
|
|
236
|
+
'should handle whitespace-only string',
|
|
237
|
+
)
|
|
238
|
+
t.equal(
|
|
239
|
+
normalizePath('packages/a'),
|
|
240
|
+
'packages/a',
|
|
241
|
+
'should leave already normalized paths unchanged',
|
|
242
|
+
)
|
|
243
|
+
})
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
await t.test('createPathMatcher with normalization', async t => {
|
|
247
|
+
await t.test(
|
|
248
|
+
'pattern and path normalization matching',
|
|
249
|
+
async t => {
|
|
250
|
+
const matcher1 = createPathMatcher('packages/a')
|
|
251
|
+
t.ok(
|
|
252
|
+
matcher1('./packages/a'),
|
|
253
|
+
'location ./packages/a should match packages/a pattern',
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
const matcher2 = createPathMatcher('packages/*')
|
|
257
|
+
t.ok(
|
|
258
|
+
matcher2('./packages/a'),
|
|
259
|
+
'location ./packages/a should match packages/* pattern',
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
const matcher3 = createPathMatcher('./packages/a ')
|
|
263
|
+
t.ok(
|
|
264
|
+
matcher3('packages/a'),
|
|
265
|
+
'location packages/a should match ./packages/a (with trailing spaces) pattern',
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
const matcher4 = createPathMatcher(' packages/* ')
|
|
269
|
+
t.ok(
|
|
270
|
+
matcher4('./packages/b'),
|
|
271
|
+
'location ./packages/b should match trimmed packages/* pattern',
|
|
272
|
+
)
|
|
273
|
+
},
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
await t.test('glob patterns with normalization', async t => {
|
|
277
|
+
const matcher1 = createPathMatcher('./packages/**')
|
|
278
|
+
t.ok(
|
|
279
|
+
matcher1('packages/a/nested'),
|
|
280
|
+
'should match nested paths after normalization',
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
const matcher2 = createPathMatcher(' **/a/* ')
|
|
284
|
+
t.ok(
|
|
285
|
+
matcher2('./some/path/a/file'),
|
|
286
|
+
'should match double glob patterns after normalization',
|
|
287
|
+
)
|
|
288
|
+
})
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
await t.test('path normalization integration tests', async t => {
|
|
292
|
+
// Create a custom graph with nodes that have ./ prefixed locations
|
|
293
|
+
const createNormalizedTestGraph = () => {
|
|
294
|
+
const graph = getPathBasedGraph()
|
|
295
|
+
|
|
296
|
+
// Modify some node locations to have ./ prefixes for testing
|
|
297
|
+
const nodes = Array.from(graph.nodes.values())
|
|
298
|
+
const nodeA = nodes.find(n => n.name === 'a')
|
|
299
|
+
const nodeX = nodes.find(n => n.name === 'x')
|
|
300
|
+
|
|
301
|
+
if (nodeA) {
|
|
302
|
+
nodeA.location = './packages/a' // Add ./ prefix
|
|
303
|
+
}
|
|
304
|
+
if (nodeX) {
|
|
305
|
+
nodeX.location = './x' // Add ./ prefix
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return graph
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
await t.test(
|
|
312
|
+
'matches ./prefixed locations with normalized patterns',
|
|
313
|
+
async t => {
|
|
314
|
+
const graph = createNormalizedTestGraph()
|
|
315
|
+
const res = await path(getState(':path("packages/a")', graph))
|
|
316
|
+
t.strictSame(
|
|
317
|
+
[...res.partial.nodes].map(n => n.name),
|
|
318
|
+
['a'],
|
|
319
|
+
'location ./packages/a should match packages/a pattern',
|
|
320
|
+
)
|
|
321
|
+
},
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
await t.test(
|
|
325
|
+
'matches ./prefixed locations with glob patterns',
|
|
326
|
+
async t => {
|
|
327
|
+
const graph = createNormalizedTestGraph()
|
|
328
|
+
const res = await path(getState(':path("packages/*")', graph))
|
|
329
|
+
const nodeNames = [...res.partial.nodes]
|
|
330
|
+
.map(n => n.name)
|
|
331
|
+
.sort()
|
|
332
|
+
t.ok(
|
|
333
|
+
nodeNames.includes('a'),
|
|
334
|
+
'location ./packages/a should match packages/* pattern',
|
|
335
|
+
)
|
|
336
|
+
t.ok(
|
|
337
|
+
nodeNames.includes('b'),
|
|
338
|
+
'location packages/b should also match packages/* pattern',
|
|
339
|
+
)
|
|
340
|
+
},
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
await t.test(
|
|
344
|
+
'matches regular locations with ./prefixed patterns',
|
|
345
|
+
async t => {
|
|
346
|
+
const res = await path(getState(':path("./packages/a ")'))
|
|
347
|
+
t.strictSame(
|
|
348
|
+
[...res.partial.nodes].map(n => n.name),
|
|
349
|
+
['a'],
|
|
350
|
+
'location packages/a should match ./packages/a (with trailing spaces) pattern',
|
|
351
|
+
)
|
|
352
|
+
},
|
|
353
|
+
)
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
await t.test('matches all workspace and file nodes', async t => {
|
|
357
|
+
const res = await path(getState(':path("*")'))
|
|
358
|
+
t.strictSame(
|
|
359
|
+
[...res.partial.nodes].map(n => n.name).sort(),
|
|
360
|
+
['a', 'b', 'c', 'path-based-project', 'x', 'y'],
|
|
361
|
+
'should match all workspace and file nodes',
|
|
362
|
+
)
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
await t.test('matches workspace packages only', async t => {
|
|
366
|
+
const res = await path(getState(':path("packages/*")'))
|
|
367
|
+
t.strictSame(
|
|
368
|
+
[...res.partial.nodes].map(n => n.name).sort(),
|
|
369
|
+
['a', 'b'],
|
|
370
|
+
'should match workspace packages in packages directory',
|
|
371
|
+
)
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
await t.test('matches specific workspace', async t => {
|
|
375
|
+
const res = await path(getState(':path("packages/a")'))
|
|
376
|
+
t.strictSame(
|
|
377
|
+
[...res.partial.nodes].map(n => n.name),
|
|
378
|
+
['a'],
|
|
379
|
+
'should match specific workspace package',
|
|
380
|
+
)
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
await t.test('matches root project', async t => {
|
|
384
|
+
const res = await path(getState(':path(".")'))
|
|
385
|
+
t.strictSame(
|
|
386
|
+
[...res.partial.nodes].map(n => n.name),
|
|
387
|
+
['path-based-project'],
|
|
388
|
+
'should match root project',
|
|
389
|
+
)
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
await t.test('matches file dependencies', async t => {
|
|
393
|
+
const res = await path(getState(':path("x")'))
|
|
394
|
+
t.strictSame(
|
|
395
|
+
[...res.partial.nodes].map(n => n.name),
|
|
396
|
+
['x'],
|
|
397
|
+
'should match file dependency',
|
|
398
|
+
)
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
await t.test('matches with glob patterns', async t => {
|
|
402
|
+
const res = await path(getState(':path("packages/**")'))
|
|
403
|
+
t.strictSame(
|
|
404
|
+
[...res.partial.nodes].map(n => n.name).sort(),
|
|
405
|
+
['a', 'b', 'y'],
|
|
406
|
+
'should match all items under packages directory using glob',
|
|
407
|
+
)
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
await t.test('matches nested file paths', async t => {
|
|
411
|
+
const res = await path(getState(':path("packages/a/*")'))
|
|
412
|
+
t.strictSame(
|
|
413
|
+
[...res.partial.nodes].map(n => n.name),
|
|
414
|
+
['y'],
|
|
415
|
+
'should match nested file dependency',
|
|
416
|
+
)
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
await t.test('matches nested paths with double glob', async t => {
|
|
420
|
+
const res = await path(getState(':path("**/a/*")'))
|
|
421
|
+
t.strictSame(
|
|
422
|
+
[...res.partial.nodes].map(n => n.name),
|
|
423
|
+
['y'],
|
|
424
|
+
'should match nested paths using double glob pattern',
|
|
425
|
+
)
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
await t.test('no matches for non-existent paths', async t => {
|
|
429
|
+
const res = await path(getState(':path("nonexistent/**")'))
|
|
430
|
+
t.strictSame(
|
|
431
|
+
[...res.partial.nodes].map(n => n.name),
|
|
432
|
+
[],
|
|
433
|
+
'should not match any nodes for non-existent paths',
|
|
434
|
+
)
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
await t.test('handles empty pattern', async t => {
|
|
438
|
+
const res = await path(getState(':path("")'))
|
|
439
|
+
t.strictSame(
|
|
440
|
+
[...res.partial.nodes].map(n => n.name),
|
|
441
|
+
[],
|
|
442
|
+
'should match nothing for empty pattern',
|
|
443
|
+
)
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
await t.test('handles missing pattern', async t => {
|
|
447
|
+
const res = await path(getState(':path()'))
|
|
448
|
+
t.strictSame(
|
|
449
|
+
[...res.partial.nodes].map(n => n.name),
|
|
450
|
+
[],
|
|
451
|
+
'should match nothing for missing pattern',
|
|
452
|
+
)
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
await t.test('matches case-sensitive patterns', async t => {
|
|
456
|
+
const res = await path(getState(':path("Packages/*")'))
|
|
457
|
+
t.strictSame(
|
|
458
|
+
[...res.partial.nodes].map(n => n.name),
|
|
459
|
+
[],
|
|
460
|
+
'should be case-sensitive and not match',
|
|
461
|
+
)
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
await t.test('excludes .vlt store paths', async t => {
|
|
465
|
+
// This test verifies that even if a .vlt path pattern is used,
|
|
466
|
+
// only workspace and file nodes are matched (not registry nodes)
|
|
467
|
+
const res = await path(getState(':path("**/.vlt/**")'))
|
|
468
|
+
t.strictSame(
|
|
469
|
+
[...res.partial.nodes].map(n => n.name),
|
|
470
|
+
[],
|
|
471
|
+
'should not match any nodes for .vlt store patterns since only workspace/file nodes are considered',
|
|
472
|
+
)
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
await t.test('error handling in loose mode', async t => {
|
|
476
|
+
const state = getState(':path("[")')
|
|
477
|
+
state.loose = true
|
|
478
|
+
const res = await path(state)
|
|
479
|
+
t.strictSame(
|
|
480
|
+
[...res.partial.nodes].map(n => n.name),
|
|
481
|
+
[],
|
|
482
|
+
'should handle invalid glob patterns gracefully in loose mode',
|
|
483
|
+
)
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
await t.test('error handling in strict mode', async t => {
|
|
487
|
+
const state = getState(':path("[")')
|
|
488
|
+
await t.rejects(
|
|
489
|
+
path(state),
|
|
490
|
+
/Invalid glob pattern in :path selector/,
|
|
491
|
+
'should throw error for invalid glob patterns in strict mode',
|
|
492
|
+
)
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
await t.test('requires quoted strings', async t => {
|
|
496
|
+
// This test assumes unquoted patterns would be rejected by the parser
|
|
497
|
+
// The path implementation should only receive quoted strings
|
|
498
|
+
const res = await path(getState(':path("unquoted")'))
|
|
499
|
+
t.strictSame(
|
|
500
|
+
[...res.partial.nodes].map(n => n.name),
|
|
501
|
+
[],
|
|
502
|
+
'should handle quoted patterns correctly',
|
|
503
|
+
)
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
// Additional test cases for improved coverage
|
|
507
|
+
await t.test('handles missing pathContainer.nodes', async t => {
|
|
508
|
+
const state = createCustomState({ withEmptyNodes: true })
|
|
509
|
+
const res = await path(state)
|
|
510
|
+
t.strictSame(
|
|
511
|
+
[...res.partial.nodes].map(n => n.name),
|
|
512
|
+
[],
|
|
513
|
+
'should return empty result when pathContainer.nodes is missing',
|
|
514
|
+
)
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
await t.test('handles empty selector.nodes', async t => {
|
|
518
|
+
const state = createCustomState({ withEmptySelector: true })
|
|
519
|
+
const res = await path(state)
|
|
520
|
+
t.strictSame(
|
|
521
|
+
[...res.partial.nodes].map(n => n.name),
|
|
522
|
+
[],
|
|
523
|
+
'should return empty result when selector.nodes is empty',
|
|
524
|
+
)
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
await t.test(
|
|
528
|
+
'handles non-string selector in strict mode',
|
|
529
|
+
async t => {
|
|
530
|
+
const state = createCustomState({ withNonStringSelector: true })
|
|
531
|
+
await t.rejects(
|
|
532
|
+
path(state),
|
|
533
|
+
/Failed to parse path pattern in :path selector/,
|
|
534
|
+
'should throw error for non-string selector in strict mode',
|
|
535
|
+
)
|
|
536
|
+
},
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
await t.test(
|
|
540
|
+
'handles non-string selector in loose mode',
|
|
541
|
+
async t => {
|
|
542
|
+
const state = createCustomState({ withNonStringSelector: true })
|
|
543
|
+
state.loose = true
|
|
544
|
+
const res = await path(state)
|
|
545
|
+
t.strictSame(
|
|
546
|
+
[...res.partial.nodes].map(n => n.name),
|
|
547
|
+
[],
|
|
548
|
+
'should return empty result for non-string selector in loose mode',
|
|
549
|
+
)
|
|
550
|
+
},
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
await t.test('handles broken selectors in strict mode', async t => {
|
|
554
|
+
const state = createCustomState({ withBrokenSelector: true })
|
|
555
|
+
await t.rejects(
|
|
556
|
+
path(state),
|
|
557
|
+
/Not a query selector node with children/,
|
|
558
|
+
'should throw error for broken selector in strict mode',
|
|
559
|
+
)
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
await t.test(
|
|
563
|
+
'handles main importer node with root path pattern',
|
|
564
|
+
async t => {
|
|
565
|
+
// Create a graph with a main importer
|
|
566
|
+
const graph = getPathBasedGraph()
|
|
567
|
+
// Find the main importer node
|
|
568
|
+
const mainImporterNode = Array.from(graph.nodes.values()).find(
|
|
569
|
+
node => node.name === 'path-based-project',
|
|
570
|
+
)
|
|
571
|
+
if (mainImporterNode) {
|
|
572
|
+
mainImporterNode.mainImporter = true
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const res = await path(getState(':path(".")', graph))
|
|
576
|
+
t.strictSame(
|
|
577
|
+
[...res.partial.nodes].map(n => n.name),
|
|
578
|
+
['path-based-project'],
|
|
579
|
+
'should preserve main importer node with root path pattern',
|
|
580
|
+
)
|
|
581
|
+
},
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
await t.test('handles minimatch errors in loose mode', async t => {
|
|
585
|
+
// Create a pattern that will cause minimatch to throw an error
|
|
586
|
+
const state = getState(':path("{[}")')
|
|
587
|
+
state.loose = true
|
|
588
|
+
const res = await path(state)
|
|
589
|
+
t.strictSame(
|
|
590
|
+
[...res.partial.nodes].map(n => n.name),
|
|
591
|
+
[],
|
|
592
|
+
'should handle minimatch errors gracefully in loose mode',
|
|
593
|
+
)
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
await t.test('handles minimatch errors in strict mode', async t => {
|
|
597
|
+
// Create a pattern that will cause minimatch to throw an error
|
|
598
|
+
const state = getState(':path("{[}")')
|
|
599
|
+
await t.rejects(
|
|
600
|
+
path(state),
|
|
601
|
+
/Invalid glob pattern in :path selector/,
|
|
602
|
+
'should throw error for invalid glob patterns in strict mode',
|
|
603
|
+
)
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
await t.test(
|
|
607
|
+
'handles path-based vs non-path-based nodes',
|
|
608
|
+
async t => {
|
|
609
|
+
// We need to test that the path.ts implementation correctly checks node types
|
|
610
|
+
const simpleGraph = getSimpleGraph()
|
|
611
|
+
const state = getState(':path("*")', simpleGraph)
|
|
612
|
+
|
|
613
|
+
// Add some fake registry nodes to the partial nodes
|
|
614
|
+
// We do this directly on the partial.nodes set without modifying the original graph
|
|
615
|
+
const originalNodes = [...state.partial.nodes]
|
|
616
|
+
|
|
617
|
+
// Create a registry node by copying an existing node but changing its ID
|
|
618
|
+
const _exampleNode = originalNodes[0]
|
|
619
|
+
const _registryNodeId = joinDepIDTuple([
|
|
620
|
+
'registry',
|
|
621
|
+
'',
|
|
622
|
+
'fake-registry@1.0.0',
|
|
623
|
+
])
|
|
624
|
+
|
|
625
|
+
// Find if there are any registry nodes already in the graph
|
|
626
|
+
const hasRegistryNode = originalNodes.some(node => {
|
|
627
|
+
const [type] = splitDepID(node.id)
|
|
628
|
+
return type === 'registry'
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
// Ensure we have some registry nodes for the test
|
|
632
|
+
if (!hasRegistryNode) {
|
|
633
|
+
t.pass(
|
|
634
|
+
'No registry nodes found in original graph, skipping registry node type test',
|
|
635
|
+
)
|
|
636
|
+
} else {
|
|
637
|
+
// Continue with the test since we have registry nodes
|
|
638
|
+
const res = await path(state)
|
|
639
|
+
|
|
640
|
+
// Verify that no registry nodes remain
|
|
641
|
+
const remainingNodes = [...res.partial.nodes]
|
|
642
|
+
const hasRemainingRegistryNodes = remainingNodes.some(
|
|
643
|
+
node => {
|
|
644
|
+
const [type] = splitDepID(node.id)
|
|
645
|
+
return type === 'registry'
|
|
646
|
+
},
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
t.notOk(
|
|
650
|
+
hasRemainingRegistryNodes,
|
|
651
|
+
'should filter out all registry nodes',
|
|
652
|
+
)
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
await t.test('handles path matching logic', async t => {
|
|
658
|
+
// We'll test that the path filtering logic works correctly
|
|
659
|
+
const graph = getPathBasedGraph()
|
|
660
|
+
const state = getState(':path("x")', graph)
|
|
661
|
+
|
|
662
|
+
// Run the path selector that should only match nodes with location "x"
|
|
663
|
+
const res = await path(state)
|
|
664
|
+
|
|
665
|
+
// Check that only nodes with location "x" remain
|
|
666
|
+
const remainingNodeNames = [...res.partial.nodes].map(n => n.name)
|
|
667
|
+
t.ok(
|
|
668
|
+
remainingNodeNames.includes('x'),
|
|
669
|
+
'node with matching path should remain',
|
|
670
|
+
)
|
|
671
|
+
t.notOk(
|
|
672
|
+
remainingNodeNames.includes('a'),
|
|
673
|
+
'node with non-matching path should be removed',
|
|
674
|
+
)
|
|
675
|
+
t.notOk(
|
|
676
|
+
remainingNodeNames.includes('b'),
|
|
677
|
+
'node with non-matching path should be removed',
|
|
678
|
+
)
|
|
679
|
+
})
|
|
680
|
+
})
|