-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathdev-engines.js
145 lines (122 loc) · 3.99 KB
/
dev-engines.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
const satisfies = require('semver/functions/satisfies')
const validRange = require('semver/ranges/valid')
const recognizedOnFail = [
'ignore',
'warn',
'error',
'download',
]
const recognizedProperties = [
'name',
'version',
'onFail',
]
const recognizedEngines = [
'packageManager',
'runtime',
'cpu',
'libc',
'os',
]
/** checks a devEngine dependency */
function checkDependency (wanted, current, opts) {
const { engine } = opts
if ((typeof wanted !== 'object' || wanted === null) || Array.isArray(wanted)) {
throw new Error(`Invalid non-object value for "${engine}"`)
}
const properties = Object.keys(wanted)
for (const prop of properties) {
if (!recognizedProperties.includes(prop)) {
throw new Error(`Invalid property "${prop}" for "${engine}"`)
}
}
if (!properties.includes('name')) {
throw new Error(`Missing "name" property for "${engine}"`)
}
if (typeof wanted.name !== 'string') {
throw new Error(`Invalid non-string value for "name" within "${engine}"`)
}
if (typeof current.name !== 'string' || current.name === '') {
throw new Error(`Unable to determine "name" for "${engine}"`)
}
if (properties.includes('onFail')) {
if (typeof wanted.onFail !== 'string') {
throw new Error(`Invalid non-string value for "onFail" within "${engine}"`)
}
if (!recognizedOnFail.includes(wanted.onFail)) {
throw new Error(`Invalid onFail value "${wanted.onFail}" for "${engine}"`)
}
}
if (wanted.name !== current.name) {
return new Error(
`Invalid name "${wanted.name}" does not match "${current.name}" for "${engine}"`
)
}
if (properties.includes('version')) {
if (typeof wanted.version !== 'string') {
throw new Error(`Invalid non-string value for "version" within "${engine}"`)
}
if (typeof current.version !== 'string' || current.version === '') {
throw new Error(`Unable to determine "version" for "${engine}" "${wanted.name}"`)
}
if (validRange(wanted.version)) {
if (!satisfies(current.version, wanted.version, opts.semver)) {
return new Error(
// eslint-disable-next-line max-len
`Invalid semver version "${wanted.version}" does not match "${current.version}" for "${engine}"`
)
}
} else if (wanted.version !== current.version) {
return new Error(
`Invalid version "${wanted.version}" does not match "${current.version}" for "${engine}"`
)
}
}
}
/** checks devEngines package property and returns array of warnings / errors */
function checkDevEngines (wanted, current = {}, opts = {}) {
if ((typeof wanted !== 'object' || wanted === null) || Array.isArray(wanted)) {
throw new Error(`Invalid non-object value for devEngines`)
}
const errors = []
for (const engine of Object.keys(wanted)) {
if (!recognizedEngines.includes(engine)) {
throw new Error(`Invalid property "${engine}"`)
}
const dependencyAsAuthored = wanted[engine]
const dependencies = [dependencyAsAuthored].flat()
const currentEngine = current[engine] || {}
// this accounts for empty array eg { runtime: [] } and ignores it
if (dependencies.length === 0) {
continue
}
const depErrors = []
for (const dep of dependencies) {
const result = checkDependency(dep, currentEngine, { ...opts, engine })
if (result) {
depErrors.push(result)
}
}
const invalid = depErrors.length === dependencies.length
if (invalid) {
const lastDependency = dependencies[dependencies.length - 1]
let onFail = lastDependency.onFail || 'error'
if (onFail === 'download') {
onFail = 'error'
}
const err = Object.assign(new Error(`Invalid engine "${engine}"`), {
errors: depErrors,
engine,
isWarn: onFail === 'warn',
isError: onFail === 'error',
current: currentEngine,
required: dependencyAsAuthored,
})
errors.push(err)
}
}
return errors
}
module.exports = {
checkDevEngines,
}