Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/tools/github.ts
6438 views
1
/*
2
* github.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*/
6
7
import { GitHubRelease } from "./types.ts";
8
9
// deno-lint-ignore-file camelcase
10
11
// GitHub Actions Detection
12
export function isGitHubActions(): boolean {
13
return Deno.env.get("GITHUB_ACTIONS") === "true";
14
}
15
16
export function isVerboseMode(): boolean {
17
return Deno.env.get("RUNNER_DEBUG") === "1" ||
18
Deno.env.get("QUARTO_TEST_VERBOSE") === "true";
19
}
20
21
// GitHub Actions Workflow Command Escaping
22
// See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions
23
export function escapeData(s: string): string {
24
return s
25
.replace(/%/g, "%25")
26
.replace(/\r/g, "%0D")
27
.replace(/\n/g, "%0A");
28
}
29
30
export function escapeProperty(s: string): string {
31
return s
32
.replace(/%/g, "%25")
33
.replace(/\r/g, "%0D")
34
.replace(/\n/g, "%0A")
35
.replace(/:/g, "%3A")
36
.replace(/,/g, "%2C");
37
}
38
39
// GitHub Actions Annotations
40
export interface AnnotationProperties {
41
file?: string;
42
line?: number;
43
endLine?: number;
44
title?: string;
45
}
46
47
function formatProperties(props: AnnotationProperties): string {
48
const parts: string[] = [];
49
if (props.file !== undefined) {
50
parts.push(`file=${escapeProperty(props.file)}`);
51
}
52
if (props.line !== undefined) parts.push(`line=${props.line}`);
53
if (props.endLine !== undefined) parts.push(`endLine=${props.endLine}`);
54
if (props.title !== undefined) {
55
parts.push(`title=${escapeProperty(props.title)}`);
56
}
57
return parts.length > 0 ? " " + parts.join(",") : "";
58
}
59
60
export function error(
61
message: string,
62
properties?: AnnotationProperties,
63
): void {
64
if (!isGitHubActions()) {
65
console.log(message);
66
return;
67
}
68
const props = properties ? formatProperties(properties) : "";
69
console.log(`::error${props}::${escapeData(message)}`);
70
}
71
72
export function warning(
73
message: string,
74
properties?: AnnotationProperties,
75
): void {
76
if (!isGitHubActions()) {
77
console.log(message);
78
return;
79
}
80
const props = properties ? formatProperties(properties) : "";
81
console.log(`::warning${props}::${escapeData(message)}`);
82
}
83
84
export function notice(
85
message: string,
86
properties?: AnnotationProperties,
87
): void {
88
if (!isGitHubActions()) {
89
console.log(message);
90
return;
91
}
92
const props = properties ? formatProperties(properties) : "";
93
console.log(`::notice${props}::${escapeData(message)}`);
94
}
95
96
// GitHub Actions Log Grouping
97
export function startGroup(title: string): void {
98
if (!isGitHubActions()) return;
99
console.log(`::group::${escapeData(title)}`);
100
}
101
102
export function endGroup(): void {
103
if (!isGitHubActions()) return;
104
console.log("::endgroup::");
105
}
106
107
export function withGroup<T>(title: string, fn: () => T): T {
108
if (!isGitHubActions()) {
109
console.log(title);
110
return fn();
111
}
112
startGroup(title);
113
try {
114
return fn();
115
} finally {
116
endGroup();
117
}
118
}
119
120
export async function withGroupAsync<T>(
121
title: string,
122
fn: () => Promise<T>,
123
): Promise<T> {
124
if (!isGitHubActions()) {
125
console.log(title);
126
return await fn();
127
}
128
startGroup(title);
129
try {
130
return await fn();
131
} finally {
132
endGroup();
133
}
134
}
135
136
// Legacy group function for backward compatibility and alia
137
export async function group<T>(
138
title: string,
139
fn: () => Promise<T>,
140
): Promise<T> {
141
return await withGroupAsync(title, fn);
142
}
143
144
// GitHub API
145
146
// A Github Release for a Github Repo
147
148
// Look up the latest release for a Github Repo
149
export async function getLatestRelease(repo: string): Promise<GitHubRelease> {
150
const url = `https://api.github.com/repos/${repo}/releases/latest`;
151
const headers = Deno.env.get("GH_TOKEN")
152
? { headers: { Authorization: "Bearer " + Deno.env.get("GH_TOKEN") } }
153
: undefined;
154
const response = await fetch(url, headers);
155
if (response.status !== 200) {
156
throw new Error(
157
`Unable to determine latest release for ${repo}\n${response.status} - ${response.statusText}`,
158
);
159
} else {
160
return response.json();
161
}
162
}
163
164