-
Notifications
You must be signed in to change notification settings - Fork 604
/
CacheEntryId.ts
148 lines (124 loc) · 4.58 KB
/
CacheEntryId.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
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
const OPTIONS_ARGUMENT_NAME: string = 'options';
/**
* Options for generating the cache id for an operation.
* @beta
*/
export interface IGenerateCacheEntryIdOptions {
/**
* The name of the project
*/
projectName: string;
/**
* The name of the phase
*/
phaseName: string;
/**
* A hash of the input files
*/
projectStateHash: string;
}
/**
* Calculates the cache entry id string for an operation.
* @beta
*/
export type GetCacheEntryIdFunction = (options: IGenerateCacheEntryIdOptions) => string;
const HASH_TOKEN_NAME: string = 'hash';
const PROJECT_NAME_TOKEN_NAME: string = 'projectName';
const PHASE_NAME_TOKEN_NAME: string = 'phaseName';
// This regex matches substrings that look like [token]
const TOKEN_REGEX: RegExp = /\[[^\]]*\]/g;
export class CacheEntryId {
private constructor() {}
public static parsePattern(pattern?: string): GetCacheEntryIdFunction {
if (!pattern) {
return ({ projectStateHash }) => projectStateHash;
} else {
pattern = pattern.trim();
if (pattern.startsWith('/')) {
throw new Error('Cache entry name patterns may not start with a slash.');
}
const patternWithoutTokens: string = pattern.replace(TOKEN_REGEX, '');
if (patternWithoutTokens.match(/\]/)) {
throw new Error(`Unexpected "]" character in cache entry name pattern.`);
}
if (patternWithoutTokens.match(/\[/)) {
throw new Error('Unclosed token in cache entry name pattern.');
}
if (!patternWithoutTokens.match(/^[A-z0-9-_\/]*$/)) {
throw new Error(
'Cache entry name pattern contains an invalid character. ' +
'Only alphanumeric characters, slashes, underscores, and hyphens are allowed.'
);
}
let foundHashToken: boolean = false;
const templateString: string = pattern.trim().replace(TOKEN_REGEX, (token: string) => {
token = token.substring(1, token.length - 1);
let tokenName: string;
let tokenAttribute: string | undefined;
const tokenSplitIndex: number = token.indexOf(':');
if (tokenSplitIndex === -1) {
tokenName = token;
} else {
tokenName = token.substr(0, tokenSplitIndex);
tokenAttribute = token.substr(tokenSplitIndex + 1);
}
switch (tokenName) {
case HASH_TOKEN_NAME: {
if (tokenAttribute !== undefined) {
throw new Error(`An attribute isn\'t supported for the "${tokenName}" token.`);
}
foundHashToken = true;
return `\${${OPTIONS_ARGUMENT_NAME}.projectStateHash}`;
}
case PROJECT_NAME_TOKEN_NAME: {
switch (tokenAttribute) {
case undefined: {
return `\${${OPTIONS_ARGUMENT_NAME}.projectName}`;
}
case 'normalize': {
return `\${${OPTIONS_ARGUMENT_NAME}.projectName.replace(/\\+/g, '++').replace(/\\/\/g, '+')}`;
}
default: {
throw new Error(`Unexpected attribute "${tokenAttribute}" for the "${tokenName}" token.`);
}
}
}
case PHASE_NAME_TOKEN_NAME: {
switch (tokenAttribute) {
case undefined: {
throw new Error(
'Either the "normalize" or the "trimPrefix" attribute is required ' +
`for the "${tokenName}" token.`
);
}
case 'normalize': {
// Replace colons with underscores.
return `\${${OPTIONS_ARGUMENT_NAME}.phaseName.replace(/:/g, '_')}`;
}
case 'trimPrefix': {
// Trim the "_phase:" prefix from the phase name.
return `\${${OPTIONS_ARGUMENT_NAME}.phaseName.replace(/^_phase:/, '')}`;
}
default: {
throw new Error(`Unexpected attribute "${tokenAttribute}" for the "${tokenName}" token.`);
}
}
}
default: {
throw new Error(`Unexpected token name "${tokenName}".`);
}
}
});
if (!foundHashToken) {
throw new Error(`Cache entry name pattern is missing a [${HASH_TOKEN_NAME}] token.`);
}
// eslint-disable-next-line no-new-func
return new Function(
OPTIONS_ARGUMENT_NAME,
`"use strict"\nreturn \`${templateString}\`;`
) as GetCacheEntryIdFunction;
}
}
}