• Azure Devops (VSTS) Extensions 开发小记


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

    PS D:Worksoards-extensions> npm run package
    > vsts-widgets@1.0.234 package D:Worksoards-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), "
    ");
    						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(/
    /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>
    
  • 相关阅读:
    python之函数对象、函数嵌套、名称空间与作用域、装饰器
    python之函数
    python基础-小练习
    python基础之文件操作
    python基础之字符编码
    web开发-Django博客系统
    HotSpot的算法实现
    垃圾回收机制(GC)
    Java注意点...
    JVM内存区域及对象
  • 原文地址:https://www.cnblogs.com/VAllen/p/azure-devops-extensions-content-types-warning-fix.html
Copyright © 2020-2023  润新知