forked from microsoft/rushstack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCommonVersionsConfiguration.ts
More file actions
358 lines (311 loc) · 12.5 KB
/
CommonVersionsConfiguration.ts
File metadata and controls
358 lines (311 loc) · 12.5 KB
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import crypto from 'node:crypto';
import * as path from 'node:path';
import {
JsonFile,
JsonSchema,
MapExtensions,
ProtectableMap,
FileSystem,
Sort
} from '@rushstack/node-core-library';
import type { OptionalToUndefined } from '../utilities/Utilities';
import { PackageNameParsers } from './PackageNameParsers';
import { JsonSchemaUrls } from '../logic/JsonSchemaUrls';
import type { RushConfiguration } from './RushConfiguration';
import { RushConstants } from '../logic/RushConstants';
import schemaJson from '../schemas/common-versions.schema.json';
/**
* Part of the ICommonVersionsJson structure.
*/
export declare interface ICommonVersionsJsonVersionMap {
/**
* The key is the name of a dependency. The value is a Semantic Versioning (SemVer)
* range specifier.
*/
[dependencyName: string]: string;
}
/**
* Part of the ICommonVersionsJson structure.
*/
export declare interface ICommonVersionsJsonVersionsMap {
/**
* The key is the name of a dependency. The value is a list of Semantic Versioning (SemVer)
* range specifiers.
*/
[dependencyName: string]: string[];
}
/**
* Describes the file structure for the "common/config/rush/common-versions.json" config file.
*/
interface ICommonVersionsJson {
$schema?: string;
preferredVersions?: ICommonVersionsJsonVersionMap;
implicitlyPreferredVersions?: boolean;
allowedAlternativeVersions?: ICommonVersionsJsonVersionsMap;
ensureConsistentVersions?: boolean;
}
/**
* Use this class to load and save the "common/config/rush/common-versions.json" config file.
* This config file stores dependency version information that affects all projects in the repo.
* @public
*/
export class CommonVersionsConfiguration {
private static _jsonSchema: JsonSchema = JsonSchema.fromLoadedObject(schemaJson);
private _preferredVersions: ProtectableMap<string, string>;
private _allowedAlternativeVersions: ProtectableMap<string, string[]>;
private _modified: boolean = false;
private _commonVersionsJsonHasEnsureConsistentVersionsProperty: boolean;
/**
* Get the absolute file path of the common-versions.json file.
*/
public readonly filePath: string;
/**
* When set to true, for all projects in the repo, all dependencies will be automatically added as preferredVersions,
* except in cases where different projects specify different version ranges for a given dependency. For older
* package managers, this tended to reduce duplication of indirect dependencies. However, it can sometimes cause
* trouble for indirect dependencies with incompatible peerDependencies ranges.
*
* If the value is `undefined`, then the default value is `true`.
*/
public readonly implicitlyPreferredVersions: boolean | undefined;
/**
* If true, then consistent version specifiers for dependencies will be enforced.
* I.e. "rush check" is run before some commands.
*/
public readonly ensureConsistentVersions: boolean;
/**
* A table that specifies a "preferred version" for a given NPM package. This feature is typically used
* to hold back an indirect dependency to a specific older version, or to reduce duplication of indirect dependencies.
*
* @remarks
* The "preferredVersions" value can be any SemVer range specifier (e.g. `~1.2.3`). Rush injects these values into
* the "dependencies" field of the top-level common/temp/package.json, which influences how the package manager
* will calculate versions. The specific effect depends on your package manager. Generally it will have no
* effect on an incompatible or already constrained SemVer range. If you are using PNPM, similar effects can be
* achieved using the pnpmfile.js hook. See the Rush documentation for more details.
*
* After modifying this field, it's recommended to run `rush update --full` so that the package manager
* will recalculate all version selections.
*/
public readonly preferredVersions: Map<string, string>;
/**
* A table that stores, for a given dependency, a list of SemVer ranges that will be accepted
* by "rush check" in addition to the normal version range.
*
* @remarks
* The "rush check" command can be used to enforce that every project in the repo
* must specify the same SemVer range for a given dependency. However, sometimes
* exceptions are needed. The allowedAlternativeVersions table allows you to list
* other SemVer ranges that will be accepted by "rush check" for a given dependency.
* Note that the normal version range (as inferred by looking at all projects in the repo)
* should NOT be included in this list.
*/
public readonly allowedAlternativeVersions: Map<string, ReadonlyArray<string>>;
private constructor(
commonVersionsJson: ICommonVersionsJson | undefined,
filePath: string,
rushConfiguration: RushConfiguration | undefined
) {
this._preferredVersions = new ProtectableMap<string, string>({
onSet: this._onSetPreferredVersions.bind(this)
});
this.preferredVersions = this._preferredVersions.protectedView;
if (commonVersionsJson && commonVersionsJson.implicitlyPreferredVersions !== undefined) {
this.implicitlyPreferredVersions = commonVersionsJson.implicitlyPreferredVersions;
} else {
this.implicitlyPreferredVersions = undefined;
}
this._allowedAlternativeVersions = new ProtectableMap<string, string[]>({
onSet: this._onSetAllowedAlternativeVersions.bind(this)
});
this.allowedAlternativeVersions = this._allowedAlternativeVersions.protectedView;
const subspacesFeatureEnabled: boolean | undefined = rushConfiguration?.subspacesFeatureEnabled;
const rushJsonEnsureConsistentVersions: boolean | undefined =
rushConfiguration?._ensureConsistentVersionsJsonValue;
const commonVersionsEnsureConsistentVersions: boolean | undefined =
commonVersionsJson?.ensureConsistentVersions;
if (subspacesFeatureEnabled && rushJsonEnsureConsistentVersions !== undefined) {
throw new Error(
`When using subspaces, the ensureConsistentVersions config is now defined in the ${RushConstants.commonVersionsFilename} file, ` +
`you must remove the old setting "ensureConsistentVersions" from ${RushConstants.rushJsonFilename}`
);
} else if (
!subspacesFeatureEnabled &&
rushJsonEnsureConsistentVersions !== undefined &&
commonVersionsEnsureConsistentVersions !== undefined
) {
throw new Error(
`When the ensureConsistentVersions config is defined in the ${RushConstants.rushJsonFilename} file, ` +
`it cannot also be defined in the ${RushConstants.commonVersionsFilename} file`
);
}
this.ensureConsistentVersions =
commonVersionsEnsureConsistentVersions ?? rushJsonEnsureConsistentVersions ?? false;
this._commonVersionsJsonHasEnsureConsistentVersionsProperty =
commonVersionsEnsureConsistentVersions !== undefined;
if (commonVersionsJson) {
try {
CommonVersionsConfiguration._deserializeTable(
this.preferredVersions,
commonVersionsJson.preferredVersions
);
CommonVersionsConfiguration._deserializeTable(
this.allowedAlternativeVersions,
commonVersionsJson.allowedAlternativeVersions
);
} catch (e) {
throw new Error(`Error loading "${path.basename(filePath)}": ${(e as Error).message}`);
}
}
this.filePath = filePath;
}
/**
* @deprecated Use {@link CommonVersionsConfiguration.loadFromFileAsync} method instead.
*/
public static loadFromFile(
jsonFilePath: string,
rushConfiguration?: RushConfiguration
): CommonVersionsConfiguration {
let commonVersionsJson: ICommonVersionsJson | undefined = undefined;
try {
commonVersionsJson = JsonFile.loadAndValidate(jsonFilePath, CommonVersionsConfiguration._jsonSchema);
} catch (error) {
if (!FileSystem.isNotExistError(error)) {
throw error;
}
}
return new CommonVersionsConfiguration(commonVersionsJson, jsonFilePath, rushConfiguration);
}
/**
* Loads the common-versions.json data from the specified file path.
* If the file has not been created yet, then an empty object is returned.
*/
public static async loadFromFileAsync(
jsonFilePath: string,
rushConfiguration?: RushConfiguration
): Promise<CommonVersionsConfiguration> {
let commonVersionsJson: ICommonVersionsJson | undefined = undefined;
try {
commonVersionsJson = await JsonFile.loadAndValidateAsync(
jsonFilePath,
CommonVersionsConfiguration._jsonSchema
);
} catch (error) {
if (!FileSystem.isNotExistError(error)) {
throw error;
}
}
return new CommonVersionsConfiguration(commonVersionsJson, jsonFilePath, rushConfiguration);
}
private static _deserializeTable<TValue>(
map: Map<string, TValue>,
object: { [key: string]: TValue } | undefined
): void {
if (object) {
for (const [key, value] of Object.entries(object)) {
map.set(key, value);
}
}
}
private static _serializeTable<TValue>(map: Map<string, TValue>): { [key: string]: TValue } {
const table: { [key: string]: TValue } = {};
const keys: string[] = [...map.keys()];
keys.sort();
for (const key of keys) {
table[key] = map.get(key)!;
}
return table;
}
/**
* Get a sha1 hash of the preferred versions.
*/
public getPreferredVersionsHash(): string {
// Sort so that the hash is stable
const orderedPreferredVersions: Map<string, string> = new Map<string, string>(
this._preferredVersions.protectedView
);
Sort.sortMapKeys(orderedPreferredVersions);
// JSON.stringify does not support maps, so we need to convert to an object first
const preferredVersionsObj: { [dependency: string]: string } =
MapExtensions.toObject(orderedPreferredVersions);
return crypto.createHash('sha1').update(JSON.stringify(preferredVersionsObj)).digest('hex');
}
/**
* @deprecated Use {@link CommonVersionsConfiguration.saveAsync} method instead.
*/
public save(): boolean {
if (this._modified) {
JsonFile.save(this._serialize(), this.filePath, {
updateExistingFile: true,
ignoreUndefinedValues: true
});
this._modified = false;
return true;
}
return false;
}
/**
* Writes the "common-versions.json" file to disk, using the filename that was passed to loadFromFile().
*/
public async saveAsync(): Promise<boolean> {
if (this._modified) {
await JsonFile.saveAsync(this._serialize(), this.filePath, {
updateExistingFile: true,
ignoreUndefinedValues: true
});
this._modified = false;
return true;
}
return false;
}
/**
* Returns preferredVersions.
*/
public getAllPreferredVersions(): Map<string, string> {
const allPreferredVersions: Map<string, string> = new Map<string, string>();
MapExtensions.mergeFromMap(allPreferredVersions, this.preferredVersions);
return allPreferredVersions;
}
private _onSetPreferredVersions(
source: ProtectableMap<string, string>,
key: string,
value: string
): string {
PackageNameParsers.permissive.validate(key);
this._modified = true;
return value;
}
private _onSetAllowedAlternativeVersions(
source: ProtectableMap<string, string[]>,
key: string,
value: string[]
): string[] {
PackageNameParsers.permissive.validate(key);
this._modified = true;
return value;
}
private _serialize(): ICommonVersionsJson {
let preferredVersions: ICommonVersionsJsonVersionMap | undefined;
if (this._preferredVersions.size) {
preferredVersions = CommonVersionsConfiguration._serializeTable(this.preferredVersions);
}
let allowedAlternativeVersions: ICommonVersionsJsonVersionsMap | undefined;
if (this._allowedAlternativeVersions.size) {
allowedAlternativeVersions = CommonVersionsConfiguration._serializeTable(
this.allowedAlternativeVersions
) as ICommonVersionsJsonVersionsMap;
}
const result: OptionalToUndefined<ICommonVersionsJson> = {
$schema: JsonSchemaUrls.commonVersions,
preferredVersions,
implicitlyPreferredVersions: this.implicitlyPreferredVersions,
allowedAlternativeVersions,
ensureConsistentVersions: this._commonVersionsJsonHasEnsureConsistentVersionsProperty
? this.ensureConsistentVersions
: undefined
};
return result;
}
}