-
Notifications
You must be signed in to change notification settings - Fork 0
/
circleCiConfigParser.ts
111 lines (100 loc) · 3.79 KB
/
circleCiConfigParser.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
import { ISpecificCIResolverResponse } from '../../../interfaces/ISpecificCIResolver';
import { injectable } from 'inversify';
import { ILoggerFactory } from '../../../../../utils/logger';
import { ILogger } from '../../../../../utils/logger/interfaces/ILogger';
import { INode, objectIterator } from '../../../../../utils/objectIterator/objectIterator';
import { isString } from 'ts-type-guards';
import { ITargetMatcher } from '../../../interfaces/ITargetMatcher';
import { INvmHandler } from '../../../interfaces/INvmHandler';
import { isStringOrNumber } from '../../../../../utils/types/typeGuards';
import { StringOrNumber } from '../../../../../utils/types/types';
const nodeVersionRegex = /node:([^-.]+)-?/i;
type Matrix = Record<string, string[]>;
export interface ICircleCiConfigParserOptions {
config: Record<string, unknown>;
}
const isVersionNode = (value: unknown, node: INode): value is string => {
const { parent, isLeaf, key, isNonRootNode } = node;
return (
isLeaf &&
key === `image` &&
isString(value) &&
isNonRootNode &&
parent.isNonRootNode &&
parent.parent.isNonRootNode &&
parent.parent.key === `docker`
);
};
const isEnvNode = (value: unknown, node: INode): value is StringOrNumber => {
const { parent, isLeaf } = node;
return isLeaf && parent.isNonRootNode && parent.key === `environment` && isStringOrNumber(value);
};
@injectable()
export class CircleCiConfigParser {
private readonly logger: ILogger;
constructor(
private readonly nvmHandler: INvmHandler,
private readonly targetMatcher: ITargetMatcher,
loggerFactory: ILoggerFactory
) {
this.logger = loggerFactory.getLogger(`CircleCi Config Parser`);
}
public async parse({ config }: ICircleCiConfigParserOptions): Promise<ISpecificCIResolverResponse> {
this.logger.debug(`Parsing Circle CI configuration`);
const versions = new Set<string>();
const matrix: Matrix = {};
const nvmCommands: string[] = [];
for (const node of objectIterator(config)) {
const { value, key } = node;
if (isVersionNode(value, node)) {
this.parseVersion(value, versions);
} else if (isEnvNode(value, node)) {
if (matrix[key]) {
matrix[key].push(String(value));
} else {
matrix[key] = [String(value)];
}
} else if (this.isNvmCommandNode(value, node)) {
this.logger.debug(`Found nvm command - ${value}`);
nvmCommands.push(value);
}
}
nvmCommands.forEach((command) => {
this.nodeVersionCommandParser(command, matrix, versions);
});
this.logger.debug(`Finished parsing CircleCi configuration, Found ${versions.size} version(s)`);
return {
nodeVersions: versions,
};
}
private isNvmCommandNode(value: unknown, node: INode): value is string {
const { isLeaf } = node;
return isLeaf && isString(value) && this.nvmHandler.isNvmCommand(value);
}
private nodeVersionCommandParser(command: string, matrix: Matrix, versions: Set<string>): void {
const foundVersions = this.nvmHandler.getNvmVersionsFromMatrix(command, matrix);
for (const version of foundVersions) {
versions.add(version);
}
}
private parseVersion(value: string, versions: Set<string>): void {
const version = nodeVersionRegex.exec(value)?.[1];
if (version) {
if (version === `latest` || version === `current`) {
versions.add(this.targetMatcher.getStableVersionPlaceholder());
} else if (version === `lts`) {
versions.add(this.targetMatcher.getLatestLtsVersionPlaceholder());
} else {
if (!isNaN(parseInt(version.charAt(0), 10))) {
versions.add(version);
} else {
versions.add(
this.targetMatcher.getLtsVersionPlaceholder({
codename: version,
})
);
}
}
}
}
}