extensions: allow date-based engines constraint

Fixes #121749
This commit is contained in:
Connor Peet 2021-05-11 15:39:06 -07:00
parent 05d4a5c56d
commit 3ef8ea56b6
No known key found for this signature in database
GPG key ID: CF8FD2EA0DBC61BD
6 changed files with 88 additions and 40 deletions

View file

@ -433,7 +433,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
private async getCompatibleExtensionByEngine(arg1: IExtensionIdentifier | IGalleryExtension, version?: string): Promise<IGalleryExtension | null> { private async getCompatibleExtensionByEngine(arg1: IExtensionIdentifier | IGalleryExtension, version?: string): Promise<IGalleryExtension | null> {
const extension: IGalleryExtension | null = isIExtensionIdentifier(arg1) ? null : arg1; const extension: IGalleryExtension | null = isIExtensionIdentifier(arg1) ? null : arg1;
if (extension && extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.version)) { if (extension && extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.version, this.productService.date)) {
return extension; return extension;
} }
const { id, uuid } = extension ? extension.identifier : <IExtensionIdentifier>arg1; const { id, uuid } = extension ? extension.identifier : <IExtensionIdentifier>arg1;
@ -458,7 +458,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
const versionAsset = rawExtension.versions.filter(v => v.version === version)[0]; const versionAsset = rawExtension.versions.filter(v => v.version === version)[0];
if (versionAsset) { if (versionAsset) {
const extension = toExtension(rawExtension, versionAsset, 0, query); const extension = toExtension(rawExtension, versionAsset, 0, query);
if (extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.version)) { if (extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.version, this.productService.date)) {
return extension; return extension;
} }
} }
@ -711,7 +711,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
try { try {
engine = await this.getEngine(v); engine = await this.getEngine(v);
} catch (error) { /* Ignore error and skip version */ } } catch (error) { /* Ignore error and skip version */ }
if (engine && isEngineValid(engine, this.productService.version)) { if (engine && isEngineValid(engine, this.productService.version, this.productService.date)) {
result.push({ version: v!.version, date: v!.lastUpdated }); result.push({ version: v!.version, date: v!.lastUpdated });
} }
})); }));
@ -774,7 +774,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
if (!engine) { if (!engine) {
return null; return null;
} }
if (isEngineValid(engine, this.productService.version)) { if (isEngineValid(engine, this.productService.version, this.productService.date)) {
return version; return version;
} }
} }
@ -809,7 +809,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
const version = versions[0]; const version = versions[0];
const engine = await this.getEngine(version); const engine = await this.getEngine(version);
if (!isEngineValid(engine, this.productService.version)) { if (!isEngineValid(engine, this.productService.version, this.productService.date)) {
return this.getLastValidExtensionVersionRecursively(extension, versions.slice(1)); return this.getLastValidExtensionVersionRecursively(extension, versions.slice(1));
} }

View file

@ -170,7 +170,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
const manifest = await getManifest(zipPath); const manifest = await getManifest(zipPath);
const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) }; const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
let operation: InstallOperation = InstallOperation.Install; let operation: InstallOperation = InstallOperation.Install;
if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, product.version)) { if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, product.version, product.date)) {
throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", identifier.id, product.version)); throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", identifier.id, product.version));
} }

View file

@ -24,10 +24,12 @@ export interface INormalizedVersion {
minorMustEqual: boolean; minorMustEqual: boolean;
patchBase: number; patchBase: number;
patchMustEqual: boolean; patchMustEqual: boolean;
notBefore: number; /* milliseconds timestamp, or 0 */
isMinimum: boolean; isMinimum: boolean;
} }
const VERSION_REGEXP = /^(\^|>=)?((\d+)|x)\.((\d+)|x)\.((\d+)|x)(\-.*)?$/; const VERSION_REGEXP = /^(\^|>=)?((\d+)|x)\.((\d+)|x)\.((\d+)|x)(\-.*)?$/;
const NOT_BEFORE_REGEXP = /^-(?<year>\d{4})(?<month>\d{2})(?<day>\d{2})$/;
export function isValidVersionStr(version: string): boolean { export function isValidVersionStr(version: string): boolean {
version = version.trim(); version = version.trim();
@ -93,6 +95,14 @@ export function normalizeVersion(version: IParsedVersion | null): INormalizedVer
} }
} }
let notBefore = 0;
if (version.preRelease) {
const match = NOT_BEFORE_REGEXP.exec(version.preRelease);
if (match?.groups) {
notBefore = Date.UTC(Number(match.groups.year), Number(match.groups.month) - 1, Number(match.groups.day));
}
}
return { return {
majorBase: majorBase, majorBase: majorBase,
majorMustEqual: majorMustEqual, majorMustEqual: majorMustEqual,
@ -100,16 +110,24 @@ export function normalizeVersion(version: IParsedVersion | null): INormalizedVer
minorMustEqual: minorMustEqual, minorMustEqual: minorMustEqual,
patchBase: patchBase, patchBase: patchBase,
patchMustEqual: patchMustEqual, patchMustEqual: patchMustEqual,
isMinimum: version.hasGreaterEquals isMinimum: version.hasGreaterEquals,
notBefore,
}; };
} }
export function isValidVersion(_version: string | INormalizedVersion, _desiredVersion: string | INormalizedVersion): boolean { export function isValidVersion(_inputVersion: string | INormalizedVersion, _inputDate: ProductDate, _desiredVersion: string | INormalizedVersion): boolean {
let version: INormalizedVersion | null; let version: INormalizedVersion | null;
if (typeof _version === 'string') { if (typeof _inputVersion === 'string') {
version = normalizeVersion(parseVersion(_version)); version = normalizeVersion(parseVersion(_inputVersion));
} else { } else {
version = _version; version = _inputVersion;
}
let productTs: number | undefined;
if (_inputDate instanceof Date) {
productTs = _inputDate.getTime();
} else if (typeof _inputDate === 'string') {
productTs = new Date(_inputDate).getTime();
} }
let desiredVersion: INormalizedVersion | null; let desiredVersion: INormalizedVersion | null;
@ -130,6 +148,7 @@ export function isValidVersion(_version: string | INormalizedVersion, _desiredVe
let desiredMajorBase = desiredVersion.majorBase; let desiredMajorBase = desiredVersion.majorBase;
let desiredMinorBase = desiredVersion.minorBase; let desiredMinorBase = desiredVersion.minorBase;
let desiredPatchBase = desiredVersion.patchBase; let desiredPatchBase = desiredVersion.patchBase;
let desiredNotBefore = desiredVersion.notBefore;
let majorMustEqual = desiredVersion.majorMustEqual; let majorMustEqual = desiredVersion.majorMustEqual;
let minorMustEqual = desiredVersion.minorMustEqual; let minorMustEqual = desiredVersion.minorMustEqual;
@ -152,6 +171,10 @@ export function isValidVersion(_version: string | INormalizedVersion, _desiredVe
return false; return false;
} }
if (productTs && productTs < desiredNotBefore) {
return false;
}
return patchBase >= desiredPatchBase; return patchBase >= desiredPatchBase;
} }
@ -200,6 +223,11 @@ export function isValidVersion(_version: string | INormalizedVersion, _desiredVe
} }
// at this point, patchBase are equal // at this point, patchBase are equal
if (productTs && productTs < desiredNotBefore) {
return false;
}
return true; return true;
} }
@ -211,22 +239,24 @@ export interface IReducedExtensionDescription {
main?: string; main?: string;
} }
export function isValidExtensionVersion(version: string, extensionDesc: IReducedExtensionDescription, notices: string[]): boolean { type ProductDate = string | Date | undefined;
export function isValidExtensionVersion(version: string, date: ProductDate, extensionDesc: IReducedExtensionDescription, notices: string[]): boolean {
if (extensionDesc.isBuiltin || typeof extensionDesc.main === 'undefined') { if (extensionDesc.isBuiltin || typeof extensionDesc.main === 'undefined') {
// No version check for builtin or declarative extensions // No version check for builtin or declarative extensions
return true; return true;
} }
return isVersionValid(version, extensionDesc.engines.vscode, notices); return isVersionValid(version, date, extensionDesc.engines.vscode, notices);
} }
export function isEngineValid(engine: string, version: string): boolean { export function isEngineValid(engine: string, version: string, date: ProductDate): boolean {
// TODO@joao: discuss with alex '*' doesn't seem to be a valid engine version // TODO@joao: discuss with alex '*' doesn't seem to be a valid engine version
return engine === '*' || isVersionValid(version, engine); return engine === '*' || isVersionValid(version, date, engine);
} }
export function isVersionValid(currentVersion: string, requestedVersion: string, notices: string[] = []): boolean { function isVersionValid(currentVersion: string, date: ProductDate, requestedVersion: string, notices: string[] = []): boolean {
let desiredVersion = normalizeVersion(parseVersion(requestedVersion)); let desiredVersion = normalizeVersion(parseVersion(requestedVersion));
if (!desiredVersion) { if (!desiredVersion) {
@ -251,7 +281,7 @@ export function isVersionValid(currentVersion: string, requestedVersion: string,
} }
} }
if (!isValidVersion(currentVersion, desiredVersion)) { if (!isValidVersion(currentVersion, date, desiredVersion)) {
notices.push(nls.localize('versionMismatch', "Extension is not compatible with Code {0}. Extension requires: {1}.", currentVersion, requestedVersion)); notices.push(nls.localize('versionMismatch', "Extension is not compatible with Code {0}. Extension requires: {1}.", currentVersion, requestedVersion));
return false; return false;
} }

View file

@ -6,6 +6,7 @@ import * as assert from 'assert';
import { INormalizedVersion, IParsedVersion, IReducedExtensionDescription, isValidExtensionVersion, isValidVersion, isValidVersionStr, normalizeVersion, parseVersion } from 'vs/platform/extensions/common/extensionValidator'; import { INormalizedVersion, IParsedVersion, IReducedExtensionDescription, isValidExtensionVersion, isValidVersion, isValidVersionStr, normalizeVersion, parseVersion } from 'vs/platform/extensions/common/extensionValidator';
suite('Extension Version Validator', () => { suite('Extension Version Validator', () => {
const productVersion = '2021-05-11T21:54:30.577Z';
test('isValidVersionStr', () => { test('isValidVersionStr', () => {
assert.strictEqual(isValidVersionStr('0.10.0-dev'), true); assert.strictEqual(isValidVersionStr('0.10.0-dev'), true);
@ -53,13 +54,16 @@ suite('Extension Version Validator', () => {
}); });
test('normalizeVersion', () => { test('normalizeVersion', () => {
function assertNormalizeVersion(version: string, majorBase: number, majorMustEqual: boolean, minorBase: number, minorMustEqual: boolean, patchBase: number, patchMustEqual: boolean, isMinimum: boolean): void { function assertNormalizeVersion(version: string, majorBase: number, majorMustEqual: boolean, minorBase: number, minorMustEqual: boolean, patchBase: number, patchMustEqual: boolean, isMinimum: boolean, notBefore = 0): void {
const actual = normalizeVersion(parseVersion(version)); const actual = normalizeVersion(parseVersion(version));
const expected: INormalizedVersion = { majorBase, majorMustEqual, minorBase, minorMustEqual, patchBase, patchMustEqual, isMinimum }; const expected: INormalizedVersion = { majorBase, majorMustEqual, minorBase, minorMustEqual, patchBase, patchMustEqual, isMinimum, notBefore };
assert.deepStrictEqual(actual, expected, 'parseVersion for ' + version); assert.deepStrictEqual(actual, expected, 'parseVersion for ' + version);
} }
assertNormalizeVersion('0.10.0-dev', 0, true, 10, true, 0, true, false); assertNormalizeVersion('0.10.0-dev', 0, true, 10, true, 0, true, false, 0);
assertNormalizeVersion('0.10.0-222222222', 0, true, 10, true, 0, true, false, 0);
assertNormalizeVersion('0.10.0-20210511', 0, true, 10, true, 0, true, false, new Date('2021-05-11T00:00:00Z').getTime());
assertNormalizeVersion('0.10.0', 0, true, 10, true, 0, true, false); assertNormalizeVersion('0.10.0', 0, true, 10, true, 0, true, false);
assertNormalizeVersion('0.10.1', 0, true, 10, true, 1, true, false); assertNormalizeVersion('0.10.1', 0, true, 10, true, 1, true, false);
assertNormalizeVersion('0.10.100', 0, true, 10, true, 100, true, false); assertNormalizeVersion('0.10.100', 0, true, 10, true, 100, true, false);
@ -75,11 +79,12 @@ suite('Extension Version Validator', () => {
assertNormalizeVersion('>=0.0.1', 0, true, 0, true, 1, true, true); assertNormalizeVersion('>=0.0.1', 0, true, 0, true, 1, true, true);
assertNormalizeVersion('>=2.4.3', 2, true, 4, true, 3, true, true); assertNormalizeVersion('>=2.4.3', 2, true, 4, true, 3, true, true);
assertNormalizeVersion('>=2.4.3', 2, true, 4, true, 3, true, true);
}); });
test('isValidVersion', () => { test('isValidVersion', () => {
function testIsValidVersion(version: string, desiredVersion: string, expectedResult: boolean): void { function testIsValidVersion(version: string, desiredVersion: string, expectedResult: boolean): void {
let actual = isValidVersion(version, desiredVersion); let actual = isValidVersion(version, productVersion, desiredVersion);
assert.strictEqual(actual, expectedResult, 'extension - vscode: ' + version + ', desiredVersion: ' + desiredVersion + ' should be ' + expectedResult); assert.strictEqual(actual, expectedResult, 'extension - vscode: ' + version + ', desiredVersion: ' + desiredVersion + ' should be ' + expectedResult);
} }
@ -211,7 +216,7 @@ suite('Extension Version Validator', () => {
main: hasMain ? 'something' : undefined main: hasMain ? 'something' : undefined
}; };
let reasons: string[] = []; let reasons: string[] = [];
let actual = isValidExtensionVersion(version, desc, reasons); let actual = isValidExtensionVersion(version, productVersion, desc, reasons);
assert.strictEqual(actual, expectedResult, 'version: ' + version + ', desiredVersion: ' + desiredVersion + ', desc: ' + JSON.stringify(desc) + ', reasons: ' + JSON.stringify(reasons)); assert.strictEqual(actual, expectedResult, 'version: ' + version + ', desiredVersion: ' + desiredVersion + ', desc: ' + JSON.stringify(desc) + ', reasons: ' + JSON.stringify(reasons));
} }
@ -390,5 +395,12 @@ suite('Extension Version Validator', () => {
testIsValidVersion('2.0.0', '^1.100.0', false); testIsValidVersion('2.0.0', '^1.100.0', false);
testIsValidVersion('2.0.0', '^2.0.0', true); testIsValidVersion('2.0.0', '^2.0.0', true);
testIsValidVersion('2.0.0', '*', false); // fails due to lack of specificity testIsValidVersion('2.0.0', '*', false); // fails due to lack of specificity
// date tags
testIsValidVersion('1.10.0', '^1.10.0-20210511', true); // current date
testIsValidVersion('1.10.0', '^1.10.0-20210510', true); // before date
testIsValidVersion('1.10.0', '^1.10.0-20210512', false); // future date
testIsValidVersion('1.10.1', '^1.10.0-20200101', true); // before date, but ahead version
testIsValidVersion('1.11.0', '^1.10.0-20200101', true);
}); });
}); });

View file

@ -69,9 +69,10 @@ export class CachedExtensionScanner {
const version = this._productService.version; const version = this._productService.version;
const commit = this._productService.commit; const commit = this._productService.commit;
const date = this._productService.date;
const devMode = !!process.env['VSCODE_DEV']; const devMode = !!process.env['VSCODE_DEV'];
const locale = platform.language; const locale = platform.language;
const input = new ExtensionScannerInput(version, commit, locale, devMode, path, isBuiltin, false, translations); const input = new ExtensionScannerInput(version, date, commit, locale, devMode, path, isBuiltin, false, translations);
return ExtensionScanner.scanSingleExtension(input, log); return ExtensionScanner.scanSingleExtension(input, log);
} }
@ -245,6 +246,7 @@ export class CachedExtensionScanner {
const version = productService.version; const version = productService.version;
const commit = productService.commit; const commit = productService.commit;
const date = productService.date;
const devMode = !!process.env['VSCODE_DEV']; const devMode = !!process.env['VSCODE_DEV'];
const locale = platform.language; const locale = platform.language;
@ -253,7 +255,7 @@ export class CachedExtensionScanner {
notificationService, notificationService,
environmentService, environmentService,
BUILTIN_MANIFEST_CACHE_FILE, BUILTIN_MANIFEST_CACHE_FILE,
new ExtensionScannerInput(version, commit, locale, devMode, getSystemExtensionsRoot(), true, false, translations), new ExtensionScannerInput(version, date, commit, locale, devMode, getSystemExtensionsRoot(), true, false, translations),
log log
); );
@ -266,7 +268,7 @@ export class CachedExtensionScanner {
const controlFile = fs.promises.readFile(controlFilePath, 'utf8') const controlFile = fs.promises.readFile(controlFilePath, 'utf8')
.then<IBuiltInExtensionControl>(raw => JSON.parse(raw), () => ({} as any)); .then<IBuiltInExtensionControl>(raw => JSON.parse(raw), () => ({} as any));
const input = new ExtensionScannerInput(version, commit, locale, devMode, getExtraDevSystemExtensionsRoot(), true, false, translations); const input = new ExtensionScannerInput(version, date, commit, locale, devMode, getExtraDevSystemExtensionsRoot(), true, false, translations);
const extraBuiltinExtensions = Promise.all([builtInExtensions, controlFile]) const extraBuiltinExtensions = Promise.all([builtInExtensions, controlFile])
.then(([builtInExtensions, control]) => new ExtraBuiltInExtensionResolver(builtInExtensions, control)) .then(([builtInExtensions, control]) => new ExtraBuiltInExtensionResolver(builtInExtensions, control))
.then(resolver => ExtensionScanner.scanExtensions(input, log, resolver)); .then(resolver => ExtensionScanner.scanExtensions(input, log, resolver));
@ -279,7 +281,7 @@ export class CachedExtensionScanner {
notificationService, notificationService,
environmentService, environmentService,
USER_MANIFEST_CACHE_FILE, USER_MANIFEST_CACHE_FILE,
new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionsPath, false, false, translations), new ExtensionScannerInput(version, date, commit, locale, devMode, environmentService.extensionsPath, false, false, translations),
log log
)); ));
@ -288,7 +290,7 @@ export class CachedExtensionScanner {
if (environmentService.isExtensionDevelopment && environmentService.extensionDevelopmentLocationURI) { if (environmentService.isExtensionDevelopment && environmentService.extensionDevelopmentLocationURI) {
const extDescsP = environmentService.extensionDevelopmentLocationURI.filter(extLoc => extLoc.scheme === Schemas.file).map(extLoc => { const extDescsP = environmentService.extensionDevelopmentLocationURI.filter(extLoc => extLoc.scheme === Schemas.file).map(extLoc => {
return ExtensionScanner.scanOneOrMultipleExtensions( return ExtensionScanner.scanOneOrMultipleExtensions(
new ExtensionScannerInput(version, commit, locale, devMode, originalFSPath(extLoc), false, true, translations), log new ExtensionScannerInput(version, date, commit, locale, devMode, originalFSPath(extLoc), false, true, translations), log
); );
}); });
developedExtensions = Promise.all(extDescsP).then((extDescArrays: IExtensionDescription[][]) => { developedExtensions = Promise.all(extDescsP).then((extDescArrays: IExtensionDescription[][]) => {

View file

@ -30,14 +30,16 @@ export interface NlsConfiguration {
abstract class ExtensionManifestHandler { abstract class ExtensionManifestHandler {
protected readonly _ourVersion: string; protected readonly _ourVersion: string;
protected readonly _ourProductDate: string | undefined;
protected readonly _log: ILog; protected readonly _log: ILog;
protected readonly _absoluteFolderPath: string; protected readonly _absoluteFolderPath: string;
protected readonly _isBuiltin: boolean; protected readonly _isBuiltin: boolean;
protected readonly _isUnderDevelopment: boolean; protected readonly _isUnderDevelopment: boolean;
protected readonly _absoluteManifestPath: string; protected readonly _absoluteManifestPath: string;
constructor(ourVersion: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean) { constructor(ourVersion: string, ourProductDate: string | undefined, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean) {
this._ourVersion = ourVersion; this._ourVersion = ourVersion;
this._ourProductDate = ourProductDate;
this._log = log; this._log = log;
this._absoluteFolderPath = absoluteFolderPath; this._absoluteFolderPath = absoluteFolderPath;
this._isBuiltin = isBuiltin; this._isBuiltin = isBuiltin;
@ -91,8 +93,8 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
private readonly _nlsConfig: NlsConfiguration; private readonly _nlsConfig: NlsConfiguration;
constructor(ourVersion: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration) { constructor(ourVersion: string, ourProductDate: string | undefined, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration) {
super(ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment); super(ourVersion, ourProductDate, log, absoluteFolderPath, isBuiltin, isUnderDevelopment);
this._nlsConfig = nlsConfig; this._nlsConfig = nlsConfig;
} }
@ -318,7 +320,7 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
extensionDescription.isUnderDevelopment = this._isUnderDevelopment; extensionDescription.isUnderDevelopment = this._isUnderDevelopment;
let notices: string[] = []; let notices: string[] = [];
if (!ExtensionManifestValidator.isValidExtensionDescription(this._ourVersion, this._absoluteFolderPath, extensionDescription, notices)) { if (!ExtensionManifestValidator.isValidExtensionDescription(this._ourVersion, this._ourProductDate, this._absoluteFolderPath, extensionDescription, notices)) {
notices.forEach((error) => { notices.forEach((error) => {
this._log.error(this._absoluteFolderPath, error); this._log.error(this._absoluteFolderPath, error);
}); });
@ -344,7 +346,7 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
return extensionDescription; return extensionDescription;
} }
private static isValidExtensionDescription(version: string, extensionFolderPath: string, extensionDescription: IExtensionDescription, notices: string[]): boolean { private static isValidExtensionDescription(version: string, productDate: string | undefined, extensionFolderPath: string, extensionDescription: IExtensionDescription, notices: string[]): boolean {
if (!ExtensionManifestValidator.baseIsValidExtensionDescription(extensionFolderPath, extensionDescription, notices)) { if (!ExtensionManifestValidator.baseIsValidExtensionDescription(extensionFolderPath, extensionDescription, notices)) {
return false; return false;
@ -355,7 +357,7 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
return false; return false;
} }
return isValidExtensionVersion(version, extensionDescription, notices); return isValidExtensionVersion(version, productDate, extensionDescription, notices);
} }
private static baseIsValidExtensionDescription(extensionFolderPath: string, extensionDescription: IExtensionDescription, notices: string[]): boolean { private static baseIsValidExtensionDescription(extensionFolderPath: string, extensionDescription: IExtensionDescription, notices: string[]): boolean {
@ -453,6 +455,7 @@ export class ExtensionScannerInput {
constructor( constructor(
public readonly ourVersion: string, public readonly ourVersion: string,
public readonly ourProductDate: string | undefined,
public readonly commit: string | undefined, public readonly commit: string | undefined,
public readonly locale: string | undefined, public readonly locale: string | undefined,
public readonly devMode: boolean, public readonly devMode: boolean,
@ -476,6 +479,7 @@ export class ExtensionScannerInput {
public static equals(a: ExtensionScannerInput, b: ExtensionScannerInput): boolean { public static equals(a: ExtensionScannerInput, b: ExtensionScannerInput): boolean {
return ( return (
a.ourVersion === b.ourVersion a.ourVersion === b.ourVersion
&& a.ourProductDate === b.ourProductDate
&& a.commit === b.commit && a.commit === b.commit
&& a.locale === b.locale && a.locale === b.locale
&& a.devMode === b.devMode && a.devMode === b.devMode
@ -512,23 +516,23 @@ export class ExtensionScanner {
/** /**
* Read the extension defined in `absoluteFolderPath` * Read the extension defined in `absoluteFolderPath`
*/ */
private static scanExtension(version: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration): Promise<IExtensionDescription | null> { private static scanExtension(version: string, productDate: string | undefined, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration): Promise<IExtensionDescription | null> {
absoluteFolderPath = path.normalize(absoluteFolderPath); absoluteFolderPath = path.normalize(absoluteFolderPath);
let parser = new ExtensionManifestParser(version, log, absoluteFolderPath, isBuiltin, isUnderDevelopment); let parser = new ExtensionManifestParser(version, productDate, log, absoluteFolderPath, isBuiltin, isUnderDevelopment);
return parser.parse().then<IExtensionDescription | null>((extensionDescription) => { return parser.parse().then<IExtensionDescription | null>((extensionDescription) => {
if (extensionDescription === null) { if (extensionDescription === null) {
return null; return null;
} }
let nlsReplacer = new ExtensionManifestNLSReplacer(version, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig); let nlsReplacer = new ExtensionManifestNLSReplacer(version, productDate, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig);
return nlsReplacer.replaceNLS(extensionDescription); return nlsReplacer.replaceNLS(extensionDescription);
}).then((extensionDescription) => { }).then((extensionDescription) => {
if (extensionDescription === null) { if (extensionDescription === null) {
return null; return null;
} }
let validator = new ExtensionManifestValidator(version, log, absoluteFolderPath, isBuiltin, isUnderDevelopment); let validator = new ExtensionManifestValidator(version, productDate, log, absoluteFolderPath, isBuiltin, isUnderDevelopment);
return validator.validate(extensionDescription); return validator.validate(extensionDescription);
}); });
} }
@ -566,7 +570,7 @@ export class ExtensionScanner {
} }
const nlsConfig = ExtensionScannerInput.createNLSConfig(input); const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
let _extensionDescriptions = await Promise.all(refs.map(r => this.scanExtension(input.ourVersion, log, r.path, isBuiltin, isUnderDevelopment, nlsConfig))); let _extensionDescriptions = await Promise.all(refs.map(r => this.scanExtension(input.ourVersion, input.ourProductDate, log, r.path, isBuiltin, isUnderDevelopment, nlsConfig)));
let extensionDescriptions = arrays.coalesce(_extensionDescriptions); let extensionDescriptions = arrays.coalesce(_extensionDescriptions);
extensionDescriptions = extensionDescriptions.filter(item => item !== null && !obsolete[new ExtensionIdentifierWithVersion({ id: getGalleryExtensionId(item.publisher, item.name) }, item.version).key()]); extensionDescriptions = extensionDescriptions.filter(item => item !== null && !obsolete[new ExtensionIdentifierWithVersion({ id: getGalleryExtensionId(item.publisher, item.name) }, item.version).key()]);
@ -601,7 +605,7 @@ export class ExtensionScanner {
return pfs.SymlinkSupport.existsFile(path.join(absoluteFolderPath, MANIFEST_FILE)).then((exists) => { return pfs.SymlinkSupport.existsFile(path.join(absoluteFolderPath, MANIFEST_FILE)).then((exists) => {
if (exists) { if (exists) {
const nlsConfig = ExtensionScannerInput.createNLSConfig(input); const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
return this.scanExtension(input.ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig).then((extensionDescription) => { return this.scanExtension(input.ourVersion, input.ourProductDate, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig).then((extensionDescription) => {
if (extensionDescription === null) { if (extensionDescription === null) {
return []; return [];
} }
@ -620,7 +624,7 @@ export class ExtensionScanner {
const isBuiltin = input.isBuiltin; const isBuiltin = input.isBuiltin;
const isUnderDevelopment = input.isUnderDevelopment; const isUnderDevelopment = input.isUnderDevelopment;
const nlsConfig = ExtensionScannerInput.createNLSConfig(input); const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
return this.scanExtension(input.ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig); return this.scanExtension(input.ourVersion, input.ourProductDate, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig);
} }
public static mergeBuiltinExtensions(builtinExtensions: Promise<IExtensionDescription[]>, extraBuiltinExtensions: Promise<IExtensionDescription[]>): Promise<IExtensionDescription[]> { public static mergeBuiltinExtensions(builtinExtensions: Promise<IExtensionDescription[]>, extraBuiltinExtensions: Promise<IExtensionDescription[]>): Promise<IExtensionDescription[]> {