Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/extensionManagement/common/extensionsScannerService.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { coalesce } from '../../../base/common/arrays.js';
7
import { ThrottledDelayer } from '../../../base/common/async.js';
8
import * as objects from '../../../base/common/objects.js';
9
import { VSBuffer } from '../../../base/common/buffer.js';
10
import { getErrorMessage } from '../../../base/common/errors.js';
11
import { getNodeType, parse, ParseError } from '../../../base/common/json.js';
12
import { getParseErrorMessage } from '../../../base/common/jsonErrorMessages.js';
13
import { Disposable } from '../../../base/common/lifecycle.js';
14
import { FileAccess, Schemas } from '../../../base/common/network.js';
15
import * as path from '../../../base/common/path.js';
16
import * as platform from '../../../base/common/platform.js';
17
import { basename, isEqual, joinPath } from '../../../base/common/resources.js';
18
import * as semver from '../../../base/common/semver/semver.js';
19
import Severity from '../../../base/common/severity.js';
20
import { URI } from '../../../base/common/uri.js';
21
import { localize } from '../../../nls.js';
22
import { IEnvironmentService } from '../../environment/common/environment.js';
23
import { IProductVersion, Metadata } from './extensionManagement.js';
24
import { areSameExtensions, computeTargetPlatform, getExtensionId, getGalleryExtensionId } from './extensionManagementUtil.js';
25
import { ExtensionType, ExtensionIdentifier, IExtensionManifest, TargetPlatform, IExtensionIdentifier, IRelaxedExtensionManifest, UNDEFINED_PUBLISHER, IExtensionDescription, BUILTIN_MANIFEST_CACHE_FILE, USER_MANIFEST_CACHE_FILE, ExtensionIdentifierMap, parseEnabledApiProposalNames } from '../../extensions/common/extensions.js';
26
import { validateExtensionManifest } from '../../extensions/common/extensionValidator.js';
27
import { FileOperationResult, IFileService, toFileOperationResult } from '../../files/common/files.js';
28
import { createDecorator, IInstantiationService } from '../../instantiation/common/instantiation.js';
29
import { ILogService } from '../../log/common/log.js';
30
import { IProductService } from '../../product/common/productService.js';
31
import { Emitter, Event } from '../../../base/common/event.js';
32
import { revive } from '../../../base/common/marshalling.js';
33
import { ExtensionsProfileScanningError, ExtensionsProfileScanningErrorCode, IExtensionsProfileScannerService, IProfileExtensionsScanOptions, IScannedProfileExtension } from './extensionsProfileScannerService.js';
34
import { IUserDataProfile, IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';
35
import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';
36
import { localizeManifest } from './extensionNls.js';
37
38
export type ManifestMetadata = Partial<{
39
targetPlatform: TargetPlatform;
40
installedTimestamp: number;
41
size: number;
42
}>;
43
44
export type IScannedExtensionManifest = IRelaxedExtensionManifest & { __metadata?: ManifestMetadata };
45
46
interface IRelaxedScannedExtension {
47
type: ExtensionType;
48
isBuiltin: boolean;
49
identifier: IExtensionIdentifier;
50
manifest: IRelaxedExtensionManifest;
51
location: URI;
52
targetPlatform: TargetPlatform;
53
publisherDisplayName?: string;
54
metadata: Metadata | undefined;
55
isValid: boolean;
56
validations: readonly [Severity, string][];
57
preRelease: boolean;
58
}
59
60
export type IScannedExtension = Readonly<IRelaxedScannedExtension> & { manifest: IExtensionManifest };
61
62
export interface Translations {
63
[id: string]: string;
64
}
65
66
export namespace Translations {
67
export function equals(a: Translations, b: Translations): boolean {
68
if (a === b) {
69
return true;
70
}
71
const aKeys = Object.keys(a);
72
const bKeys: Set<string> = new Set<string>();
73
for (const key of Object.keys(b)) {
74
bKeys.add(key);
75
}
76
if (aKeys.length !== bKeys.size) {
77
return false;
78
}
79
80
for (const key of aKeys) {
81
if (a[key] !== b[key]) {
82
return false;
83
}
84
bKeys.delete(key);
85
}
86
return bKeys.size === 0;
87
}
88
}
89
90
interface MessageBag {
91
[key: string]: string | { message: string; comment: string[] };
92
}
93
94
interface TranslationBundle {
95
contents: {
96
package: MessageBag;
97
};
98
}
99
100
interface LocalizedMessages {
101
values: MessageBag | undefined;
102
default: URI | null;
103
}
104
105
interface IBuiltInExtensionControl {
106
[name: string]: 'marketplace' | 'disabled' | string;
107
}
108
109
export type SystemExtensionsScanOptions = {
110
readonly checkControlFile?: boolean;
111
readonly language?: string;
112
};
113
114
export type UserExtensionsScanOptions = {
115
readonly profileLocation: URI;
116
readonly includeInvalid?: boolean;
117
readonly language?: string;
118
readonly useCache?: boolean;
119
readonly productVersion?: IProductVersion;
120
};
121
122
export type ScanOptions = {
123
readonly includeInvalid?: boolean;
124
readonly language?: string;
125
};
126
127
export const IExtensionsScannerService = createDecorator<IExtensionsScannerService>('IExtensionsScannerService');
128
export interface IExtensionsScannerService {
129
readonly _serviceBrand: undefined;
130
131
readonly systemExtensionsLocation: URI;
132
readonly userExtensionsLocation: URI;
133
readonly onDidChangeCache: Event<ExtensionType>;
134
135
scanAllExtensions(systemScanOptions: SystemExtensionsScanOptions, userScanOptions: UserExtensionsScanOptions): Promise<IScannedExtension[]>;
136
scanSystemExtensions(scanOptions: SystemExtensionsScanOptions): Promise<IScannedExtension[]>;
137
scanUserExtensions(scanOptions: UserExtensionsScanOptions): Promise<IScannedExtension[]>;
138
scanAllUserExtensions(): Promise<IScannedExtension[]>;
139
140
scanExtensionsUnderDevelopment(existingExtensions: IScannedExtension[], scanOptions: ScanOptions): Promise<IScannedExtension[]>;
141
scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise<IScannedExtension | null>;
142
scanMultipleExtensions(extensionLocations: URI[], extensionType: ExtensionType, scanOptions: ScanOptions): Promise<IScannedExtension[]>;
143
scanOneOrMultipleExtensions(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise<IScannedExtension[]>;
144
145
updateManifestMetadata(extensionLocation: URI, metadata: ManifestMetadata): Promise<void>;
146
initializeDefaultProfileExtensions(): Promise<void>;
147
}
148
149
export abstract class AbstractExtensionsScannerService extends Disposable implements IExtensionsScannerService {
150
151
readonly _serviceBrand: undefined;
152
153
protected abstract getTranslations(language: string): Promise<Translations>;
154
155
private readonly _onDidChangeCache = this._register(new Emitter<ExtensionType>());
156
readonly onDidChangeCache = this._onDidChangeCache.event;
157
158
private readonly systemExtensionsCachedScanner: CachedExtensionsScanner;
159
private readonly userExtensionsCachedScanner: CachedExtensionsScanner;
160
private readonly extensionsScanner: ExtensionsScanner;
161
162
constructor(
163
readonly systemExtensionsLocation: URI,
164
readonly userExtensionsLocation: URI,
165
private readonly extensionsControlLocation: URI,
166
currentProfile: IUserDataProfile,
167
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
168
@IExtensionsProfileScannerService protected readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
169
@IFileService protected readonly fileService: IFileService,
170
@ILogService protected readonly logService: ILogService,
171
@IEnvironmentService private readonly environmentService: IEnvironmentService,
172
@IProductService private readonly productService: IProductService,
173
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
174
@IInstantiationService private readonly instantiationService: IInstantiationService,
175
) {
176
super();
177
178
this.systemExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, currentProfile));
179
this.userExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, currentProfile));
180
this.extensionsScanner = this._register(this.instantiationService.createInstance(ExtensionsScanner));
181
182
this._register(this.systemExtensionsCachedScanner.onDidChangeCache(() => this._onDidChangeCache.fire(ExtensionType.System)));
183
this._register(this.userExtensionsCachedScanner.onDidChangeCache(() => this._onDidChangeCache.fire(ExtensionType.User)));
184
}
185
186
private _targetPlatformPromise: Promise<TargetPlatform> | undefined;
187
private getTargetPlatform(): Promise<TargetPlatform> {
188
if (!this._targetPlatformPromise) {
189
this._targetPlatformPromise = computeTargetPlatform(this.fileService, this.logService);
190
}
191
return this._targetPlatformPromise;
192
}
193
194
async scanAllExtensions(systemScanOptions: SystemExtensionsScanOptions, userScanOptions: UserExtensionsScanOptions): Promise<IScannedExtension[]> {
195
const [system, user] = await Promise.all([
196
this.scanSystemExtensions(systemScanOptions),
197
this.scanUserExtensions(userScanOptions),
198
]);
199
return this.dedupExtensions(system, user, [], await this.getTargetPlatform(), true);
200
}
201
202
async scanSystemExtensions(scanOptions: SystemExtensionsScanOptions): Promise<IScannedExtension[]> {
203
const promises: Promise<IRelaxedScannedExtension[]>[] = [];
204
promises.push(this.scanDefaultSystemExtensions(scanOptions.language));
205
promises.push(this.scanDevSystemExtensions(scanOptions.language, !!scanOptions.checkControlFile));
206
const [defaultSystemExtensions, devSystemExtensions] = await Promise.all(promises);
207
return this.applyScanOptions([...defaultSystemExtensions, ...devSystemExtensions], ExtensionType.System, { pickLatest: false });
208
}
209
210
async scanUserExtensions(scanOptions: UserExtensionsScanOptions): Promise<IScannedExtension[]> {
211
this.logService.trace('Started scanning user extensions', scanOptions.profileLocation);
212
const profileScanOptions: IProfileExtensionsScanOptions | undefined = this.uriIdentityService.extUri.isEqual(scanOptions.profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource) ? { bailOutWhenFileNotFound: true } : undefined;
213
const extensionsScannerInput = await this.createExtensionScannerInput(scanOptions.profileLocation, true, ExtensionType.User, scanOptions.language, true, profileScanOptions, scanOptions.productVersion ?? this.getProductVersion());
214
const extensionsScanner = scanOptions.useCache && !extensionsScannerInput.devMode ? this.userExtensionsCachedScanner : this.extensionsScanner;
215
let extensions: IRelaxedScannedExtension[];
216
try {
217
extensions = await extensionsScanner.scanExtensions(extensionsScannerInput);
218
} catch (error) {
219
if (error instanceof ExtensionsProfileScanningError && error.code === ExtensionsProfileScanningErrorCode.ERROR_PROFILE_NOT_FOUND) {
220
await this.doInitializeDefaultProfileExtensions();
221
extensions = await extensionsScanner.scanExtensions(extensionsScannerInput);
222
} else {
223
throw error;
224
}
225
}
226
extensions = await this.applyScanOptions(extensions, ExtensionType.User, { includeInvalid: scanOptions.includeInvalid, pickLatest: true });
227
this.logService.trace('Scanned user extensions:', extensions.length);
228
return extensions;
229
}
230
231
async scanAllUserExtensions(scanOptions: { includeAllVersions?: boolean; includeInvalid: boolean } = { includeInvalid: true, includeAllVersions: true }): Promise<IScannedExtension[]> {
232
const extensionsScannerInput = await this.createExtensionScannerInput(this.userExtensionsLocation, false, ExtensionType.User, undefined, true, undefined, this.getProductVersion());
233
const extensions = await this.extensionsScanner.scanExtensions(extensionsScannerInput);
234
return this.applyScanOptions(extensions, ExtensionType.User, { includeAllVersions: scanOptions.includeAllVersions, includeInvalid: scanOptions.includeInvalid });
235
}
236
237
async scanExtensionsUnderDevelopment(existingExtensions: IScannedExtension[], scanOptions: ScanOptions): Promise<IScannedExtension[]> {
238
if (this.environmentService.isExtensionDevelopment && this.environmentService.extensionDevelopmentLocationURI) {
239
const extensions = (await Promise.all(this.environmentService.extensionDevelopmentLocationURI.filter(extLoc => extLoc.scheme === Schemas.file)
240
.map(async extensionDevelopmentLocationURI => {
241
const input = await this.createExtensionScannerInput(extensionDevelopmentLocationURI, false, ExtensionType.User, scanOptions.language, false /* do not validate */, undefined, this.getProductVersion());
242
const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(input);
243
return extensions.map(extension => {
244
// Override the extension type from the existing extensions
245
extension.type = existingExtensions.find(e => areSameExtensions(e.identifier, extension.identifier))?.type ?? extension.type;
246
// Validate the extension
247
return this.extensionsScanner.validate(extension, input);
248
});
249
})))
250
.flat();
251
return this.applyScanOptions(extensions, 'development', { includeInvalid: scanOptions.includeInvalid, pickLatest: true });
252
}
253
return [];
254
}
255
256
async scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise<IScannedExtension | null> {
257
const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, scanOptions.language, true, undefined, this.getProductVersion());
258
const extension = await this.extensionsScanner.scanExtension(extensionsScannerInput);
259
if (!extension) {
260
return null;
261
}
262
if (!scanOptions.includeInvalid && !extension.isValid) {
263
return null;
264
}
265
return extension;
266
}
267
268
async scanOneOrMultipleExtensions(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise<IScannedExtension[]> {
269
const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, scanOptions.language, true, undefined, this.getProductVersion());
270
const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(extensionsScannerInput);
271
return this.applyScanOptions(extensions, extensionType, { includeInvalid: scanOptions.includeInvalid, pickLatest: true });
272
}
273
274
async scanMultipleExtensions(extensionLocations: URI[], extensionType: ExtensionType, scanOptions: ScanOptions): Promise<IScannedExtension[]> {
275
const extensions: IRelaxedScannedExtension[] = [];
276
await Promise.all(extensionLocations.map(async extensionLocation => {
277
const scannedExtensions = await this.scanOneOrMultipleExtensions(extensionLocation, extensionType, scanOptions);
278
extensions.push(...scannedExtensions);
279
}));
280
return this.applyScanOptions(extensions, extensionType, { includeInvalid: scanOptions.includeInvalid, pickLatest: true });
281
}
282
283
async updateManifestMetadata(extensionLocation: URI, metaData: ManifestMetadata): Promise<void> {
284
const manifestLocation = joinPath(extensionLocation, 'package.json');
285
const content = (await this.fileService.readFile(manifestLocation)).value.toString();
286
const manifest: IScannedExtensionManifest = JSON.parse(content);
287
manifest.__metadata = { ...manifest.__metadata, ...metaData };
288
289
await this.fileService.writeFile(joinPath(extensionLocation, 'package.json'), VSBuffer.fromString(JSON.stringify(manifest, null, '\t')));
290
}
291
292
async initializeDefaultProfileExtensions(): Promise<void> {
293
try {
294
await this.extensionsProfileScannerService.scanProfileExtensions(this.userDataProfilesService.defaultProfile.extensionsResource, { bailOutWhenFileNotFound: true });
295
} catch (error) {
296
if (error instanceof ExtensionsProfileScanningError && error.code === ExtensionsProfileScanningErrorCode.ERROR_PROFILE_NOT_FOUND) {
297
await this.doInitializeDefaultProfileExtensions();
298
} else {
299
throw error;
300
}
301
}
302
}
303
304
private initializeDefaultProfileExtensionsPromise: Promise<void> | undefined = undefined;
305
private async doInitializeDefaultProfileExtensions(): Promise<void> {
306
if (!this.initializeDefaultProfileExtensionsPromise) {
307
this.initializeDefaultProfileExtensionsPromise = (async () => {
308
try {
309
this.logService.info('Started initializing default profile extensions in extensions installation folder.', this.userExtensionsLocation.toString());
310
const userExtensions = await this.scanAllUserExtensions({ includeInvalid: true });
311
if (userExtensions.length) {
312
await this.extensionsProfileScannerService.addExtensionsToProfile(userExtensions.map(e => [e, e.metadata]), this.userDataProfilesService.defaultProfile.extensionsResource);
313
} else {
314
try {
315
await this.fileService.createFile(this.userDataProfilesService.defaultProfile.extensionsResource, VSBuffer.fromString(JSON.stringify([])));
316
} catch (error) {
317
if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) {
318
this.logService.warn('Failed to create default profile extensions manifest in extensions installation folder.', this.userExtensionsLocation.toString(), getErrorMessage(error));
319
}
320
}
321
}
322
this.logService.info('Completed initializing default profile extensions in extensions installation folder.', this.userExtensionsLocation.toString());
323
} catch (error) {
324
this.logService.error(error);
325
} finally {
326
this.initializeDefaultProfileExtensionsPromise = undefined;
327
}
328
})();
329
}
330
return this.initializeDefaultProfileExtensionsPromise;
331
}
332
333
private async applyScanOptions(extensions: IRelaxedScannedExtension[], type: ExtensionType | 'development', scanOptions: { includeAllVersions?: boolean; includeInvalid?: boolean; pickLatest?: boolean } = {}): Promise<IRelaxedScannedExtension[]> {
334
if (!scanOptions.includeAllVersions) {
335
extensions = this.dedupExtensions(type === ExtensionType.System ? extensions : undefined, type === ExtensionType.User ? extensions : undefined, type === 'development' ? extensions : undefined, await this.getTargetPlatform(), !!scanOptions.pickLatest);
336
}
337
if (!scanOptions.includeInvalid) {
338
extensions = extensions.filter(extension => extension.isValid);
339
}
340
return extensions.sort((a, b) => {
341
const aLastSegment = path.basename(a.location.fsPath);
342
const bLastSegment = path.basename(b.location.fsPath);
343
if (aLastSegment < bLastSegment) {
344
return -1;
345
}
346
if (aLastSegment > bLastSegment) {
347
return 1;
348
}
349
return 0;
350
});
351
}
352
353
private dedupExtensions(system: IScannedExtension[] | undefined, user: IScannedExtension[] | undefined, development: IScannedExtension[] | undefined, targetPlatform: TargetPlatform, pickLatest: boolean): IScannedExtension[] {
354
const pick = (existing: IScannedExtension, extension: IScannedExtension, isDevelopment: boolean): boolean => {
355
if (!isDevelopment) {
356
if (existing.metadata?.isApplicationScoped && !extension.metadata?.isApplicationScoped) {
357
return false;
358
}
359
if (!existing.metadata?.isApplicationScoped && extension.metadata?.isApplicationScoped) {
360
return true;
361
}
362
}
363
if (existing.isValid && !extension.isValid) {
364
return false;
365
}
366
if (existing.isValid === extension.isValid) {
367
if (pickLatest && semver.gt(existing.manifest.version, extension.manifest.version)) {
368
this.logService.debug(`Skipping extension ${extension.location.path} with lower version ${extension.manifest.version} in favour of ${existing.location.path} with version ${existing.manifest.version}`);
369
return false;
370
}
371
if (semver.eq(existing.manifest.version, extension.manifest.version)) {
372
if (existing.type === ExtensionType.System) {
373
this.logService.debug(`Skipping extension ${extension.location.path} in favour of system extension ${existing.location.path} with same version`);
374
return false;
375
}
376
if (existing.targetPlatform === targetPlatform) {
377
this.logService.debug(`Skipping extension ${extension.location.path} from different target platform ${extension.targetPlatform}`);
378
return false;
379
}
380
}
381
}
382
if (isDevelopment) {
383
this.logService.warn(`Overwriting user extension ${existing.location.path} with ${extension.location.path}.`);
384
} else {
385
this.logService.debug(`Overwriting user extension ${existing.location.path} with ${extension.location.path}.`);
386
}
387
return true;
388
};
389
const result = new ExtensionIdentifierMap<IScannedExtension>();
390
system?.forEach((extension) => {
391
const existing = result.get(extension.identifier.id);
392
if (!existing || pick(existing, extension, false)) {
393
result.set(extension.identifier.id, extension);
394
}
395
});
396
user?.forEach((extension) => {
397
const existing = result.get(extension.identifier.id);
398
if (!existing && system && extension.type === ExtensionType.System) {
399
this.logService.debug(`Skipping obsolete system extension ${extension.location.path}.`);
400
return;
401
}
402
if (!existing || pick(existing, extension, false)) {
403
result.set(extension.identifier.id, extension);
404
}
405
});
406
development?.forEach(extension => {
407
const existing = result.get(extension.identifier.id);
408
if (!existing || pick(existing, extension, true)) {
409
result.set(extension.identifier.id, extension);
410
}
411
result.set(extension.identifier.id, extension);
412
});
413
return [...result.values()];
414
}
415
416
private async scanDefaultSystemExtensions(language: string | undefined): Promise<IRelaxedScannedExtension[]> {
417
this.logService.trace('Started scanning system extensions');
418
const extensionsScannerInput = await this.createExtensionScannerInput(this.systemExtensionsLocation, false, ExtensionType.System, language, true, undefined, this.getProductVersion());
419
const extensionsScanner = extensionsScannerInput.devMode ? this.extensionsScanner : this.systemExtensionsCachedScanner;
420
const result = await extensionsScanner.scanExtensions(extensionsScannerInput);
421
this.logService.trace('Scanned system extensions:', result.length);
422
return result;
423
}
424
425
private async scanDevSystemExtensions(language: string | undefined, checkControlFile: boolean): Promise<IRelaxedScannedExtension[]> {
426
const devSystemExtensionsList = this.environmentService.isBuilt ? [] : this.productService.builtInExtensions;
427
if (!devSystemExtensionsList?.length) {
428
return [];
429
}
430
431
this.logService.trace('Started scanning dev system extensions');
432
const builtinExtensionControl = checkControlFile ? await this.getBuiltInExtensionControl() : {};
433
const devSystemExtensionsLocations: URI[] = [];
434
const devSystemExtensionsLocation = URI.file(path.normalize(path.join(FileAccess.asFileUri('').fsPath, '..', '.build', 'builtInExtensions')));
435
for (const extension of devSystemExtensionsList) {
436
const controlState = builtinExtensionControl[extension.name] || 'marketplace';
437
switch (controlState) {
438
case 'disabled':
439
break;
440
case 'marketplace':
441
devSystemExtensionsLocations.push(joinPath(devSystemExtensionsLocation, extension.name));
442
break;
443
default:
444
devSystemExtensionsLocations.push(URI.file(controlState));
445
break;
446
}
447
}
448
const result = await Promise.all(devSystemExtensionsLocations.map(async location => this.extensionsScanner.scanExtension((await this.createExtensionScannerInput(location, false, ExtensionType.System, language, true, undefined, this.getProductVersion())))));
449
this.logService.trace('Scanned dev system extensions:', result.length);
450
return coalesce(result);
451
}
452
453
private async getBuiltInExtensionControl(): Promise<IBuiltInExtensionControl> {
454
try {
455
const content = await this.fileService.readFile(this.extensionsControlLocation);
456
return JSON.parse(content.value.toString());
457
} catch (error) {
458
return {};
459
}
460
}
461
462
private async createExtensionScannerInput(location: URI, profile: boolean, type: ExtensionType, language: string | undefined, validate: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined, productVersion: IProductVersion): Promise<ExtensionScannerInput> {
463
const translations = await this.getTranslations(language ?? platform.language);
464
const mtime = await this.getMtime(location);
465
const applicationExtensionsLocation = profile && !this.uriIdentityService.extUri.isEqual(location, this.userDataProfilesService.defaultProfile.extensionsResource) ? this.userDataProfilesService.defaultProfile.extensionsResource : undefined;
466
const applicationExtensionsLocationMtime = applicationExtensionsLocation ? await this.getMtime(applicationExtensionsLocation) : undefined;
467
return new ExtensionScannerInput(
468
location,
469
mtime,
470
applicationExtensionsLocation,
471
applicationExtensionsLocationMtime,
472
profile,
473
profileScanOptions,
474
type,
475
validate,
476
productVersion.version,
477
productVersion.date,
478
this.productService.commit,
479
!this.environmentService.isBuilt,
480
language,
481
translations,
482
);
483
}
484
485
private async getMtime(location: URI): Promise<number | undefined> {
486
try {
487
const stat = await this.fileService.stat(location);
488
if (typeof stat.mtime === 'number') {
489
return stat.mtime;
490
}
491
} catch (err) {
492
// That's ok...
493
}
494
return undefined;
495
}
496
497
private getProductVersion(): IProductVersion {
498
return {
499
version: this.productService.version,
500
date: this.productService.date,
501
};
502
}
503
504
}
505
506
export class ExtensionScannerInput {
507
508
constructor(
509
public readonly location: URI,
510
public readonly mtime: number | undefined,
511
public readonly applicationExtensionslocation: URI | undefined,
512
public readonly applicationExtensionslocationMtime: number | undefined,
513
public readonly profile: boolean,
514
public readonly profileScanOptions: IProfileExtensionsScanOptions | undefined,
515
public readonly type: ExtensionType,
516
public readonly validate: boolean,
517
public readonly productVersion: string,
518
public readonly productDate: string | undefined,
519
public readonly productCommit: string | undefined,
520
public readonly devMode: boolean,
521
public readonly language: string | undefined,
522
public readonly translations: Translations
523
) {
524
// Keep empty!! (JSON.parse)
525
}
526
527
public static createNlsConfiguration(input: ExtensionScannerInput): NlsConfiguration {
528
return {
529
language: input.language,
530
pseudo: input.language === 'pseudo',
531
devMode: input.devMode,
532
translations: input.translations
533
};
534
}
535
536
public static equals(a: ExtensionScannerInput, b: ExtensionScannerInput): boolean {
537
return (
538
isEqual(a.location, b.location)
539
&& a.mtime === b.mtime
540
&& isEqual(a.applicationExtensionslocation, b.applicationExtensionslocation)
541
&& a.applicationExtensionslocationMtime === b.applicationExtensionslocationMtime
542
&& a.profile === b.profile
543
&& objects.equals(a.profileScanOptions, b.profileScanOptions)
544
&& a.type === b.type
545
&& a.validate === b.validate
546
&& a.productVersion === b.productVersion
547
&& a.productDate === b.productDate
548
&& a.productCommit === b.productCommit
549
&& a.devMode === b.devMode
550
&& a.language === b.language
551
&& Translations.equals(a.translations, b.translations)
552
);
553
}
554
}
555
556
type NlsConfiguration = {
557
language: string | undefined;
558
pseudo: boolean;
559
devMode: boolean;
560
translations: Translations;
561
};
562
563
class ExtensionsScanner extends Disposable {
564
565
private readonly extensionsEnabledWithApiProposalVersion: string[];
566
567
constructor(
568
@IExtensionsProfileScannerService protected readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
569
@IUriIdentityService protected readonly uriIdentityService: IUriIdentityService,
570
@IFileService protected readonly fileService: IFileService,
571
@IProductService productService: IProductService,
572
@IEnvironmentService private readonly environmentService: IEnvironmentService,
573
@ILogService protected readonly logService: ILogService
574
) {
575
super();
576
this.extensionsEnabledWithApiProposalVersion = productService.extensionsEnabledWithApiProposalVersion?.map(id => id.toLowerCase()) ?? [];
577
}
578
579
async scanExtensions(input: ExtensionScannerInput): Promise<IRelaxedScannedExtension[]> {
580
return input.profile
581
? this.scanExtensionsFromProfile(input)
582
: this.scanExtensionsFromLocation(input);
583
}
584
585
private async scanExtensionsFromLocation(input: ExtensionScannerInput): Promise<IRelaxedScannedExtension[]> {
586
const stat = await this.fileService.resolve(input.location);
587
if (!stat.children?.length) {
588
return [];
589
}
590
const extensions = await Promise.all<IRelaxedScannedExtension | null>(
591
stat.children.map(async c => {
592
if (!c.isDirectory) {
593
return null;
594
}
595
// Do not consider user extension folder starting with `.`
596
if (input.type === ExtensionType.User && basename(c.resource).indexOf('.') === 0) {
597
return null;
598
}
599
const extensionScannerInput = new ExtensionScannerInput(c.resource, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations);
600
return this.scanExtension(extensionScannerInput);
601
}));
602
return coalesce(extensions)
603
// Sort: Make sure extensions are in the same order always. Helps cache invalidation even if the order changes.
604
.sort((a, b) => a.location.path < b.location.path ? -1 : 1);
605
}
606
607
private async scanExtensionsFromProfile(input: ExtensionScannerInput): Promise<IRelaxedScannedExtension[]> {
608
let profileExtensions = await this.scanExtensionsFromProfileResource(input.location, () => true, input);
609
if (input.applicationExtensionslocation && !this.uriIdentityService.extUri.isEqual(input.location, input.applicationExtensionslocation)) {
610
profileExtensions = profileExtensions.filter(e => !e.metadata?.isApplicationScoped);
611
const applicationExtensions = await this.scanExtensionsFromProfileResource(input.applicationExtensionslocation, (e) => !!e.metadata?.isBuiltin || !!e.metadata?.isApplicationScoped, input);
612
profileExtensions.push(...applicationExtensions);
613
}
614
return profileExtensions;
615
}
616
617
private async scanExtensionsFromProfileResource(profileResource: URI, filter: (extensionInfo: IScannedProfileExtension) => boolean, input: ExtensionScannerInput): Promise<IRelaxedScannedExtension[]> {
618
const scannedProfileExtensions = await this.extensionsProfileScannerService.scanProfileExtensions(profileResource, input.profileScanOptions);
619
if (!scannedProfileExtensions.length) {
620
return [];
621
}
622
const extensions = await Promise.all<IRelaxedScannedExtension | null>(
623
scannedProfileExtensions.map(async extensionInfo => {
624
if (filter(extensionInfo)) {
625
const extensionScannerInput = new ExtensionScannerInput(extensionInfo.location, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations);
626
return this.scanExtension(extensionScannerInput, extensionInfo);
627
}
628
return null;
629
}));
630
return coalesce(extensions);
631
}
632
633
async scanOneOrMultipleExtensions(input: ExtensionScannerInput): Promise<IRelaxedScannedExtension[]> {
634
try {
635
if (await this.fileService.exists(joinPath(input.location, 'package.json'))) {
636
const extension = await this.scanExtension(input);
637
return extension ? [extension] : [];
638
} else {
639
return await this.scanExtensions(input);
640
}
641
} catch (error) {
642
this.logService.error(`Error scanning extensions at ${input.location.path}:`, getErrorMessage(error));
643
return [];
644
}
645
}
646
647
async scanExtension(input: ExtensionScannerInput): Promise<IRelaxedScannedExtension | null>;
648
async scanExtension(input: ExtensionScannerInput, scannedProfileExtension: IScannedProfileExtension): Promise<IRelaxedScannedExtension>;
649
async scanExtension(input: ExtensionScannerInput, scannedProfileExtension?: IScannedProfileExtension): Promise<IRelaxedScannedExtension | null> {
650
const validations: [Severity, string][] = [];
651
let isValid = true;
652
let manifest: IScannedExtensionManifest;
653
try {
654
manifest = await this.scanExtensionManifest(input.location);
655
} catch (e) {
656
if (scannedProfileExtension) {
657
validations.push([Severity.Error, getErrorMessage(e)]);
658
isValid = false;
659
const [publisher, name] = scannedProfileExtension.identifier.id.split('.');
660
manifest = {
661
name,
662
publisher,
663
version: scannedProfileExtension.version,
664
engines: { vscode: '' }
665
};
666
} else {
667
if (input.type !== ExtensionType.System) {
668
this.logService.error(e);
669
}
670
return null;
671
}
672
}
673
674
// allow publisher to be undefined to make the initial extension authoring experience smoother
675
if (!manifest.publisher) {
676
manifest.publisher = UNDEFINED_PUBLISHER;
677
}
678
679
let metadata: Metadata | undefined;
680
if (scannedProfileExtension) {
681
metadata = {
682
...scannedProfileExtension.metadata,
683
size: manifest.__metadata?.size,
684
};
685
} else if (manifest.__metadata) {
686
metadata = {
687
installedTimestamp: manifest.__metadata.installedTimestamp,
688
size: manifest.__metadata.size,
689
targetPlatform: manifest.__metadata.targetPlatform,
690
};
691
}
692
693
delete manifest.__metadata;
694
const id = getGalleryExtensionId(manifest.publisher, manifest.name);
695
const identifier = metadata?.id ? { id, uuid: metadata.id } : { id };
696
const type = metadata?.isSystem ? ExtensionType.System : input.type;
697
const isBuiltin = type === ExtensionType.System || !!metadata?.isBuiltin;
698
try {
699
manifest = await this.translateManifest(input.location, manifest, ExtensionScannerInput.createNlsConfiguration(input));
700
} catch (error) {
701
this.logService.warn('Failed to translate manifest', getErrorMessage(error));
702
}
703
let extension: IRelaxedScannedExtension = {
704
type,
705
identifier,
706
manifest,
707
location: input.location,
708
isBuiltin,
709
targetPlatform: metadata?.targetPlatform ?? TargetPlatform.UNDEFINED,
710
publisherDisplayName: metadata?.publisherDisplayName,
711
metadata,
712
isValid,
713
validations,
714
preRelease: !!metadata?.preRelease,
715
};
716
if (input.validate) {
717
extension = this.validate(extension, input);
718
}
719
if (manifest.enabledApiProposals && (!this.environmentService.isBuilt || this.extensionsEnabledWithApiProposalVersion.includes(id.toLowerCase()))) {
720
manifest.originalEnabledApiProposals = manifest.enabledApiProposals;
721
manifest.enabledApiProposals = parseEnabledApiProposalNames([...manifest.enabledApiProposals]);
722
}
723
return extension;
724
}
725
726
validate(extension: IRelaxedScannedExtension, input: ExtensionScannerInput): IRelaxedScannedExtension {
727
let isValid = extension.isValid;
728
const validateApiVersion = this.environmentService.isBuilt && this.extensionsEnabledWithApiProposalVersion.includes(extension.identifier.id.toLowerCase());
729
const validations = validateExtensionManifest(input.productVersion, input.productDate, input.location, extension.manifest, extension.isBuiltin, validateApiVersion);
730
for (const [severity, message] of validations) {
731
if (severity === Severity.Error) {
732
isValid = false;
733
this.logService.error(this.formatMessage(input.location, message));
734
}
735
}
736
extension.isValid = isValid;
737
extension.validations = [...extension.validations, ...validations];
738
return extension;
739
}
740
741
private async scanExtensionManifest(extensionLocation: URI): Promise<IScannedExtensionManifest> {
742
const manifestLocation = joinPath(extensionLocation, 'package.json');
743
let content;
744
try {
745
content = (await this.fileService.readFile(manifestLocation)).value.toString();
746
} catch (error) {
747
if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) {
748
this.logService.error(this.formatMessage(extensionLocation, localize('fileReadFail', "Cannot read file {0}: {1}.", manifestLocation.path, error.message)));
749
}
750
throw error;
751
}
752
let manifest: IScannedExtensionManifest;
753
try {
754
manifest = JSON.parse(content);
755
} catch (err) {
756
// invalid JSON, let's get good errors
757
const errors: ParseError[] = [];
758
parse(content, errors);
759
for (const e of errors) {
760
this.logService.error(this.formatMessage(extensionLocation, localize('jsonParseFail', "Failed to parse {0}: [{1}, {2}] {3}.", manifestLocation.path, e.offset, e.length, getParseErrorMessage(e.error))));
761
}
762
throw err;
763
}
764
if (getNodeType(manifest) !== 'object') {
765
const errorMessage = this.formatMessage(extensionLocation, localize('jsonParseInvalidType', "Invalid manifest file {0}: Not a JSON object.", manifestLocation.path));
766
this.logService.error(errorMessage);
767
throw new Error(errorMessage);
768
}
769
return manifest;
770
}
771
772
private async translateManifest(extensionLocation: URI, extensionManifest: IExtensionManifest, nlsConfiguration: NlsConfiguration): Promise<IExtensionManifest> {
773
const localizedMessages = await this.getLocalizedMessages(extensionLocation, extensionManifest, nlsConfiguration);
774
if (localizedMessages) {
775
try {
776
const errors: ParseError[] = [];
777
// resolveOriginalMessageBundle returns null if localizedMessages.default === undefined;
778
const defaults = await this.resolveOriginalMessageBundle(localizedMessages.default, errors);
779
if (errors.length > 0) {
780
errors.forEach((error) => {
781
this.logService.error(this.formatMessage(extensionLocation, localize('jsonsParseReportErrors', "Failed to parse {0}: {1}.", localizedMessages.default?.path, getParseErrorMessage(error.error))));
782
});
783
return extensionManifest;
784
} else if (getNodeType(localizedMessages) !== 'object') {
785
this.logService.error(this.formatMessage(extensionLocation, localize('jsonInvalidFormat', "Invalid format {0}: JSON object expected.", localizedMessages.default?.path)));
786
return extensionManifest;
787
}
788
const localized = localizedMessages.values || Object.create(null);
789
return localizeManifest(this.logService, extensionManifest, localized, defaults);
790
} catch (error) {
791
/*Ignore Error*/
792
}
793
}
794
return extensionManifest;
795
}
796
797
private async getLocalizedMessages(extensionLocation: URI, extensionManifest: IExtensionManifest, nlsConfiguration: NlsConfiguration): Promise<LocalizedMessages | undefined> {
798
const defaultPackageNLS = joinPath(extensionLocation, 'package.nls.json');
799
const reportErrors = (localized: URI | null, errors: ParseError[]): void => {
800
errors.forEach((error) => {
801
this.logService.error(this.formatMessage(extensionLocation, localize('jsonsParseReportErrors', "Failed to parse {0}: {1}.", localized?.path, getParseErrorMessage(error.error))));
802
});
803
};
804
const reportInvalidFormat = (localized: URI | null): void => {
805
this.logService.error(this.formatMessage(extensionLocation, localize('jsonInvalidFormat', "Invalid format {0}: JSON object expected.", localized?.path)));
806
};
807
808
const translationId = `${extensionManifest.publisher}.${extensionManifest.name}`;
809
const translationPath = nlsConfiguration.translations[translationId];
810
811
if (translationPath) {
812
try {
813
const translationResource = URI.file(translationPath);
814
const content = (await this.fileService.readFile(translationResource)).value.toString();
815
const errors: ParseError[] = [];
816
const translationBundle: TranslationBundle = parse(content, errors);
817
if (errors.length > 0) {
818
reportErrors(translationResource, errors);
819
return { values: undefined, default: defaultPackageNLS };
820
} else if (getNodeType(translationBundle) !== 'object') {
821
reportInvalidFormat(translationResource);
822
return { values: undefined, default: defaultPackageNLS };
823
} else {
824
const values = translationBundle.contents ? translationBundle.contents.package : undefined;
825
return { values: values, default: defaultPackageNLS };
826
}
827
} catch (error) {
828
return { values: undefined, default: defaultPackageNLS };
829
}
830
} else {
831
const exists = await this.fileService.exists(defaultPackageNLS);
832
if (!exists) {
833
return undefined;
834
}
835
let messageBundle;
836
try {
837
messageBundle = await this.findMessageBundles(extensionLocation, nlsConfiguration);
838
} catch (error) {
839
return undefined;
840
}
841
if (!messageBundle.localized) {
842
return { values: undefined, default: messageBundle.original };
843
}
844
try {
845
const messageBundleContent = (await this.fileService.readFile(messageBundle.localized)).value.toString();
846
const errors: ParseError[] = [];
847
const messages: MessageBag = parse(messageBundleContent, errors);
848
if (errors.length > 0) {
849
reportErrors(messageBundle.localized, errors);
850
return { values: undefined, default: messageBundle.original };
851
} else if (getNodeType(messages) !== 'object') {
852
reportInvalidFormat(messageBundle.localized);
853
return { values: undefined, default: messageBundle.original };
854
}
855
return { values: messages, default: messageBundle.original };
856
} catch (error) {
857
return { values: undefined, default: messageBundle.original };
858
}
859
}
860
}
861
862
/**
863
* Parses original message bundle, returns null if the original message bundle is null.
864
*/
865
private async resolveOriginalMessageBundle(originalMessageBundle: URI | null, errors: ParseError[]): Promise<{ [key: string]: string } | undefined> {
866
if (originalMessageBundle) {
867
try {
868
const originalBundleContent = (await this.fileService.readFile(originalMessageBundle)).value.toString();
869
return parse(originalBundleContent, errors);
870
} catch (error) {
871
/* Ignore Error */
872
}
873
}
874
return;
875
}
876
877
/**
878
* Finds localized message bundle and the original (unlocalized) one.
879
* If the localized file is not present, returns null for the original and marks original as localized.
880
*/
881
private findMessageBundles(extensionLocation: URI, nlsConfiguration: NlsConfiguration): Promise<{ localized: URI; original: URI | null }> {
882
return new Promise<{ localized: URI; original: URI | null }>((c, e) => {
883
const loop = (locale: string): void => {
884
const toCheck = joinPath(extensionLocation, `package.nls.${locale}.json`);
885
this.fileService.exists(toCheck).then(exists => {
886
if (exists) {
887
c({ localized: toCheck, original: joinPath(extensionLocation, 'package.nls.json') });
888
}
889
const index = locale.lastIndexOf('-');
890
if (index === -1) {
891
c({ localized: joinPath(extensionLocation, 'package.nls.json'), original: null });
892
} else {
893
locale = locale.substring(0, index);
894
loop(locale);
895
}
896
});
897
};
898
if (nlsConfiguration.devMode || nlsConfiguration.pseudo || !nlsConfiguration.language) {
899
return c({ localized: joinPath(extensionLocation, 'package.nls.json'), original: null });
900
}
901
loop(nlsConfiguration.language);
902
});
903
}
904
905
private formatMessage(extensionLocation: URI, message: string): string {
906
return `[${extensionLocation.path}]: ${message}`;
907
}
908
909
}
910
911
interface IExtensionCacheData {
912
input: ExtensionScannerInput;
913
result: IRelaxedScannedExtension[];
914
}
915
916
class CachedExtensionsScanner extends ExtensionsScanner {
917
918
private input: ExtensionScannerInput | undefined;
919
private readonly cacheValidatorThrottler: ThrottledDelayer<void> = this._register(new ThrottledDelayer(3000));
920
921
private readonly _onDidChangeCache = this._register(new Emitter<void>());
922
readonly onDidChangeCache = this._onDidChangeCache.event;
923
924
constructor(
925
private readonly currentProfile: IUserDataProfile,
926
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
927
@IExtensionsProfileScannerService extensionsProfileScannerService: IExtensionsProfileScannerService,
928
@IUriIdentityService uriIdentityService: IUriIdentityService,
929
@IFileService fileService: IFileService,
930
@IProductService productService: IProductService,
931
@IEnvironmentService environmentService: IEnvironmentService,
932
@ILogService logService: ILogService
933
) {
934
super(extensionsProfileScannerService, uriIdentityService, fileService, productService, environmentService, logService);
935
}
936
937
override async scanExtensions(input: ExtensionScannerInput): Promise<IRelaxedScannedExtension[]> {
938
const cacheFile = this.getCacheFile(input);
939
const cacheContents = await this.readExtensionCache(cacheFile);
940
this.input = input;
941
if (cacheContents && cacheContents.input && ExtensionScannerInput.equals(cacheContents.input, this.input)) {
942
this.logService.debug('Using cached extensions scan result', input.type === ExtensionType.System ? 'system' : 'user', input.location.toString());
943
this.cacheValidatorThrottler.trigger(() => this.validateCache());
944
return cacheContents.result.map((extension) => {
945
// revive URI object
946
extension.location = URI.revive(extension.location);
947
return extension;
948
});
949
}
950
const result = await super.scanExtensions(input);
951
await this.writeExtensionCache(cacheFile, { input, result });
952
return result;
953
}
954
955
private async readExtensionCache(cacheFile: URI): Promise<IExtensionCacheData | null> {
956
try {
957
const cacheRawContents = await this.fileService.readFile(cacheFile);
958
const extensionCacheData: IExtensionCacheData = JSON.parse(cacheRawContents.value.toString());
959
return { result: extensionCacheData.result, input: revive(extensionCacheData.input) };
960
} catch (error) {
961
this.logService.debug('Error while reading the extension cache file:', cacheFile.path, getErrorMessage(error));
962
}
963
return null;
964
}
965
966
private async writeExtensionCache(cacheFile: URI, cacheContents: IExtensionCacheData): Promise<void> {
967
try {
968
await this.fileService.writeFile(cacheFile, VSBuffer.fromString(JSON.stringify(cacheContents)));
969
} catch (error) {
970
this.logService.debug('Error while writing the extension cache file:', cacheFile.path, getErrorMessage(error));
971
}
972
}
973
974
private async validateCache(): Promise<void> {
975
if (!this.input) {
976
// Input has been unset by the time we get here, so skip validation
977
return;
978
}
979
980
const cacheFile = this.getCacheFile(this.input);
981
const cacheContents = await this.readExtensionCache(cacheFile);
982
if (!cacheContents) {
983
// Cache has been deleted by someone else, which is perfectly fine...
984
return;
985
}
986
987
const actual = cacheContents.result;
988
const expected = JSON.parse(JSON.stringify(await super.scanExtensions(this.input)));
989
if (objects.equals(expected, actual)) {
990
// Cache is valid and running with it is perfectly fine...
991
return;
992
}
993
994
try {
995
this.logService.info('Invalidating Cache', actual, expected);
996
// Cache is invalid, delete it
997
await this.fileService.del(cacheFile);
998
this._onDidChangeCache.fire();
999
} catch (error) {
1000
this.logService.error(error);
1001
}
1002
}
1003
1004
private getCacheFile(input: ExtensionScannerInput): URI {
1005
const profile = this.getProfile(input);
1006
return this.uriIdentityService.extUri.joinPath(profile.cacheHome, input.type === ExtensionType.System ? BUILTIN_MANIFEST_CACHE_FILE : USER_MANIFEST_CACHE_FILE);
1007
}
1008
1009
private getProfile(input: ExtensionScannerInput): IUserDataProfile {
1010
if (input.type === ExtensionType.System) {
1011
return this.userDataProfilesService.defaultProfile;
1012
}
1013
if (!input.profile) {
1014
return this.userDataProfilesService.defaultProfile;
1015
}
1016
if (this.uriIdentityService.extUri.isEqual(input.location, this.currentProfile.extensionsResource)) {
1017
return this.currentProfile;
1018
}
1019
return this.userDataProfilesService.profiles.find(p => this.uriIdentityService.extUri.isEqual(input.location, p.extensionsResource)) ?? this.currentProfile;
1020
}
1021
1022
}
1023
1024
export function toExtensionDescription(extension: IScannedExtension, isUnderDevelopment: boolean): IExtensionDescription {
1025
const id = getExtensionId(extension.manifest.publisher, extension.manifest.name);
1026
return {
1027
id,
1028
identifier: new ExtensionIdentifier(id),
1029
isBuiltin: extension.type === ExtensionType.System,
1030
isUserBuiltin: extension.type === ExtensionType.User && extension.isBuiltin,
1031
isUnderDevelopment,
1032
extensionLocation: extension.location,
1033
uuid: extension.identifier.uuid,
1034
targetPlatform: extension.targetPlatform,
1035
publisherDisplayName: extension.publisherDisplayName,
1036
preRelease: extension.preRelease,
1037
...extension.manifest,
1038
};
1039
}
1040
1041
export class NativeExtensionsScannerService extends AbstractExtensionsScannerService implements IExtensionsScannerService {
1042
1043
private readonly translationsPromise: Promise<Translations>;
1044
1045
constructor(
1046
systemExtensionsLocation: URI,
1047
userExtensionsLocation: URI,
1048
userHome: URI,
1049
currentProfile: IUserDataProfile,
1050
userDataProfilesService: IUserDataProfilesService,
1051
extensionsProfileScannerService: IExtensionsProfileScannerService,
1052
fileService: IFileService,
1053
logService: ILogService,
1054
environmentService: IEnvironmentService,
1055
productService: IProductService,
1056
uriIdentityService: IUriIdentityService,
1057
instantiationService: IInstantiationService,
1058
) {
1059
super(
1060
systemExtensionsLocation,
1061
userExtensionsLocation,
1062
joinPath(userHome, '.vscode-oss-dev', 'extensions', 'control.json'),
1063
currentProfile,
1064
userDataProfilesService, extensionsProfileScannerService, fileService, logService, environmentService, productService, uriIdentityService, instantiationService);
1065
this.translationsPromise = (async () => {
1066
if (platform.translationsConfigFile) {
1067
try {
1068
const content = await this.fileService.readFile(URI.file(platform.translationsConfigFile));
1069
return JSON.parse(content.value.toString());
1070
} catch (err) { /* Ignore Error */ }
1071
}
1072
return Object.create(null);
1073
})();
1074
}
1075
1076
protected getTranslations(language: string): Promise<Translations> {
1077
return this.translationsPromise;
1078
}
1079
1080
}
1081
1082