我在使用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. c# sqlserver 删除大批量数据超时

    我做的项目有个功能需要进行批量删除,删除的数据量有4.5W条数据. 通过下面的sql语句删除这么多数据,直接导致结果超时,无法删除数据. ,,,......) 我查了一些资料,可能找的不全,找到了一个 ...

  2. WPF 基础总结(学习建议)

    举个简单得例子, 类似造房子, 当然实际上可能非常细, 对应的如下所示: 在此之前, 需要了解的是. WPF项目是怎么启动的 Xaml的结构是怎么样组成, 命名控件定义引用的方法. 知道了如何在Xam ...

  3. 邮箱图标的css样式

    <div> <div style="position:relative; height:40px;width: 70px;border:2px solid black; m ...

  4. Python中的常见特殊方法或属性—— dir方法和dict属性

    一.__dir__方法 对象的__dir__()方法的作用是列出对象内部所有的属性名和方法名,该方法将会返回包含所有属性或方法名的序列. 如果程序对某个对象执行dir(obj)函数,实际上就是将该对象 ...

  5. c++和java的一些debug方法

    就上面那个绿色的小瓢虫,点了就进了debug模式. 好尴尬啊,就说一句话. 而且,要加断点,不然就一下debug完了.

  6. python3之利用字典和列表实现城市多级菜单

    利用字典和列表实现城市多级菜单 #coding:utf-8 #利用字典和列表实现城市多级菜单 addrIndex = {":"福建"} addrDict = {" ...

  7. boa移植 boa交叉编译

    官网:http://www.boa.org/ BOA 服务器是一个小巧高效的web服务器,是一个运行于unix或linux下的,支持CGI的.适合于嵌入式系统的单任务的http服务器,源代码开放.性能 ...

  8. 7 CentOS 7网卡配置

    首先重中之重:修改前一定要进行系统备份,如果是虚拟机进行快照 查看虚拟机的网卡配置 注意桥接模式和NAT模式     桥接模式:网络层面,虚拟机和PC处于同级地位,虚拟机直接和路由器相连     NA ...

  9. RabbitMQ启动报unknown exchange type 'x-delayed-message'

    RabbitMQ延迟队列插件未安装,导致以下问题: ShutdownSignalException: connection error; protocol method: #method<con ...

  10. itextpdf确定页面坐标的方式

    itextpdf的确定页面上的具体坐标是通过坐标来确定的. 它们的坐标轴是这样的:以左下角为原点的xy坐标轴. 在itextpdf的方法中中,定义某个点的位置,都是以左下方为原点坐标轴来确定. 如果要 ...