/
extract.ts
169 lines (150 loc) · 4.97 KB
/
extract.ts
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import { XmlDocument, XmlElement, XmlNode } from 'xmldoc';
import { getGlobalConfig } from '../../config/global';
import * as datasourceNuget from '../../datasource/nuget';
import { logger } from '../../logger';
import { SkipReason } from '../../types';
import { getSiblingFileName, localPathExists } from '../../util/fs';
import { hasKey } from '../../util/object';
import type { ExtractConfig, PackageDependency, PackageFile } from '../types';
import type { DotnetToolsManifest, MsbuildGlobalManifest } from './types';
import { getConfiguredRegistries } from './util';
/**
* https://docs.microsoft.com/en-us/nuget/concepts/package-versioning
* This article mentions that Nuget 3.x and later tries to restore the lowest possible version
* regarding to given version range.
* 1.3.4 equals [1.3.4,)
* Due to guarantee that an update of package version will result in its usage by the next restore + build operation,
* only following constrained versions make sense
* 1.3.4, [1.3.4], [1.3.4, ], [1.3.4, )
* The update of the right boundary does not make sense regarding to the lowest version restore rule,
* so we don't include it in the extracting regexp
*/
const checkVersion =
/^\s*(?:[[])?(?:(?<currentValue>[^"(,[\]]+)\s*(?:,\s*[)\]]|])?)\s*$/;
const elemNames = new Set([
'PackageReference',
'PackageVersion',
'DotNetCliToolReference',
'GlobalPackageReference',
]);
function isXmlElem(node: XmlNode): boolean {
return hasKey('name', node);
}
function extractDepsFromXml(xmlNode: XmlDocument): PackageDependency[] {
const results: PackageDependency[] = [];
const todo: XmlElement[] = [xmlNode];
while (todo.length) {
const child = todo.pop();
const { name, attr } = child;
if (elemNames.has(name)) {
const depName = attr?.Include || attr?.Update;
const version =
attr?.Version ||
child.valueWithPath('Version') ||
attr?.VersionOverride ||
child.valueWithPath('VersionOverride');
const currentValue = checkVersion
?.exec(version)
?.groups?.currentValue?.trim();
if (depName && currentValue) {
results.push({
datasource: datasourceNuget.id,
depType: 'nuget',
depName,
currentValue,
});
}
} else {
todo.push(...(child.children.filter(isXmlElem) as XmlElement[]));
}
}
return results;
}
export async function extractPackageFile(
content: string,
packageFile: string,
config: ExtractConfig
): Promise<PackageFile | null> {
logger.trace({ packageFile }, 'nuget.extractPackageFile()');
const { localDir } = getGlobalConfig();
const registries = await getConfiguredRegistries(packageFile, localDir);
const registryUrls = registries
? registries.map((registry) => registry.url)
: undefined;
if (packageFile.endsWith('.config/dotnet-tools.json')) {
const deps: PackageDependency[] = [];
let manifest: DotnetToolsManifest;
try {
manifest = JSON.parse(content);
} catch (err) {
logger.debug({ fileName: packageFile }, 'Invalid JSON');
return null;
}
if (manifest.version !== 1) {
logger.debug({ contents: manifest }, 'Unsupported dotnet tools version');
return null;
}
for (const depName of Object.keys(manifest.tools)) {
const tool = manifest.tools[depName];
const currentValue = tool.version;
const dep: PackageDependency = {
depType: 'nuget',
depName,
currentValue,
datasource: datasourceNuget.id,
};
if (registryUrls) {
dep.registryUrls = registryUrls;
}
deps.push(dep);
}
return { deps };
}
if (packageFile.endsWith('global.json')) {
const deps: PackageDependency[] = [];
let manifest: MsbuildGlobalManifest;
try {
manifest = JSON.parse(content);
} catch (err) {
logger.debug({ fileName: packageFile }, 'Invalid JSON');
return null;
}
if (manifest['msbuild-sdks'] === undefined) {
logger.debug(
{ fileName: packageFile },
'This global.json is not a Nuget file'
);
return null;
}
for (const depName of Object.keys(manifest['msbuild-sdks'])) {
const sdk = manifest['msbuild-sdks'][depName];
const currentValue = sdk[0];
const dep: PackageDependency = {
depType: 'nuget',
depName,
currentValue,
datasource: null,
skipReason: SkipReason.UnsupportedDatasource,
};
deps.push(dep);
}
return { deps };
}
let deps: PackageDependency[] = [];
try {
const parsedXml = new XmlDocument(content);
deps = extractDepsFromXml(parsedXml).map((dep) => ({
...dep,
...(registryUrls && { registryUrls }),
}));
} catch (err) {
logger.debug({ err }, `Failed to parse ${packageFile}`);
}
const res: PackageFile = { deps };
const lockFileName = getSiblingFileName(packageFile, 'packages.lock.json');
// istanbul ignore if
if (await localPathExists(lockFileName)) {
res.lockFiles = [lockFileName];
}
return res;
}