Skip to content

Commit 6775338

Browse files
committed
wip: github links in stacks
1 parent 52f1ecd commit 6775338

File tree

6 files changed

+121
-4
lines changed

6 files changed

+121
-4
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"react": "^18.3.1",
1616
"source-map": "^0.7.4",
1717
"swc-plugin-coverage-instrument": "0.0.20",
18+
"terminal-link": "^3.0.0",
1819
"widest-line": "^5.0.0"
1920
},
2021
"devDependencies": {
@@ -59,4 +60,4 @@
5960
"main": "./dist/index.js",
6061
"module": "./dist/index.js",
6162
"types": "./dist/index.d.ts"
62-
}
63+
}

src/error/GitHubResolver.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { pathToFileURL } from 'node:url'
2+
import { SourceLocation, SourceLocationResolver } from './ErrorStackResolver'
3+
import { spawn } from 'node:child_process'
4+
5+
export class GitHubResolver implements SourceLocationResolver {
6+
constructor(
7+
readonly GITHUB_WORKSPACE = process.env.GITHUB_WORKSPACE,
8+
readonly GITHUB_SHA = process.env.GITHUB_SHA,
9+
readonly GITHUB_REPOSITORY = process.env.GITHUB_REPOSITORY,
10+
readonly GITHUB_SERVER_URL = process.env.GITHUB_SERVER_URL,
11+
) {
12+
if (GITHUB_WORKSPACE) {
13+
this.workspaceUrl = String(pathToFileURL(GITHUB_WORKSPACE))
14+
if (!this.workspaceUrl.endsWith('/')) {
15+
this.workspaceUrl += '/'
16+
}
17+
}
18+
if (GITHUB_SHA && GITHUB_REPOSITORY && GITHUB_SERVER_URL) {
19+
this.resolvedWorkspaceUrl = `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/blob/${GITHUB_SHA}/`
20+
}
21+
}
22+
23+
protected workspaceUrl?: string
24+
protected resolvedWorkspaceUrl?: string
25+
protected treeObjects?: Promise<string[]>
26+
27+
async resolve(loc: SourceLocation): Promise<SourceLocation | undefined> {
28+
if (!this.workspaceUrl || !this.resolvedWorkspaceUrl || !loc.file?.startsWith(this.workspaceUrl)) {
29+
return undefined
30+
}
31+
const path = loc.file.substring(this.workspaceUrl.length)
32+
33+
if (!this.treeObjects) {
34+
this.treeObjects = this.getGitTreeObjects()
35+
}
36+
const tree = await this.treeObjects
37+
if (tree.includes(path)) {
38+
return {...loc,
39+
url: this.resolvedWorkspaceUrl + path + (loc.position ? `#L${loc.position.line}` : ''),
40+
}
41+
}
42+
}
43+
44+
protected getGitTreeObjects(): Promise<string[]> {
45+
if (!this.GITHUB_WORKSPACE) {
46+
return Promise.reject(`GITHUB_WORKSPACE is not set.`)
47+
}
48+
49+
return new Promise((res, rej) => {
50+
const child = spawn('git', [
51+
'ls-tree',
52+
'--name-only',
53+
'-r',
54+
'HEAD',
55+
], {
56+
cwd: this.GITHUB_WORKSPACE,
57+
stdio: ['ignore', 'pipe', 'pipe'],
58+
})
59+
let out = '', err = ''
60+
child.stdout.on('data', c => {
61+
out += String(c)
62+
})
63+
child.stderr.on('data', c => {
64+
err += String(c)
65+
})
66+
child.on('exit', (code, signal) => {
67+
if (code || signal) {
68+
rej(`Could not list tree objects:\n` + err)
69+
} else {
70+
const list = out.split('\n')
71+
res(list)
72+
}
73+
})
74+
})
75+
}
76+
}

src/error/XError.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ function* genStackEntries(stack: string) {
9494

9595
export type SourceLocation = {
9696
file?: string
97+
url?: string
9798
position?: Position
9899
name?: string
99100
}
@@ -109,11 +110,17 @@ export class StackEntry {
109110
}
110111
readonly resolved
111112

112-
toString() {
113+
toString(
114+
useUrl = false,
115+
) {
113116
const e = this.resolved.get()
114117

115118
const pos = e.position ? `:${e.position.line}:${e.position.column}` : ''
116-
if (e.name && e.file) {
119+
if (useUrl && e.url && e.name) {
120+
return `${e.name} (${e.url}${pos})`
121+
} else if (useUrl && e.url) {
122+
return `${e.url}${pos}`
123+
} else if (e.name && e.file) {
117124
return `${e.name} (${e.file}${pos})`
118125
} else if (e.file) {
119126
return `${e.file}${pos}`

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { TesterCli } from './ui/cli/TesterCli'
2020
import { ErrorStackResolver, SourceLocation, SourceLocationResolver } from './error/ErrorStackResolver'
2121
import { SourceMapResolver } from './error/SourceMapResolver'
2222
import { PathResolver } from './error/PathResolver'
23+
import { GitHubResolver } from './error/GitHubResolver'
2324

2425
export type { TestContext } from './runner/TestContext'
2526

@@ -373,6 +374,7 @@ export async function setupToolboxTester(
373374
const errorStackResolver = new ErrorStackResolver([
374375
new SourceMapResolver(String(await fileServer.url), fileServer.provider),
375376
runner,
377+
new GitHubResolver(),
376378
new PathResolver(String(pathToFileURL(projectDir)), ''),
377379
])
378380

test/printError.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
test('some failing test', () => {
2+
throw new Error('some error')
3+
})

yarn.lock

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,13 @@ ansi-colors@^4.1.3:
685685
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
686686
integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==
687687

688+
ansi-escapes@^5.0.0:
689+
version "5.0.0"
690+
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-5.0.0.tgz#b6a0caf0eef0c41af190e9a749e0c00ec04bb2a6"
691+
integrity sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==
692+
dependencies:
693+
type-fest "^1.0.2"
694+
688695
ansi-escapes@^7.0.0:
689696
version "7.0.0"
690697
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.0.0.tgz#00fc19f491bbb18e1d481b97868204f92109bfe7"
@@ -3231,13 +3238,21 @@ supports-color@^5.3.0:
32313238
dependencies:
32323239
has-flag "^3.0.0"
32333240

3234-
supports-color@^7.1.0:
3241+
supports-color@^7.0.0, supports-color@^7.1.0:
32353242
version "7.2.0"
32363243
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
32373244
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
32383245
dependencies:
32393246
has-flag "^4.0.0"
32403247

3248+
supports-hyperlinks@^2.2.0:
3249+
version "2.3.0"
3250+
resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624"
3251+
integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==
3252+
dependencies:
3253+
has-flag "^4.0.0"
3254+
supports-color "^7.0.0"
3255+
32413256
supports-preserve-symlinks-flag@^1.0.0:
32423257
version "1.0.0"
32433258
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
@@ -3266,6 +3281,14 @@ tar-stream@^3.1.5:
32663281
fast-fifo "^1.2.0"
32673282
streamx "^2.15.0"
32683283

3284+
terminal-link@^3.0.0:
3285+
version "3.0.0"
3286+
resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-3.0.0.tgz#91c82a66b52fc1684123297ce384429faf72ac5c"
3287+
integrity sha512-flFL3m4wuixmf6IfhFJd1YPiLiMuxEc8uHRM1buzIeZPm22Au2pDqBJQgdo7n1WfPU1ONFGv7YDwpFBmHGF6lg==
3288+
dependencies:
3289+
ansi-escapes "^5.0.0"
3290+
supports-hyperlinks "^2.2.0"
3291+
32693292
text-table@^0.2.0:
32703293
version "0.2.0"
32713294
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@@ -3329,6 +3352,11 @@ type-fest@^0.20.2:
33293352
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
33303353
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
33313354

3355+
type-fest@^1.0.2:
3356+
version "1.4.0"
3357+
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1"
3358+
integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==
3359+
33323360
type-fest@^4.27.0:
33333361
version "4.31.0"
33343362
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.31.0.tgz#a3de630c96eb77c281b6ba2affa5dae5fb3c326c"

0 commit comments

Comments
 (0)