@tenyokjs/devdevian-cli 0.7.2-alpha.1 → 0.8.0-alpha.1
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 +6 -0
- package/VERSIONS.md +8 -1
- package/bin/devdevian.mjs +27 -2
- package/lib/github-context.mjs +113 -0
- package/lib/tui-data.mjs +2 -0
- package/lib/tui.mjs +43 -1
- package/lib/workflow.mjs +1 -0
- package/lib/workspace.mjs +50 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,6 +73,12 @@ Multiline fields show the current logical line and total line count. While editi
|
|
|
73
73
|
|
|
74
74
|
Conversation overviews and publication previews display Demo/Repository URL fields alongside any labeled links, with duplicate destinations shown once.
|
|
75
75
|
|
|
76
|
+
## GitHub review context
|
|
77
|
+
|
|
78
|
+
Code reviews have an optional **GitHub** step after Content. Change **GitHub context** from Skip to Include, optionally add a PR URL from the same repository, and inspect the complete publication preview. The repository, branch, base commit and selected path appear in both CLI and browser. Local code may differ from that base commit; metadata is author-provided and PR content/status is not synchronized.
|
|
79
|
+
|
|
80
|
+
The command equivalent is `devdevian review create --file src/retry.ts --title "Review retries" --topic web --body-file question.md --focus "Timeout handling" --github-context --pr https://github.com/owner/repo/pull/42`. The PR is optional. Context is opt-in, reads local Git only and needs a GitHub `origin` and existing HEAD. No new OAuth setup is required. See the [full guide](https://github.com/Tenyokj/devdevian/blob/main/docs/github-context.md).
|
|
81
|
+
|
|
76
82
|
## Meet Devi
|
|
77
83
|
|
|
78
84
|
Devi is the Devdevian mascot: a small ghost with a laptop and an orbit. The terminal uses colored ASCII art, so it needs no image protocol or image downloads. A compact drawing is used when space is limited; authentication codes, addresses and controls take priority over decoration.
|
package/VERSIONS.md
CHANGED
|
@@ -6,7 +6,14 @@ Every published npm artifact has a unique immutable version. We record each vers
|
|
|
6
6
|
|
|
7
7
|
## Current milestone
|
|
8
8
|
|
|
9
|
-
**0.
|
|
9
|
+
**0.8.0 begins GitHub integration** with opt-in local source context and manually linked pull requests, shared by the CLI and browser.
|
|
10
|
+
|
|
11
|
+
## 0.8.0-alpha.1 — GitHub source context
|
|
12
|
+
|
|
13
|
+
- Optional GitHub step for code reviews: explicitly include local origin, branch/detached HEAD, base commit, repository-relative path and source type; add a matching PR URL when useful. Preview and account/project-scoped drafts preserve the choice. Command clients use `--github-context` and optional `--pr`.
|
|
14
|
+
- Terminal overviews, script output and responsive browser cards share the same context. Base-commit links distinguish local working code/diffs from committed content. Metadata is author-provided; this stage does not synchronize PR content or verify ownership/status.
|
|
15
|
+
- Local read-only Git commands only, canonical GitHub origins without embedded credentials, bounded server validation and existing private/owner permission checks. Other post details survive edits; no schema migration or new OAuth permission is required.
|
|
16
|
+
- Regression coverage exercises real Git repositories, nested project roots, detached HEAD, staged patches, rejected origins/PRs, command/API/browser sharing, private reads, draft restoration and compact/mobile layouts.
|
|
10
17
|
|
|
11
18
|
## 0.7.2-alpha.1 — Visible links and multiline writing
|
|
12
19
|
|
package/bin/devdevian.mjs
CHANGED
|
@@ -23,6 +23,9 @@ import {
|
|
|
23
23
|
loadAppearance,
|
|
24
24
|
saveAppearance,
|
|
25
25
|
} from "../lib/appearance.mjs";
|
|
26
|
+
import { readGitHubContext, workspaceRoot } from "../lib/workspace.mjs";
|
|
27
|
+
import { githubContextLines } from "../lib/github-context.mjs";
|
|
28
|
+
import { conversationLinks } from "../lib/conversation-links.mjs";
|
|
26
29
|
import { renderHelp } from "../lib/help.mjs";
|
|
27
30
|
const version = packageInfo.version;
|
|
28
31
|
const options = {
|
|
@@ -36,6 +39,8 @@ const options = {
|
|
|
36
39
|
file: { type: "string" },
|
|
37
40
|
lines: { type: "string" },
|
|
38
41
|
focus: { type: "string" },
|
|
42
|
+
"github-context": { type: "boolean" },
|
|
43
|
+
pr: { type: "string" },
|
|
39
44
|
"start-line": { type: "string" },
|
|
40
45
|
"end-line": { type: "string" },
|
|
41
46
|
status: { type: "string" },
|
|
@@ -131,6 +136,8 @@ const help = `Devdevian CLI ${version} · alpha
|
|
|
131
136
|
--welcome MODE on | off (Devi greeting)
|
|
132
137
|
--no-live Disable background reads in the live workspace
|
|
133
138
|
--project PATH Root directory for the live file/diff picker (default: current directory)
|
|
139
|
+
--github-context Include local GitHub origin, branch and base commit in a review
|
|
140
|
+
--pr URL Optional matching GitHub PR URL (requires --github-context)
|
|
134
141
|
--community ID Publish to an approved private community
|
|
135
142
|
--editor Write text in $VISUAL / $EDITOR
|
|
136
143
|
--json Machine-readable output
|
|
@@ -199,6 +206,9 @@ function show(data, json, kind = "posts") {
|
|
|
199
206
|
} else if (data.title) {
|
|
200
207
|
out(`#${data.id} · ${data.kind}\n${data.title}\n\n${data.body}`);
|
|
201
208
|
if (data.code) out("\nCode:\n" + data.code);
|
|
209
|
+
for (const line of githubContextLines(data.github_context)) out(line);
|
|
210
|
+
for (const link of conversationLinks(data))
|
|
211
|
+
out(link.label + " · " + link.url);
|
|
202
212
|
} else out(JSON.stringify(data, null, 2));
|
|
203
213
|
}
|
|
204
214
|
async function confirm(payload, flags) {
|
|
@@ -364,9 +374,15 @@ async function execute(args, flags) {
|
|
|
364
374
|
}
|
|
365
375
|
if (command === "ask" || (command === "review" && args[1] === "create")) {
|
|
366
376
|
const review = command === "review";
|
|
367
|
-
let code = ""
|
|
377
|
+
let code = "",
|
|
378
|
+
file,
|
|
379
|
+
range;
|
|
380
|
+
if ((flags["github-context"] || flags.pr) && !review)
|
|
381
|
+
throw Error("GitHub context is available for review create.");
|
|
382
|
+
if (flags.pr && !flags["github-context"])
|
|
383
|
+
throw Error("--pr requires --github-context.");
|
|
368
384
|
if (review) {
|
|
369
|
-
|
|
385
|
+
file = flags.file || (await prompt("Code file"));
|
|
370
386
|
code = await boundedFile(file, 20000);
|
|
371
387
|
if (flags.lines) {
|
|
372
388
|
const m = flags.lines.match(/^(\d+):(\d+)$/);
|
|
@@ -376,6 +392,7 @@ async function execute(args, flags) {
|
|
|
376
392
|
end = positive(m[2]);
|
|
377
393
|
if (end < start || end > lines.length)
|
|
378
394
|
throw Error("The selected range is outside the file.");
|
|
395
|
+
range = { start, end };
|
|
379
396
|
code = lines.slice(start - 1, end).join("\n");
|
|
380
397
|
}
|
|
381
398
|
}
|
|
@@ -395,6 +412,14 @@ async function execute(args, flags) {
|
|
|
395
412
|
payload.code = code;
|
|
396
413
|
payload.review_focus =
|
|
397
414
|
flags.focus || (await prompt("What should reviewers focus on?"));
|
|
415
|
+
if (flags["github-context"])
|
|
416
|
+
payload.details = {
|
|
417
|
+
github_context: await readGitHubContext(
|
|
418
|
+
await workspaceRoot(flags.project || process.cwd()),
|
|
419
|
+
{ kind: "file", path: file, ...range },
|
|
420
|
+
{ pullRequest: flags.pr || "" },
|
|
421
|
+
),
|
|
422
|
+
};
|
|
398
423
|
}
|
|
399
424
|
await confirm(payload, flags);
|
|
400
425
|
const result = await api("/posts", { method: "POST", body: payload });
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Pure shared contract: used by the packaged CLI, API validation and browser.
|
|
2
|
+
const repositoryPattern =
|
|
3
|
+
/^https:\/\/github\.com\/([A-Za-z0-9][A-Za-z0-9-]{0,38})\/([A-Za-z0-9_.-]{1,100})$/;
|
|
4
|
+
export function githubRepository(remote) {
|
|
5
|
+
if (typeof remote !== "string") return null;
|
|
6
|
+
const match = remote
|
|
7
|
+
.trim()
|
|
8
|
+
.match(
|
|
9
|
+
/^(?:https:\/\/github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)([^/?#:\s]+)\/([^/?#:\s]+?)\/?$/,
|
|
10
|
+
);
|
|
11
|
+
if (!match) return null;
|
|
12
|
+
const url = `https://github.com/${match[1]}/${match[2].replace(/\.git$/, "")}`;
|
|
13
|
+
return repositoryPattern.test(url) &&
|
|
14
|
+
![".", ".."].includes(url.split("/").at(-1))
|
|
15
|
+
? url
|
|
16
|
+
: null;
|
|
17
|
+
}
|
|
18
|
+
export function normalizeGitHubContext(value) {
|
|
19
|
+
if (value === null) return null;
|
|
20
|
+
const fail = () => {
|
|
21
|
+
throw Error(
|
|
22
|
+
"Check the GitHub repository, commit, relative file path and matching PR URL.",
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) fail();
|
|
26
|
+
const {
|
|
27
|
+
repository,
|
|
28
|
+
commit,
|
|
29
|
+
branch = null,
|
|
30
|
+
path,
|
|
31
|
+
mode,
|
|
32
|
+
start_line = null,
|
|
33
|
+
end_line = null,
|
|
34
|
+
pull_request = null,
|
|
35
|
+
} = value;
|
|
36
|
+
if (
|
|
37
|
+
typeof repository !== "string" ||
|
|
38
|
+
!repositoryPattern.test(repository) ||
|
|
39
|
+
githubRepository(repository) !== repository ||
|
|
40
|
+
typeof commit !== "string" ||
|
|
41
|
+
!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(commit) ||
|
|
42
|
+
(branch !== null &&
|
|
43
|
+
(typeof branch !== "string" ||
|
|
44
|
+
!branch ||
|
|
45
|
+
branch.length > 200 ||
|
|
46
|
+
/[\s\x00-\x1f\x7f-\x9f\u202a-\u202e\u2066-\u2069]/u.test(branch))) ||
|
|
47
|
+
typeof path !== "string" ||
|
|
48
|
+
!path ||
|
|
49
|
+
path.length > 1000 ||
|
|
50
|
+
path.startsWith("/") ||
|
|
51
|
+
/[\\\x00-\x1f\x7f-\x9f\u202a-\u202e\u2066-\u2069]/u.test(path) ||
|
|
52
|
+
path.split("/").some((p) => !p || p === "." || p === "..") ||
|
|
53
|
+
!["file", "staged", "unstaged"].includes(mode)
|
|
54
|
+
)
|
|
55
|
+
fail();
|
|
56
|
+
if (
|
|
57
|
+
(start_line !== null || end_line !== null) &&
|
|
58
|
+
(mode !== "file" ||
|
|
59
|
+
!Number.isSafeInteger(start_line) ||
|
|
60
|
+
!Number.isSafeInteger(end_line) ||
|
|
61
|
+
start_line < 1 ||
|
|
62
|
+
end_line < start_line ||
|
|
63
|
+
end_line > 10000000)
|
|
64
|
+
)
|
|
65
|
+
fail();
|
|
66
|
+
let pr = null;
|
|
67
|
+
if (pull_request !== null && pull_request !== "") {
|
|
68
|
+
if (typeof pull_request !== "string") fail();
|
|
69
|
+
const match = pull_request.match(
|
|
70
|
+
/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/([1-9]\d{0,9})$/,
|
|
71
|
+
);
|
|
72
|
+
if (
|
|
73
|
+
!match ||
|
|
74
|
+
`https://github.com/${match[1]}/${match[2]}`.toLowerCase() !==
|
|
75
|
+
repository.toLowerCase()
|
|
76
|
+
)
|
|
77
|
+
fail();
|
|
78
|
+
pr = repository + "/pull/" + match[3];
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
repository,
|
|
82
|
+
branch,
|
|
83
|
+
commit,
|
|
84
|
+
path,
|
|
85
|
+
mode,
|
|
86
|
+
start_line,
|
|
87
|
+
end_line,
|
|
88
|
+
pull_request: pr,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export function githubContextLines(value) {
|
|
92
|
+
if (!value) return [];
|
|
93
|
+
let c;
|
|
94
|
+
try {
|
|
95
|
+
c = normalizeGitHubContext(value);
|
|
96
|
+
} catch {
|
|
97
|
+
return [
|
|
98
|
+
"GitHub context could not be displayed. Refresh or check it in the browser.",
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
return [
|
|
102
|
+
"GITHUB CONTEXT · provided by the author",
|
|
103
|
+
c.repository,
|
|
104
|
+
`Branch: ${c.branch || "detached HEAD"} · ${c.mode === "file" ? "Local file" : c.mode === "staged" ? "Staged patch" : "Unstaged patch"}`,
|
|
105
|
+
"File: " +
|
|
106
|
+
c.path +
|
|
107
|
+
(c.start_line ? ` · lines ${c.start_line}–${c.end_line}` : ""),
|
|
108
|
+
"Base commit: " + c.commit,
|
|
109
|
+
c.repository + "/commit/" + c.commit,
|
|
110
|
+
...(c.pull_request ? ["Pull request: " + c.pull_request] : []),
|
|
111
|
+
"Local code may differ from the base commit. GitHub content is not synchronized.",
|
|
112
|
+
];
|
|
113
|
+
}
|
package/lib/tui-data.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { loadCatalog, isCatalog } from "./catalog.mjs";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
+
import { githubContextLines } from "./github-context.mjs";
|
|
3
4
|
import { conversationLinks } from "./conversation-links.mjs";
|
|
4
5
|
export function assertSnippet(code, hash) {
|
|
5
6
|
if (
|
|
@@ -289,6 +290,7 @@ export function contentLines(route, data) {
|
|
|
289
290
|
.split("\n")
|
|
290
291
|
.map((line, index) => String(index + 1).padStart(4) + " " + line),
|
|
291
292
|
);
|
|
293
|
+
lines.push(...githubContextLines(p.github_context));
|
|
292
294
|
const links = conversationLinks(p);
|
|
293
295
|
if (links.length)
|
|
294
296
|
lines.push(
|
package/lib/tui.mjs
CHANGED
|
@@ -2,7 +2,11 @@ import React, { useState, useEffect, useMemo, useRef } from "react";
|
|
|
2
2
|
import { render, Box, Text, useApp, useInput, useWindowSize } from "ink";
|
|
3
3
|
import { randomBytes } from "node:crypto";
|
|
4
4
|
import { homedir, hostname } from "node:os";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
workspaceRoot,
|
|
7
|
+
reviewSource,
|
|
8
|
+
readGitHubContext,
|
|
9
|
+
} from "./workspace.mjs";
|
|
6
10
|
import { DeviPanel } from "./devi.mjs";
|
|
7
11
|
import { WorkspacePicker } from "./picker.mjs";
|
|
8
12
|
import { DraftStore, draftIdentity } from "./drafts.mjs";
|
|
@@ -55,6 +59,7 @@ import {
|
|
|
55
59
|
import { contextActions } from "./context-actions.mjs";
|
|
56
60
|
import { editExternal } from "./external-editor.mjs";
|
|
57
61
|
import { markdownRows } from "./rich-text.mjs";
|
|
62
|
+
import { githubContextLines } from "./github-context.mjs";
|
|
58
63
|
import { conversationLinks } from "./conversation-links.mjs";
|
|
59
64
|
import { problemMessage } from "./problems.mjs";
|
|
60
65
|
import { ReviewWorkspace, codeRange } from "./review-workspace.mjs";
|
|
@@ -177,6 +182,25 @@ function fieldsFor(form) {
|
|
|
177
182
|
max: 15000,
|
|
178
183
|
multiline: true,
|
|
179
184
|
},
|
|
185
|
+
...(form.values.kind === "review"
|
|
186
|
+
? [
|
|
187
|
+
{
|
|
188
|
+
key: "github",
|
|
189
|
+
label: "GitHub context",
|
|
190
|
+
options: [
|
|
191
|
+
["off", "Skip"],
|
|
192
|
+
["on", "Include origin, branch and base commit"],
|
|
193
|
+
],
|
|
194
|
+
hint: "Optional. Local Git metadata is included only in your publication preview.",
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
key: "pull_request",
|
|
198
|
+
label: "Pull request URL (optional)",
|
|
199
|
+
max: 1000,
|
|
200
|
+
hint: "https://github.com/owner/repo/pull/123 · same repository as origin; enable GitHub context first.",
|
|
201
|
+
},
|
|
202
|
+
]
|
|
203
|
+
: []),
|
|
180
204
|
];
|
|
181
205
|
if (form.kind === "open")
|
|
182
206
|
return [
|
|
@@ -567,6 +591,11 @@ function documentRows(route, data, width, colors) {
|
|
|
567
591
|
...markdownRows(fence + "\n" + post.code + "\n" + fence, width, colors),
|
|
568
592
|
);
|
|
569
593
|
}
|
|
594
|
+
rows.push(
|
|
595
|
+
...githubContextLines(post.github_context).flatMap((line) =>
|
|
596
|
+
wrap(line, width),
|
|
597
|
+
),
|
|
598
|
+
);
|
|
570
599
|
const links = conversationLinks(post);
|
|
571
600
|
if (links.length)
|
|
572
601
|
rows.push(
|
|
@@ -1512,6 +1541,8 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
1512
1541
|
lines: "",
|
|
1513
1542
|
review_focus: "",
|
|
1514
1543
|
demo_url: "",
|
|
1544
|
+
github: "off",
|
|
1545
|
+
pull_request: "",
|
|
1515
1546
|
},
|
|
1516
1547
|
});
|
|
1517
1548
|
});
|
|
@@ -1746,6 +1777,16 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
1746
1777
|
"Shorten the context to leave room for the Git diff source label.",
|
|
1747
1778
|
);
|
|
1748
1779
|
}
|
|
1780
|
+
if (form.values.github === "on")
|
|
1781
|
+
payload.details = {
|
|
1782
|
+
github_context: await readGitHubContext(root, source, {
|
|
1783
|
+
pullRequest: form.values.pull_request || "",
|
|
1784
|
+
}),
|
|
1785
|
+
};
|
|
1786
|
+
else if (form.values.pull_request?.trim())
|
|
1787
|
+
throw Error(
|
|
1788
|
+
"Enable GitHub context to attach a matching pull request URL, or clear the URL.",
|
|
1789
|
+
);
|
|
1749
1790
|
payload.review_focus = form.values.review_focus;
|
|
1750
1791
|
}
|
|
1751
1792
|
if (payload.kind === "showcase")
|
|
@@ -3163,6 +3204,7 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
3163
3204
|
? [payload.title || payload.name, ""]
|
|
3164
3205
|
: []),
|
|
3165
3206
|
payload.body || payload.reason || payload.description || "",
|
|
3207
|
+
...githubContextLines(payload.details?.github_context),
|
|
3166
3208
|
...conversationLinks(payload).flatMap((link) => [
|
|
3167
3209
|
"",
|
|
3168
3210
|
link.label + " · " + link.url,
|
package/lib/workflow.mjs
CHANGED
|
@@ -6,6 +6,7 @@ export function formSteps(form, fields) {
|
|
|
6
6
|
groups = [
|
|
7
7
|
["About", ["kind", "title", "topic", "community_id"]],
|
|
8
8
|
["Content", ["file", "lines", "review_focus", "demo_url", "body"]],
|
|
9
|
+
["GitHub", ["github", "pull_request"]],
|
|
9
10
|
];
|
|
10
11
|
else if (form.kind === "catalog")
|
|
11
12
|
groups =
|
package/lib/workspace.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { readdir, realpath, lstat, open } from "node:fs/promises";
|
|
|
3
3
|
import { resolve, relative, join, isAbsolute, sep } from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
|
+
import { githubRepository, normalizeGitHubContext } from "./github-context.mjs";
|
|
6
7
|
const skipped = new Set([
|
|
7
8
|
".git",
|
|
8
9
|
"node_modules",
|
|
@@ -21,7 +22,9 @@ export const sensitivePath = (path) =>
|
|
|
21
22
|
.split(/[\\/]/)
|
|
22
23
|
.some(
|
|
23
24
|
(name) =>
|
|
24
|
-
[".git", ".ssh", ".aws", ".gnupg", ".envrc"].includes(
|
|
25
|
+
[".git", ".ssh", ".aws", ".gnupg", ".envrc"].includes(
|
|
26
|
+
name.toLowerCase(),
|
|
27
|
+
) ||
|
|
25
28
|
/^\.env(?:\.|$)/i.test(name) ||
|
|
26
29
|
/\.(?:pem|key|p12|pfx|keystore)$/i.test(name) ||
|
|
27
30
|
/^(?:id_(?:rsa|ed25519|ecdsa|dsa)(?:\.pub)?|credentials(?:\.json)?|\.npmrc|\.netrc|\.git-credentials)$/i.test(
|
|
@@ -384,3 +387,49 @@ export async function reviewSource(root, source, { signal } = {}) {
|
|
|
384
387
|
kind: "file",
|
|
385
388
|
};
|
|
386
389
|
}
|
|
390
|
+
|
|
391
|
+
// Explicit opt-in only: local Git metadata, no network calls or raw remote URLs in output.
|
|
392
|
+
export async function readGitHubContext(
|
|
393
|
+
root,
|
|
394
|
+
source,
|
|
395
|
+
{ pullRequest = "", signal } = {},
|
|
396
|
+
) {
|
|
397
|
+
const file = await workspacePath(root, source.path);
|
|
398
|
+
const [remote, commit, branch, prefix] = await Promise.all([
|
|
399
|
+
git(root, ["config", "--get", "remote.origin.url"], {
|
|
400
|
+
signal,
|
|
401
|
+
max: 4096,
|
|
402
|
+
}).catch((e) => {
|
|
403
|
+
if (e.gitCode === 1)
|
|
404
|
+
throw Error(
|
|
405
|
+
"No origin remote. Skip GitHub context or configure a GitHub origin.",
|
|
406
|
+
);
|
|
407
|
+
throw e;
|
|
408
|
+
}),
|
|
409
|
+
git(root, ["rev-parse", "--verify", "HEAD"], { signal, max: 4096 }),
|
|
410
|
+
git(root, ["symbolic-ref", "--quiet", "--short", "HEAD"], {
|
|
411
|
+
signal,
|
|
412
|
+
max: 4096,
|
|
413
|
+
}).catch((e) => {
|
|
414
|
+
if (e.gitCode === 1) return "";
|
|
415
|
+
throw e;
|
|
416
|
+
}),
|
|
417
|
+
git(root, ["rev-parse", "--show-prefix"], { signal, max: 4096 }),
|
|
418
|
+
]);
|
|
419
|
+
const repository = githubRepository(remote.trim());
|
|
420
|
+
if (!repository)
|
|
421
|
+
throw Error(
|
|
422
|
+
"Origin must be a standard GitHub HTTPS or SSH URL without credentials. You can skip GitHub context.",
|
|
423
|
+
);
|
|
424
|
+
return normalizeGitHubContext({
|
|
425
|
+
repository,
|
|
426
|
+
commit: commit.trim(),
|
|
427
|
+
branch: branch.trim() || null,
|
|
428
|
+
path: prefix.replace(/\n$/, "") + relative(root, file).split(sep).join("/"),
|
|
429
|
+
mode:
|
|
430
|
+
source.kind === "diff" ? (source.staged ? "staged" : "unstaged") : "file",
|
|
431
|
+
start_line: source.start || null,
|
|
432
|
+
end_line: source.end || null,
|
|
433
|
+
pull_request: pullRequest.trim() || null,
|
|
434
|
+
});
|
|
435
|
+
}
|