/
extract.ts
137 lines (123 loc) · 4.23 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
import { XmlDocument, XmlElement, XmlNode } from 'xmldoc';
import { getGlobalConfig } from '../../config/global';
import * as datasourceNuget from '../../datasource/nuget';
import { logger } from '../../logger';
import { getSiblingFileName, localPathExists } from '../../util/fs';
import { hasKey } from '../../util/object';
import type { ExtractConfig, PackageDependency, PackageFile } from '../types';
import { extractMsbuildGlobalManifest } from './extract/global-manifest';
import type { DotnetToolsManifest } 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')) {
return extractMsbuildGlobalManifest(content, packageFile);
}
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;
}