我在使用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>

Azure Devops (VSTS) Extensions 开发小记的更多相关文章

  1. Azure DevOps(一)利用Azure DevOps Pipeline 构建应用程序镜像到AWS ECR

    一,引言 最近项目上让开始学习AWS,作为一名合格的开发人员,当然也是学会利用Azure DevOps Pipeline 将应用程序部署到 AWS ECS(完全托管的容器编排服务).我们要学会将应用程 ...

  2. 微软改名部又出动啦!微软宣布VSTS改名为Azure DevOps

    本篇为翻译,原文地址:https://azure.microsoft.com/en-us/blog/introducing-azure-devops/ 作者:Jamie Cool,Azure DevO ...

  3. VSTS 更名为 Azure DevOps

    微软正式对外宣布Azure DevOps,其实就是原来的VSTS,我们来看一下Azure DevOps的介绍: 今天我们宣布Azure DevOps.与世界各地的客户和开发人员合作,很明显,DevOp ...

  4. [转贴]infoQ VSTS被拆成5个部分,以Azure DevOps服务形式推出

    VSTS被拆成5个部分,以Azure DevOps服务形式推出 http://www.infoq.com/cn/news/2018/09/vsts-divide5parts-azuredevops?u ...

  5. 使用ML.NET + Azure DevOps + Azure Container Instances打造机器学习生产化

    介绍 Azure DevOps,以前称为Visual Studio Team Services(VSTS),可帮助个人和组织更快地规划,协作和发布产品.其中一项值得注意的服务是Azure Pipeli ...

  6. 你好,Azure DevOps Server 2019;再见,Team Foundation Server

    微软正式发布Azure DevOps Server 2019的第一个版本,作为Team Foundation Server (TFS)2018的升级版本和替代产品. 这是目前市面上唯一一款将产品名称冠 ...

  7. Azure DevOps Server(TFS) 客户端分析

    Azure DevOps Server(TFS) 是微软公司的软件协作开发管理平台产品,为软件研发.测试.实施提供全流程的服务.作为一款应用服务器产品,他的客户端是什么,在哪里下载客户端?我们在项目实 ...

  8. Azure Devops测试管理(上)

    因为最近测试人员合并到我这边开发组,对于如何能更好管理测试流程和测试与开发能更高效的完成任务,通俗的说如何能更敏捷,深入思考,然后就开始琢磨起TFS(也称之为VSTS/Azure Devops,因为我 ...

  9. 【Azure DevOps系列】什么是Azure DevOps

    DevOps DevOps是一种重视"软件开发人员(Dev)"和"IT运维技术人员(Ops)"之间沟通合作的文化,它促进开发和运营团队之间的协作,以自动化和可重 ...

随机推荐

  1. Elasticsearch(ES) 创建索引

    欢迎关注笔者的公众号: 小哈学Java, 每日推送 Java 领域干货文章,关注即免费无套路附送 100G 海量学习.面试资源哟!! 个人网站: https://www.exception.site/ ...

  2. vue入门案例

    1.技术在迭代,有时候你为了生活没有办法,必须掌握一些新的技术,可能你不会或者没有时间造轮子,那么就先把利用轮子吧. <!DOCTYPE html> <html> <he ...

  3. 一个jetty部署多个项目配置之方法一

    https://my.oschina.net/wangyongqing/blog/115647 Jetty用户经常想配置他们的web应用到不同的虚拟主机. 通常情况下,一个单一的IP地址的机器有不同的 ...

  4. python3.0安装django2.0、xadmin

    1.操作环境 Windows10.python3.8 2.安装django2.0 pip install django==2.0 x   1 pip install django==2.0 3.安装相 ...

  5. 浅谈 Web框架

    一.Web框架本质 所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端 二.Web框架功能 socket收发消息 —— wsgiref(测试).uwsgi(线上 ...

  6. maven 学习---NetBeans IDE集成Maven

    NetBeans6.7更新版本已经内置对Maven支持.如遇以前的版本,Maven插件在插件管理器中可用.我们正在使用NetBeans在这个例子中使用6.9. 在NetBeans一些特点如下 您可以从 ...

  7. 【AI测试】也许这有你想知道的人工智能 (AI) 测试--第二篇

    概述此为人工智能 (AI) 测试第二篇 第一篇主要介绍了 人工智能测试.测试什么.测试数据等.第二篇主要介绍测试用例和测试报告.之后的文章可能具体介绍如何开展各项测试,以及具体项目举例如何测试.测试用 ...

  8. 关于宝塔一个站点绑定多个域名宝塔ssl证书的问题

    目前“宝塔SSL”自动申请绑定一个证书,即根域名和www域名,如果还需要绑定手机端m则需要绑定多个域名如果多域名绑定一个网站数据,需要新建多个站点指向同一文件目录. 用相同的方法,在不新建站点的前提下 ...

  9. 便宜的回文 (USACO 2007)(c++)

    2019-08-21便宜的回文(USACO 2007) 内存限制:128 MiB 时间限制:1000 ms 标准输入输出 题目类型:传统 评测方式:文本比较 题目描述 追踪每头奶牛的去向是一件棘手的任 ...

  10. lf 前后端分离 (3) 中间建跨域

    一.关于中间建跨域 为了减少跨域代码冗余,采用中间件 from django.utils.deprecation import MiddlewareMixin class CorsMiddleware ...