dsh-github-app 0.0.0-stage → 0.0.2
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/LICENSE +21 -0
- package/README.md +149 -2
- package/api.d.ts +5 -0
- package/api.mjs +258 -0
- package/atomicFile.d.ts +14 -0
- package/atomicFile.mjs +53 -0
- package/client/client.js +3 -0
- package/constants.d.ts +20 -0
- package/constants.mjs +20 -0
- package/contracts.d.ts +40 -0
- package/cordis.patch.yml +5 -0
- package/declaration.d.ts +4 -0
- package/fence.d.ts +16 -0
- package/fence.mjs +136 -0
- package/github.d.ts +117 -0
- package/github.mjs +659 -0
- package/icon.png +0 -0
- package/index.d.ts +19 -0
- package/index.mjs +46 -0
- package/keyCrypto.d.ts +25 -0
- package/keyCrypto.mjs +132 -0
- package/locale/en.json +5 -0
- package/package.json +52 -3
- package/passphrase.d.ts +10 -0
- package/passphrase.mjs +18 -0
- package/secureFile.d.ts +21 -0
- package/secureFile.mjs +56 -0
- package/settings.d.ts +45 -0
- package/settings.mjs +127 -0
- package/time.d.ts +7 -0
- package/time.mjs +15 -0
- package/uninstall.d.ts +56 -0
- package/uninstall.mjs +124 -0
- package/utils/errorMessage.d.ts +1 -0
- package/utils/errorMessage.mjs +5 -0
package/fence.mjs
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { MAX_BODY_BYTES } from "./constants.mjs";
|
|
2
|
+
// src/fence.ts
|
|
3
|
+
const PAYLOAD_TOO_LARGE = Symbol("payload-too-large");
|
|
4
|
+
function parseAuthority(authority) {
|
|
5
|
+
if (typeof authority !== "string") return void 0;
|
|
6
|
+
const value = authority.trim();
|
|
7
|
+
if (value === "" || /[/?#@\\\s]/.test(value)) return void 0;
|
|
8
|
+
try {
|
|
9
|
+
const url = new URL(`http://${value}`);
|
|
10
|
+
if (url.username !== "" || url.password !== "" || url.pathname !== "/") return void 0;
|
|
11
|
+
return url;
|
|
12
|
+
} catch {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function isLoopbackHostname(hostname) {
|
|
17
|
+
if (typeof hostname !== "string" || hostname === "") return false;
|
|
18
|
+
let name = hostname.toLowerCase();
|
|
19
|
+
if (name.startsWith("[") && name.endsWith("]")) name = name.slice(1, -1);
|
|
20
|
+
if (name === "localhost" || name === "::1") return true;
|
|
21
|
+
const parts = (name.startsWith("::ffff:") ? name.slice(7) : name).split(".");
|
|
22
|
+
return parts.length === 4 && parts[0] === "127" && parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255);
|
|
23
|
+
}
|
|
24
|
+
function canonicalFrom(url, authority) {
|
|
25
|
+
const explicitPort = /:(\d+)$/.exec(authority.trim());
|
|
26
|
+
return explicitPort === null ? url.hostname : `${url.hostname}:${explicitPort[1]}`;
|
|
27
|
+
}
|
|
28
|
+
function canonicalAuthority(authority) {
|
|
29
|
+
const url = parseAuthority(authority);
|
|
30
|
+
return url === void 0 ? void 0 : canonicalFrom(url, authority);
|
|
31
|
+
}
|
|
32
|
+
function isTrustedAuthority(authority, hostUrl, trustedHosts) {
|
|
33
|
+
const requestAuthority = canonicalAuthority(authority) || hostUrl.hostname;
|
|
34
|
+
return trustedHosts.some((entry) => {
|
|
35
|
+
const url = parseAuthority(entry);
|
|
36
|
+
if (url === void 0) return false;
|
|
37
|
+
const canonical = canonicalFrom(url, entry);
|
|
38
|
+
return canonical !== url.hostname ? canonical === requestAuthority : url.hostname === hostUrl.hostname;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function buildTrustedHosts(entries, warn) {
|
|
42
|
+
const trusted = [];
|
|
43
|
+
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
44
|
+
if (typeof entry !== "string") {
|
|
45
|
+
if (warn) warn("trusted host entry is not a string");
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const canonical = canonicalAuthority(entry);
|
|
49
|
+
if (canonical === void 0 || canonical !== entry.trim().toLowerCase()) {
|
|
50
|
+
if (warn) warn(`entry ${JSON.stringify(entry)} is not a bare host[:port] authority`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
trusted.push(entry.trim());
|
|
54
|
+
}
|
|
55
|
+
return trusted;
|
|
56
|
+
}
|
|
57
|
+
function headersOf(req) {
|
|
58
|
+
return req !== null && typeof req === "object" && "headers" in req && req.headers !== null && typeof req.headers === "object" ? req.headers : {};
|
|
59
|
+
}
|
|
60
|
+
function isTrustedApiRequest(req, trustedHosts = []) {
|
|
61
|
+
if (req === null || typeof req !== "object") return false;
|
|
62
|
+
const headers = headersOf(req);
|
|
63
|
+
const host = typeof headers.host === "string" ? headers.host : void 0;
|
|
64
|
+
if (host === void 0) return false;
|
|
65
|
+
const hostUrl = parseAuthority(host);
|
|
66
|
+
if (hostUrl === void 0) return false;
|
|
67
|
+
if (!isLoopbackHostname(hostUrl.hostname) && !isTrustedAuthority(host, hostUrl, trustedHosts)) return false;
|
|
68
|
+
const site = headers["sec-fetch-site"];
|
|
69
|
+
if (typeof site === "string" && site.toLowerCase() === "cross-site") return false;
|
|
70
|
+
const origin = headers.origin;
|
|
71
|
+
if (origin === void 0 || origin === "") return true;
|
|
72
|
+
if (typeof origin !== "string") return false;
|
|
73
|
+
try {
|
|
74
|
+
return new URL(origin).host === hostUrl.host;
|
|
75
|
+
} catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Whether a request targets a loopback authority.
|
|
81
|
+
*
|
|
82
|
+
* @param req incoming request carrying a Host header.
|
|
83
|
+
* @returns true for localhost, ::1 and 127.0.0.0/8 authorities.
|
|
84
|
+
*/
|
|
85
|
+
function isLoopbackRequest(req) {
|
|
86
|
+
if (req === null || typeof req !== "object") return false;
|
|
87
|
+
const headers = headersOf(req);
|
|
88
|
+
const host = typeof headers.host === "string" ? headers.host : void 0;
|
|
89
|
+
if (host === void 0) return false;
|
|
90
|
+
const authority = parseAuthority(host);
|
|
91
|
+
return authority !== void 0 && isLoopbackHostname(authority.hostname);
|
|
92
|
+
}
|
|
93
|
+
function readJsonBody(req, maxBytes = MAX_BODY_BYTES) {
|
|
94
|
+
return new Promise((resolve) => {
|
|
95
|
+
const chunks = [];
|
|
96
|
+
let size = 0;
|
|
97
|
+
let settled = false;
|
|
98
|
+
req.on("data", (chunk) => {
|
|
99
|
+
if (settled) return;
|
|
100
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk), "utf8");
|
|
101
|
+
size += buffer.length;
|
|
102
|
+
if (size > maxBytes) {
|
|
103
|
+
settled = true;
|
|
104
|
+
req.pause();
|
|
105
|
+
resolve(PAYLOAD_TOO_LARGE);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
chunks.push(buffer);
|
|
109
|
+
});
|
|
110
|
+
req.on("end", () => {
|
|
111
|
+
if (settled) return;
|
|
112
|
+
settled = true;
|
|
113
|
+
try {
|
|
114
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
115
|
+
resolve(raw === "" ? null : JSON.parse(raw));
|
|
116
|
+
} catch {
|
|
117
|
+
resolve(null);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
req.on("error", () => {
|
|
121
|
+
if (settled) return;
|
|
122
|
+
settled = true;
|
|
123
|
+
resolve(null);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function writeJson(res, status, value) {
|
|
128
|
+
if (!res || res.writableEnded || res.destroyed) return;
|
|
129
|
+
const body = JSON.stringify(value);
|
|
130
|
+
res.writeHead(status, {
|
|
131
|
+
"content-type": "application/json",
|
|
132
|
+
"cache-control": "no-store"
|
|
133
|
+
});
|
|
134
|
+
res.end(body);
|
|
135
|
+
}
|
|
136
|
+
export { PAYLOAD_TOO_LARGE, buildTrustedHosts, canonicalAuthority, isLoopbackHostname, isLoopbackRequest, isTrustedApiRequest, parseAuthority, readJsonBody, writeJson };
|
package/github.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { GitIdentity, ToolStatus } from './contracts';
|
|
2
|
+
import type { GithubSettings } from './settings';
|
|
3
|
+
export interface RunOptions {
|
|
4
|
+
cwd?: string;
|
|
5
|
+
env?: NodeJS.ProcessEnv;
|
|
6
|
+
/** Variables to drop from the inherited environment before spawning. */
|
|
7
|
+
unset?: string[];
|
|
8
|
+
timeout?: number;
|
|
9
|
+
input?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface RunResult {
|
|
12
|
+
code: number;
|
|
13
|
+
stdout: string;
|
|
14
|
+
stderr: string;
|
|
15
|
+
error: Error | null;
|
|
16
|
+
}
|
|
17
|
+
export interface InstallResult {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
version?: string | null;
|
|
20
|
+
skipped?: boolean;
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface InstallGhResult extends InstallResult {
|
|
24
|
+
gh: InstallResult;
|
|
25
|
+
git: InstallResult;
|
|
26
|
+
}
|
|
27
|
+
export interface AuthStatus {
|
|
28
|
+
loggedIn: boolean;
|
|
29
|
+
gitConfigured: boolean;
|
|
30
|
+
hostname: string;
|
|
31
|
+
output?: string;
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface ConnectResult {
|
|
35
|
+
ok: boolean;
|
|
36
|
+
error?: string;
|
|
37
|
+
auth?: AuthStatus;
|
|
38
|
+
}
|
|
39
|
+
export declare function getGhInfo(): Promise<ToolStatus>;
|
|
40
|
+
export declare function getGitInfo(): Promise<ToolStatus>;
|
|
41
|
+
/**
|
|
42
|
+
* Decide whether `gh auth status` output describes a usable authentication.
|
|
43
|
+
*
|
|
44
|
+
* `gh` exits 0 as soon as *one* configured account is usable, so judging the
|
|
45
|
+
* merged output as a whole reports a false "disconnected" whenever an unusable
|
|
46
|
+
* entry sits next to a working one. The entry marked `Active account: true` —
|
|
47
|
+
* the credential `gh` and git operations actually use — decides the verdict.
|
|
48
|
+
* Other entries are only consulted when no such marker is present; when the
|
|
49
|
+
* marker is present but its status line is unrecognisable (a future `gh`
|
|
50
|
+
* re-wording), the verdict stays `false` instead of falling back to another
|
|
51
|
+
* account, so an unknown format can never claim "Connected".
|
|
52
|
+
*
|
|
53
|
+
* @param output merged stdout+stderr of `gh auth status`.
|
|
54
|
+
* @returns true when the active (or only) account is authenticated.
|
|
55
|
+
*/
|
|
56
|
+
export declare function isLoggedInOutput(output: unknown): boolean;
|
|
57
|
+
export declare function getAuthStatus(hostname?: string): Promise<AuthStatus>;
|
|
58
|
+
/** Tool the settings section can install automatically. */
|
|
59
|
+
export type InstallableTool = 'gh' | 'git';
|
|
60
|
+
/**
|
|
61
|
+
* One supported package manager and the commands that install a tool with it.
|
|
62
|
+
*
|
|
63
|
+
* `install` is a prefix: the resolved package name is appended as the last
|
|
64
|
+
* argument, so managers that put the name after flags (`winget --id`) can use
|
|
65
|
+
* the same shape.
|
|
66
|
+
*/
|
|
67
|
+
export interface PackageManager {
|
|
68
|
+
/** Executable to spawn. */
|
|
69
|
+
command: string;
|
|
70
|
+
/** Arguments that exit 0 when the manager is usable. */
|
|
71
|
+
probe: string[];
|
|
72
|
+
/** Arguments that refresh the package index, or null when not needed. */
|
|
73
|
+
update: string[] | null;
|
|
74
|
+
/** Arguments to install a package (package name appended). */
|
|
75
|
+
install: string[];
|
|
76
|
+
/** Package name when it differs from the tool name. */
|
|
77
|
+
packageName?: Partial<Record<InstallableTool, string>>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Package managers available for a platform, in probe order.
|
|
81
|
+
*
|
|
82
|
+
* @param platform target platform; defaults to the host.
|
|
83
|
+
* @returns the manager list, empty when the platform has no installer.
|
|
84
|
+
*/
|
|
85
|
+
export declare function packageManagers(platform?: NodeJS.Platform): PackageManager[];
|
|
86
|
+
/**
|
|
87
|
+
* Commands needed to install one tool with a known package manager.
|
|
88
|
+
*
|
|
89
|
+
* @param manager manager to use.
|
|
90
|
+
* @param tool tool to install.
|
|
91
|
+
* @returns ordered commands; an install failure aborts the sequence.
|
|
92
|
+
*/
|
|
93
|
+
export declare function installCommands(manager: PackageManager, tool: InstallableTool): Array<{
|
|
94
|
+
command: string;
|
|
95
|
+
args: string[];
|
|
96
|
+
}>;
|
|
97
|
+
export declare function installGit(): Promise<InstallResult>;
|
|
98
|
+
export declare function installGh(): Promise<InstallGhResult>;
|
|
99
|
+
/**
|
|
100
|
+
* Exchange the GitHub App private key for an installation token.
|
|
101
|
+
*
|
|
102
|
+
* The key file is encrypted at rest, so the passphrase is required on every
|
|
103
|
+
* call: the decrypted PEM lives only for the duration of this operation and is
|
|
104
|
+
* never cached in the server process.
|
|
105
|
+
*
|
|
106
|
+
* @param settings persisted App ID, installation ID and key location.
|
|
107
|
+
* @param passphrase passphrase protecting the key file.
|
|
108
|
+
* @returns the connection outcome and refreshed auth status.
|
|
109
|
+
*/
|
|
110
|
+
export declare function connect(settings?: Partial<GithubSettings> | null, passphrase?: unknown): Promise<ConnectResult>;
|
|
111
|
+
export interface DisconnectResult {
|
|
112
|
+
ok: boolean;
|
|
113
|
+
error?: string;
|
|
114
|
+
}
|
|
115
|
+
export declare function disconnect(): Promise<DisconnectResult>;
|
|
116
|
+
export declare function getGitIdentity(): Promise<GitIdentity>;
|
|
117
|
+
export declare function setGitIdentity(name: unknown, email: unknown): Promise<GitIdentity>;
|