Visual Studio Advent Calendar のトリを務めることになりました。
また、人知れず(実は)やっていた一人Advent Calendarも完走しました。
本題のVSTS拡張の方ですが、ここらでTypeScriptで書き換えてみたいと思います。
- VSTS 拡張を作ってみる (1) - 銀の光と碧い空
- VSTS 拡張を作ってみる (2) : VSTS Rest APIを実行する - 銀の光と碧い空
- VSTS 拡張を作ってみる (3) : 設定データの管理 - 銀の光と碧い空
- VSTS 拡張を作ってみる (4) : GridViewの拡張 - 銀の光と碧い空
- VSTS 拡張を作ってみる (5) : GridViewの拡張 その② - 銀の光と碧い空
TypeScriptについてはまじめに書くのが初めてな状態ですが、まず、TypeScriptはVisual Studio Codeで開発することにして、このあたりのページを参考に環境を整えました。
https://code.visualstudio.com/Docs/languages/typescript
さて、VSTS拡張をTypeScriptで書く方法ですが、これまた最新の情報は整理されきってはいないようです。情報が古いと言っていたサンプルリポジトリにTypeScriptで書かれたサンプルがあるのでそれを参考にしつつ、
公式ページのサンプルコードのいくつかはTypeScriptでも書かれているので、それらを参考にとりあえず動くものが作れました。
Overview of extensions for Visual Studio Team Services and Team Foundation Server
まず、フォルダ構造を説明します。おそらく変えてもいい場所もありますが、いったんこれで動かしているということで。
- .vdcode (VSCode用の設定フォルダ)
- tasks.json (VSCodeでTypeScriptをビルドするときのタスク定義ファイル)
- out/scripts (ビルドして生成するJavaScriptの出力先フォルダ)
- scripts (TypeScriptファイルの保存フォルダ)
- main.ts (今回記述するTypeScriptファイル)
- sdk/scripts/VSS.SDK.js (VSS SDKのライブラリ)
- typings (参照するライブラリのTypeScriptの型定義(d.ts)ファイル。vssとtfsはVSS SDKのGitHubリポジトリから、そのほかは適当な場所から持ってくる)
- jquery/jquery.d.ts
- knockout/knockout.d.ts
- q/q.d.ts
- tfs.d.ts
- vss.d.ts
- build.html (VSTS拡張で書くHTMLファイル)
- tsconfig.json (TypeScriptのビルドオプションファイル)
- vss-extension.json (VSTS拡張の定義ファイル)
tasks.json はこんな感じ
{ "version": "0.1.0", "command": "tsc", "isShellCommand": true, "showOutput": "silent", "args": ["-p", "."], "problemMatcher": "$tsc" }
tsconfig.json はこう。async/await
使いたくてes6
にしてみたら、VSS.d.ts にエラーが多発したので断念。
{ "compilerOptions": { "module": "amd", "target": "es5", "outDir": "out/scripts", "moduleResolution": "node" }, "files": ["scripts/main.ts"] }
vss-extension.json は基本的に同じですが、files
要素でVSIXに含めるスクリプトはJavaScriptのみ(VSS.SDK.js とコンパイルして出力されたjsファイルのみ)になります。
あとは肝心のTypeScriptですがこんな感じで書いてみました。ところどころ、JavaScript臭がしますが。。。あとこのコード、VSCodeで警告が出ます。どうも必要な上に実際のオブジェクトには存在するプロパティだけど、型定義ファイルには定義されていないものがあったりするようです...
/// <reference path='../typings/vss' /> /// <reference path='../typings/tfs' /> import Controls = require("VSS/Controls"); import Grids = require("VSS/Controls/Grids"); import TFS_Build_Contracts = require("TFS/Build/Contracts"); import VSS_Service = require("VSS/Service"); import TFS_Build_Client = require("TFS/Build/RestClient"); var vsoContext = VSS.getWebContext(); var build = TFS_Build_Client.getClient(); var container = $("#grid-container"); var regex = /^[^-_]+[-_]+([^-_]+)([-_]+|$)/; build.getDefinitions(vsoContext.project.name).then<Contracts.DefinitionReference[]>((buildDefinitions) =>{ build.getBuilds(vsoContext.project.name, buildDefinitions.map((x) => {return x.id;})).then<any>((builds) => { //grouping var groups = new Map<any, any>(); buildDefinitions.forEach(function(b){ var m = regex.exec(b.name); var n = m == null ? "" : m[1]; var group = groups.get(n); if (group == null){ group = []; groups.set(n, group); } group.push(b); }); console.log(groups); var output = []; groups.forEach(function(value, key, m){ var item: {groupName: string, children: [any]} = {groupName: key, children: value}; output.push(item); }); console.log(output); var gridOptions: Grids.IGridOptions = { height: "1000px", width: "100%", columns: [ { text: "group", index: "groupName", width: 100, indent: true }, { text: "ID", index: "id", width: 50 }, { text: "Name", index: "name", width: 200 }, { text: "Last Status", width: 50, getColumnValue: (index: number)=>{ var id = grid.getRowData(index).id; //buildNumberRevisionの最新1件を取得。OrderByDesend().FirstOrDefault() var filtered = builds.filter(function(e, i, array) { return e.definition.id == id; }).sort(function(a,b) { if (a.buildNumberRevision < b.buildNumberRevision) return 1; if (a.buildNumberRevision > b.buildNumberRevision) return -1; return 0; }); if (filtered == null || filtered.length == 0) return ""; console.log(filtered); if (filtered[0].result != 2) return "NG"; return "OK"; }} ], // This data source is rendered into the Grid columns defined above source: new Grids.GridHierarchySource(output), gutter: { contextMenu: true }, contextMenu: { items: [{ id: "open", text: "Open Details" } ], executeAction: (args) => { var buildDefinition = args.get_commandArgument().item; switch (args.get_commandName()) { case "open": window.parent.location.href = `${vsoContext.host.uri}/${vsoContext.project.name}/_build#_a=completed&definitionType=2&definitionId=${buildDefinition.id}`; break; case "queue": break; default: console.info(args.get_commandName()); } }, //コンパイルエラーになるけど必要 arguments: (contextInfo) => { return { item: contextInfo.item }; } } }; var grid = Controls.create<Grids.Grid, Grids.IGridOptions>(Grids.Grid, container, gridOptions); VSS.notifyLoadSucceeded(); }) });
後は、VSCodeでビルドして、今まで通りVSIXパッケージを作ってアップロードすれば使えます。