azure-pipelines-tui 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/dist/cache.js +54 -0
- package/dist/debugRetry.js +150 -0
- package/dist/debugSignalR.js +151 -0
- package/dist/debugWarnings.js +169 -0
- package/dist/lib/api.js +256 -0
- package/dist/lib/format.js +580 -0
- package/dist/lib/types.js +3 -0
- package/dist/screens/EnvironmentsScreen.js +220 -0
- package/dist/screens/MappingScreen.js +165 -0
- package/dist/screens/PipelineRunScreen.js +408 -0
- package/dist/screens/PipelineRunsScreen.js +135 -0
- package/dist/screens/PipelinesScreen.js +184 -0
- package/dist/screens/StagesScreen.js +194 -0
- package/dist/screens/context.js +2 -0
- package/dist/signalr.js +117 -0
- package/dist/tui.js +358 -0
- package/package.json +31 -0
package/dist/lib/api.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.enc = exports.API_VER = void 0;
|
|
7
|
+
exports.loadConfig = loadConfig;
|
|
8
|
+
exports.saveConfig = saveConfig;
|
|
9
|
+
exports.getToken = getToken;
|
|
10
|
+
exports.httpGet = httpGet;
|
|
11
|
+
exports.httpGetPaged = httpGetPaged;
|
|
12
|
+
exports.httpPatch = httpPatch;
|
|
13
|
+
exports.fetchAllEnvironments = fetchAllEnvironments;
|
|
14
|
+
exports.fetchLatestDeployment = fetchLatestDeployment;
|
|
15
|
+
exports.fetchBuildInfo = fetchBuildInfo;
|
|
16
|
+
exports.fetchPipelineDefinitions = fetchPipelineDefinitions;
|
|
17
|
+
exports.fetchPipelineRuns = fetchPipelineRuns;
|
|
18
|
+
exports.fetchRunStages = fetchRunStages;
|
|
19
|
+
exports.fetchProjectId = fetchProjectId;
|
|
20
|
+
exports.buildBase = buildBase;
|
|
21
|
+
exports.fetchBuild = fetchBuild;
|
|
22
|
+
exports.fetchTimeline = fetchTimeline;
|
|
23
|
+
exports.fetchLogLines = fetchLogLines;
|
|
24
|
+
const https_1 = __importDefault(require("https"));
|
|
25
|
+
const child_process_1 = require("child_process");
|
|
26
|
+
const url_1 = require("url");
|
|
27
|
+
const fs_1 = __importDefault(require("fs"));
|
|
28
|
+
const cache_js_1 = require("../cache.js");
|
|
29
|
+
// ── Config ────────────────────────────────────────────────────────────────────
|
|
30
|
+
function loadConfig(configFile) {
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(fs_1.default.readFileSync(configFile, "utf8"));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return { mappings: [] };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function saveConfig(configFile, cfg) {
|
|
39
|
+
fs_1.default.writeFileSync(configFile, JSON.stringify(cfg, null, 2), "utf8");
|
|
40
|
+
}
|
|
41
|
+
// ── Token management ──────────────────────────────────────────────────────────
|
|
42
|
+
const ADO_RESOURCE = "499b84ac-1321-427f-aa17-267ca6975798";
|
|
43
|
+
let cachedToken = null;
|
|
44
|
+
let tokenExpiry = 0;
|
|
45
|
+
async function getToken(azConfigDir) {
|
|
46
|
+
if (cachedToken && Date.now() < tokenExpiry - 60_000)
|
|
47
|
+
return cachedToken;
|
|
48
|
+
const env = azConfigDir ? { ...process.env, AZURE_CONFIG_DIR: azConfigDir } : process.env;
|
|
49
|
+
const raw = (0, child_process_1.execSync)(`az account get-access-token --resource ${ADO_RESOURCE} --output json`, { encoding: "utf8", env });
|
|
50
|
+
const { accessToken, expiresOn } = JSON.parse(raw);
|
|
51
|
+
cachedToken = accessToken;
|
|
52
|
+
tokenExpiry = new Date(expiresOn).getTime();
|
|
53
|
+
return cachedToken;
|
|
54
|
+
}
|
|
55
|
+
// ── HTTP helpers ──────────────────────────────────────────────────────────────
|
|
56
|
+
function httpGet(reqUrl, token, accept = "application/json") {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const u = new url_1.URL(reqUrl);
|
|
59
|
+
https_1.default.get({ hostname: u.hostname, path: u.pathname + u.search,
|
|
60
|
+
headers: { Authorization: `Bearer ${token}`, Accept: accept } }, (res) => {
|
|
61
|
+
let data = "";
|
|
62
|
+
res.on("data", (c) => (data += c));
|
|
63
|
+
res.on("end", () => {
|
|
64
|
+
if ((res.statusCode ?? 0) >= 400)
|
|
65
|
+
return reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`));
|
|
66
|
+
try {
|
|
67
|
+
resolve((accept === "application/json" ? JSON.parse(data) : data));
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
reject(e);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}).on("error", reject);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function httpGetPaged(reqUrl, token) {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const u = new url_1.URL(reqUrl);
|
|
79
|
+
https_1.default.get({ hostname: u.hostname, path: u.pathname + u.search,
|
|
80
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "application/json" } }, (res) => {
|
|
81
|
+
let data = "";
|
|
82
|
+
res.on("data", (c) => (data += c));
|
|
83
|
+
res.on("end", () => {
|
|
84
|
+
if ((res.statusCode ?? 0) >= 400)
|
|
85
|
+
return reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`));
|
|
86
|
+
try {
|
|
87
|
+
const ct = res.headers["x-ms-continuationtoken"];
|
|
88
|
+
resolve({ data: JSON.parse(data), ct });
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
reject(e);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}).on("error", reject);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function httpPatch(reqUrl, token, body) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const u = new url_1.URL(reqUrl);
|
|
100
|
+
const payload = JSON.stringify(body);
|
|
101
|
+
const req = https_1.default.request({ hostname: u.hostname, path: u.pathname + u.search, method: "PATCH",
|
|
102
|
+
headers: {
|
|
103
|
+
Authorization: `Bearer ${token}`,
|
|
104
|
+
"Content-Type": "application/json",
|
|
105
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
106
|
+
} }, res => {
|
|
107
|
+
let data = "";
|
|
108
|
+
res.on("data", (c) => (data += c));
|
|
109
|
+
res.on("end", () => {
|
|
110
|
+
if ((res.statusCode ?? 0) >= 400)
|
|
111
|
+
return reject(new Error(`HTTP ${res.statusCode}: ${data}`));
|
|
112
|
+
resolve((data ? JSON.parse(data) : {}));
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
req.on("error", reject);
|
|
116
|
+
req.write(payload);
|
|
117
|
+
req.end();
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
// ── ADO API ───────────────────────────────────────────────────────────────────
|
|
121
|
+
exports.API_VER = "api-version=7.1";
|
|
122
|
+
exports.enc = encodeURIComponent;
|
|
123
|
+
async function fetchAllEnvironments(org, project, token) {
|
|
124
|
+
const ckey = `envs_${org}_${project}`;
|
|
125
|
+
const cached = (0, cache_js_1.readCache)(ckey);
|
|
126
|
+
if (cached)
|
|
127
|
+
return cached;
|
|
128
|
+
const all = [];
|
|
129
|
+
let ct;
|
|
130
|
+
do {
|
|
131
|
+
const url = `https://dev.azure.com/${(0, exports.enc)(org)}/${(0, exports.enc)(project)}/_apis/distributedtask/environments?$top=100` +
|
|
132
|
+
(ct ? `&continuationToken=${ct}` : "") + `&${exports.API_VER}`;
|
|
133
|
+
const { data, ct: next } = await httpGetPaged(url, token);
|
|
134
|
+
all.push(...(data.value ?? []));
|
|
135
|
+
ct = next;
|
|
136
|
+
} while (ct);
|
|
137
|
+
all.sort((a, b) => a.name.localeCompare(b.name));
|
|
138
|
+
(0, cache_js_1.writeCache)(ckey, all, 5 * 60_000);
|
|
139
|
+
return all;
|
|
140
|
+
}
|
|
141
|
+
async function fetchLatestDeployment(org, project, envId, token) {
|
|
142
|
+
const ckey = `deploy_${org}_${project}_${envId}`;
|
|
143
|
+
const cached = (0, cache_js_1.readCache)(ckey);
|
|
144
|
+
if (cached !== null)
|
|
145
|
+
return cached;
|
|
146
|
+
try {
|
|
147
|
+
const url = `https://dev.azure.com/${(0, exports.enc)(org)}/${(0, exports.enc)(project)}/_apis/distributedtask/environments/${envId}/environmentdeploymentrecords?top=1&${exports.API_VER}`;
|
|
148
|
+
const data = await httpGet(url, token);
|
|
149
|
+
const record = data.value?.[0] ?? null;
|
|
150
|
+
(0, cache_js_1.writeCache)(ckey, record, 2 * 60_000);
|
|
151
|
+
return record;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function fetchBuildInfo(org, project, buildId, token) {
|
|
158
|
+
const id = Number(buildId);
|
|
159
|
+
if (!id)
|
|
160
|
+
return null;
|
|
161
|
+
const ckey = `build_${org}_${project}_${id}`;
|
|
162
|
+
const cached = (0, cache_js_1.readCache)(ckey);
|
|
163
|
+
if (cached)
|
|
164
|
+
return cached;
|
|
165
|
+
try {
|
|
166
|
+
const url = `https://dev.azure.com/${(0, exports.enc)(org)}/${(0, exports.enc)(project)}/_apis/build/builds/${id}?${exports.API_VER}`;
|
|
167
|
+
const data = await httpGet(url, token);
|
|
168
|
+
const ttl = data.status === "completed" ? 60 * 60_000 : 2 * 60_000;
|
|
169
|
+
(0, cache_js_1.writeCache)(ckey, data, ttl);
|
|
170
|
+
return data;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function fetchPipelineDefinitions(org, project, token) {
|
|
177
|
+
const ckey = `pipelines_${org}_${project}`;
|
|
178
|
+
const cached = (0, cache_js_1.readCache)(ckey);
|
|
179
|
+
if (cached)
|
|
180
|
+
return cached;
|
|
181
|
+
const url = `https://dev.azure.com/${(0, exports.enc)(org)}/${(0, exports.enc)(project)}/_apis/build/definitions?$top=1000&${exports.API_VER}`;
|
|
182
|
+
const data = await httpGet(url, token);
|
|
183
|
+
const defs = (data.value ?? []).sort((a, b) => a.name.localeCompare(b.name));
|
|
184
|
+
(0, cache_js_1.writeCache)(ckey, defs, 10 * 60_000);
|
|
185
|
+
return defs;
|
|
186
|
+
}
|
|
187
|
+
async function fetchPipelineRuns(org, project, pipelineId, token, top = 50) {
|
|
188
|
+
const ckey = `runs_${org}_${project}_${pipelineId}`;
|
|
189
|
+
const cached = (0, cache_js_1.readCache)(ckey);
|
|
190
|
+
if (cached)
|
|
191
|
+
return cached;
|
|
192
|
+
const url = `https://dev.azure.com/${(0, exports.enc)(org)}/${(0, exports.enc)(project)}/_apis/build/builds?definitions=${pipelineId}&$top=${top}&${exports.API_VER}`;
|
|
193
|
+
const data = await httpGet(url, token);
|
|
194
|
+
const runs = (data.value ?? []).sort((a, b) => b.id - a.id);
|
|
195
|
+
(0, cache_js_1.writeCache)(ckey, runs, 2 * 60_000);
|
|
196
|
+
return runs;
|
|
197
|
+
}
|
|
198
|
+
async function fetchRunStages(org, project, runId, token) {
|
|
199
|
+
const ckey = `stages_${org}_${project}_${runId}`;
|
|
200
|
+
const cached = (0, cache_js_1.readCache)(ckey);
|
|
201
|
+
if (cached) {
|
|
202
|
+
const hasSucceeded = cached.some(s => s.result === "succeeded" || s.result === "failed");
|
|
203
|
+
const hasFinishTime = cached.some(s => s.finishTime);
|
|
204
|
+
const hasWarningCount = cached.every(s => s.warningCount !== undefined);
|
|
205
|
+
if ((!hasSucceeded || hasFinishTime) && hasWarningCount)
|
|
206
|
+
return cached;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
const url = `https://dev.azure.com/${(0, exports.enc)(org)}/${(0, exports.enc)(project)}/_apis/build/builds/${runId}/timeline?${exports.API_VER}`;
|
|
210
|
+
const data = await httpGet(url, token);
|
|
211
|
+
const allRecords = data.records ?? [];
|
|
212
|
+
const childIds = new Set(allRecords.map(r => r.parentId).filter(Boolean));
|
|
213
|
+
const childrenOf = new Map();
|
|
214
|
+
for (const r of allRecords) {
|
|
215
|
+
if (r.parentId) {
|
|
216
|
+
if (!childrenOf.has(r.parentId))
|
|
217
|
+
childrenOf.set(r.parentId, []);
|
|
218
|
+
childrenOf.get(r.parentId).push(r);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function sumWarnings(id) {
|
|
222
|
+
const kids = childrenOf.get(id) ?? [];
|
|
223
|
+
if (kids.length === 0)
|
|
224
|
+
return 0;
|
|
225
|
+
let total = 0;
|
|
226
|
+
for (const kid of kids)
|
|
227
|
+
total += childIds.has(kid.id) ? sumWarnings(kid.id) : (kid.warningCount ?? 0);
|
|
228
|
+
return total;
|
|
229
|
+
}
|
|
230
|
+
const stages = allRecords
|
|
231
|
+
.filter(r => r.type === "Stage")
|
|
232
|
+
.map(r => ({ id: r.id, name: r.name, state: r.state, result: r.result, order: r.order, finishTime: r.finishTime, warningCount: sumWarnings(r.id) }));
|
|
233
|
+
const allDone = stages.length > 0 && stages.every(s => s.state === "completed");
|
|
234
|
+
(0, cache_js_1.writeCache)(ckey, stages, allDone ? 60 * 60_000 : 2 * 60_000);
|
|
235
|
+
return stages;
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async function fetchProjectId(org, project, token) {
|
|
242
|
+
const { id } = await httpGet(`https://dev.azure.com/${(0, exports.enc)(org)}/_apis/projects/${(0, exports.enc)(project)}?${exports.API_VER}`, token);
|
|
243
|
+
return id;
|
|
244
|
+
}
|
|
245
|
+
function buildBase(org, project, buildId) {
|
|
246
|
+
return `https://dev.azure.com/${(0, exports.enc)(org)}/${(0, exports.enc)(project)}/_apis/build/builds/${buildId}`;
|
|
247
|
+
}
|
|
248
|
+
async function fetchBuild(org, project, buildId, token) {
|
|
249
|
+
return httpGet(`${buildBase(org, project, buildId)}?${exports.API_VER}`, token);
|
|
250
|
+
}
|
|
251
|
+
async function fetchTimeline(org, project, buildId, token) {
|
|
252
|
+
return httpGet(`${buildBase(org, project, buildId)}/timeline?${exports.API_VER}`, token).catch(() => null);
|
|
253
|
+
}
|
|
254
|
+
async function fetchLogLines(org, project, buildId, logId, startLine, token) {
|
|
255
|
+
return httpGet(`${buildBase(org, project, buildId)}/logs/${logId}?startLine=${startLine}&${exports.API_VER}`, token).catch(() => null);
|
|
256
|
+
}
|