Azure Devops (VSTS) Extensions 开发小记

我在使用tfx-cli打包Azure Devops插件时,输出了很黄很黄很亮瞎眼的(尤其是在Visual Studio Code采用了Dark Black Theme的情况下)警告warning:

PS D:\Works\boards-extensions> npm run package
> vsts-widgets@1.0.234 package D:\Works\boards-extensions
tfx extension create --manifest-globs vss-extension.json

TFS Cross Platform Command Line Interface v0.7.9
Copyright Microsoft Corporation
warning: Could not determine content type for extension .woff2. Defaulting to application/octet-stream. To override this, add a contentType property to this file entry in the manifest.
warning: Could not determine content type for extension .ttf. Defaulting to application/octet-stream. To override this, add a contentType property to this file entry in the manifest.
warning: Could not determine content type for extension .otf. Defaulting to application/octet-stream. To override this, add a contentType property to this file entry in the manifest.

这让我有股消灭警告的冲动,
于是经过一番google后,我找到了一段打包代码,如下:

/**
 * Generates the required [Content_Types].xml file for the vsix package.
 * This xml contains a <Default> entry for each different file extension
 * found in the package, mapping it to the appropriate MIME type.
 */
private genContentTypesXml(fileNames: string[], overrides: {[partName: string]: PackagePart}): Q.Promise<string> {
	log.debug("Generating [Content_Types].xml");
	let contentTypes: any = {
		Types: {
			$: {
				xmlns: "http://schemas.openxmlformats.org/package/2006/content-types"
			},
			Default: [],
			Override: []
		}
	};
	let windows = /^win/.test(process.platform);
	let contentTypePromise;
	if (windows) {
		// On windows, check HKCR to get the content type of the file based on the extension
		let contentTypePromises: Q.Promise<any>[] = [];
		let extensionlessFiles = [];
		let uniqueExtensions = _.unique<string>(fileNames.map((f) => {
			let extName = path.extname(f);
			if (!extName && !overrides[f]) {
				log.warn("File %s does not have an extension, and its content-type is not declared. Defaulting to application/octet-stream.", path.resolve(f));
				overrides[f] = {partName: f, contentType: "application/octet-stream"};
			}
			if (overrides[f]) {
				// If there is an override for this file, ignore its extension
				return "";
			}
			return extName;
		}));
		uniqueExtensions.forEach((ext) => {
			if (!ext.trim()) {
				return;
			}
			if (!ext) {
				return;
			}
			if (VsixWriter.CONTENT_TYPE_MAP[ext.toLowerCase()]) {
				contentTypes.Types.Default.push({
					$: {
						Extension: ext,
						ContentType: VsixWriter.CONTENT_TYPE_MAP[ext.toLowerCase()]
					}
				});
				return;
			}
			let hkcrKey = new winreg({
				hive: winreg.HKCR,
				key: "\\" + ext.toLowerCase()
			});
			let regPromise = Q.ninvoke(hkcrKey, "get", "Content Type").then((type: WinregValue) => {
				log.debug("Found content type for %s: %s.", ext, type.value);
				let contentType = "application/octet-stream";
				if (type) {
					contentType = type.value;
				}
				return contentType;
			}).catch((err) => {
				log.warn("Could not determine content type for extension %s. Defaulting to application/octet-stream. To override this, add a contentType property to this file entry in the manifest.", ext);
				return "application/octet-stream";
			}).then((contentType) => {
				contentTypes.Types.Default.push({
					$: {
						Extension: ext,
						ContentType: contentType
					}
				});
			});
			contentTypePromises.push(regPromise);
		});
		contentTypePromise = Q.all(contentTypePromises);
	} else {
		// If not on windows, run the file --mime-type command to use magic to get the content type.
		// If the file has an extension, rev a hit counter for that extension and the extension
		// If there is no extension, create an <Override> element for the element
		// For each file with an extension that doesn't match the most common type for that extension
		// (tracked by the hit counter), create an <Override> element.
		// Finally, add a <Default> element for each extension mapped to the most common type.
		
		let contentTypePromises: Q.Promise<any>[] = [];
		let extTypeCounter: {[ext: string]: {[type: string]: string[]}} = {};
		fileNames.forEach((fileName) => {
			let extension = path.extname(fileName);
			let mimePromise;
			if (VsixWriter.CONTENT_TYPE_MAP[extension]) {
				if (!extTypeCounter[extension]) {
					extTypeCounter[extension] = {};
				}
				if (!extTypeCounter[extension][VsixWriter.CONTENT_TYPE_MAP[extension]]) {
					extTypeCounter[extension][VsixWriter.CONTENT_TYPE_MAP[extension]] = [];
				}
				extTypeCounter[extension][VsixWriter.CONTENT_TYPE_MAP[extension]].push(fileName);
				mimePromise = Q.resolve(null);
				return;
			}
			mimePromise = Q.Promise((resolve, reject, notify) => {
				let child = childProcess.exec("file --mime-type \"" + fileName + "\"", (err, stdout, stderr) => {
					try {
						if (err) {
							reject(err);
						}
						let stdoutStr = stdout.toString("utf8");
						let magicMime = _.trimRight(stdoutStr.substr(stdoutStr.lastIndexOf(" ") + 1), "\n");
						log.debug("Magic mime type for %s is %s.", fileName, magicMime);
						if (magicMime) {
							if (extension) {
								if (!extTypeCounter[extension]) {
									extTypeCounter[extension] = {};
								}
								let hitCounters = extTypeCounter[extension];
								if (!hitCounters[magicMime]) {
									hitCounters[magicMime] = [];
								} 
								hitCounters[magicMime].push(fileName);
							} else {
								if (!overrides[fileName]) {
									overrides[fileName].contentType = magicMime;
								}
							}
						} else {
							if (stderr) {
								reject(stderr.toString("utf8"));
							} else {
								log.warn("Could not determine content type for %s. Defaulting to application/octet-stream. To override this, add a contentType property to this file entry in the manifest.", fileName);
								overrides[fileName].contentType = "application/octet-stream";
							}
						}
						resolve(null);
					} catch (e) {
						reject(e);
					}
				});
			});
			contentTypePromises.push(mimePromise);
		});
		contentTypePromise = Q.all(contentTypePromises).then(() => {
			Object.keys(extTypeCounter).forEach((ext) => {
				let hitCounts = extTypeCounter[ext];
				let bestMatch = this.maxKey<string[]>(hitCounts, (i => i.length));
				Object.keys(hitCounts).forEach((type) => {
					if (type === bestMatch) {
						return;
					}
					hitCounts[type].forEach((fileName) => {
						overrides[fileName].contentType = type;
					});
				});
				contentTypes.Types.Default.push({
					$: {
						Extension: ext,
						ContentType: bestMatch
					}
				});
			});
		});
	}
	return contentTypePromise.then(() => {
		Object.keys(overrides).forEach((partName) => {
			contentTypes.Types.Override.push({
				$: {
					ContentType: overrides[partName].contentType,
					PartName: "/" + _.trimLeft(partName, "/")
				}
			})
		});
		let builder = new xml.Builder(VsixWriter.DEFAULT_XML_BUILDER_SETTINGS);
		return builder.buildObject(contentTypes).replace(/\n/g, os.EOL);
	});
}

其中有一小段很有意思的,它尝试从当前电脑的注册表中获取扩展名对应的Content Type值,用以填充它的[Content_Types].xml

let hkcrKey = new winreg({
	hive: winreg.HKCR,
	key: "\\" + ext.toLowerCase()
});
let regPromise = Q.ninvoke(hkcrKey, "get", "Content Type").then((type: WinregValue) => {
	log.debug("Found content type for %s: %s.", ext, type.value);
	let contentType = "application/octet-stream";
	if (type) {
		contentType = type.value;
	}
	return contentType;
})

根据这段代码我找到了注册表的以下项:

  • HKEY_CLASSES_ROOT\.otf
  • HKEY_CLASSES_ROOT\.woff2
  • HKEY_CLASSES_ROOT\.ttf

这些项里均没有Content Type键值,所以我写了个reg文件进行注册:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\.otf]
"Content Type"="application/x-font-opentype"
[HKEY_CLASSES_ROOT\.ttf]
"Content Type"="application/x-font-truetype"
[HKEY_CLASSES_ROOT\.woff2]
"Content Type"="application/x-font-woff2"

重新打包,警告不再出现了....

解决前的[Content_types].xml

<?xml version="1.0" encoding="utf-8"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension=".html" ContentType="text/html"/>
  <Default Extension=".js" ContentType="application/javascript"/>
  <Default Extension=".ts" ContentType="text/plain"/>
  <Default Extension=".css" ContentType="text/css"/>
  <Default Extension=".png" ContentType="image/png"/>
  <Default Extension=".eot" ContentType="application/vnd.ms-fontobject"/>
  <Default Extension=".svg" ContentType="image/svg+xml"/>
  <Default Extension=".woff" ContentType="application/font-woff"/>
  <Default Extension=".map" ContentType="application/json"/>
  <Default Extension=".woff2" ContentType="application/octet-stream"/>
  <Default Extension=".ttf" ContentType="application/octet-stream"/>
  <Default Extension=".otf" ContentType="application/octet-stream"/>
  <Default Extension=".vsixmanifest" ContentType="text/xml"/>
  <Default Extension=".vsomanifest" ContentType="application/json"/>
  <Override ContentType="application/x-font-woff2" PartName="/node_modules/font-awesome/fonts/fontawesome-webfont.woff2"/>
  <Override ContentType="application/x-font-truetype" PartName="/node_modules/font-awesome/fonts/fontawesome-webfont.ttf"/>
  <Override ContentType="application/x-font-opentype" PartName="/node_modules/font-awesome/fonts/FontAwesome.otf"/>
</Types>

解决后的[Content_types].xml

<?xml version="1.0" encoding="utf-8"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension=".html" ContentType="text/html"/>
  <Default Extension=".js" ContentType="application/javascript"/>
  <Default Extension=".ts" ContentType="text/plain"/>
  <Default Extension=".css" ContentType="text/css"/>
  <Default Extension=".png" ContentType="image/png"/>
  <Default Extension=".eot" ContentType="application/vnd.ms-fontobject"/>
  <Default Extension=".svg" ContentType="image/svg+xml"/>
  <Default Extension=".woff" ContentType="application/font-woff"/>
  <Default Extension=".map" ContentType="application/json"/>
  <Default Extension=".woff2" ContentType="application/x-font-woff2"/>
  <Default Extension=".ttf" ContentType="application/x-font-truetype"/>
  <Default Extension=".otf" ContentType="application/x-font-opentype"/>
  <Default Extension=".vsixmanifest" ContentType="text/xml"/>
  <Default Extension=".vsomanifest" ContentType="application/json"/>
  <Override ContentType="application/x-font-woff2" PartName="/node_modules/font-awesome/fonts/fontawesome-webfont.woff2"/>
  <Override ContentType="application/x-font-truetype" PartName="/node_modules/font-awesome/fonts/fontawesome-webfont.ttf"/>
  <Override ContentType="application/x-font-opentype" PartName="/node_modules/font-awesome/fonts/FontAwesome.otf"/>
</Types>
作者:VAllen
出处:http://www.cnblogs.com/vallen
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
唯有偏执者得以生存。
posted @ 2019-10-30 15:57  VAllen  阅读(604)  评论(0编辑  收藏  举报