dsh-github-app 0.0.0-stage → 0.0.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.
@@ -0,0 +1,4 @@
1
+ interface ImportMeta {
2
+ readonly env: Record<string, string>;
3
+ readonly require: (src?: string) => any
4
+ }
package/fence.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ export declare const PAYLOAD_TOO_LARGE: unique symbol;
3
+ export declare function parseAuthority(authority: unknown): URL | undefined;
4
+ export declare function isLoopbackHostname(hostname: unknown): boolean;
5
+ export declare function canonicalAuthority(authority: string): string | undefined;
6
+ export declare function buildTrustedHosts(entries: unknown, warn?: (message: string) => void): string[];
7
+ export declare function isTrustedApiRequest(req: unknown, trustedHosts?: string[]): boolean;
8
+ /**
9
+ * Whether a request targets a loopback authority.
10
+ *
11
+ * @param req incoming request carrying a Host header.
12
+ * @returns true for localhost, ::1 and 127.0.0.0/8 authorities.
13
+ */
14
+ export declare function isLoopbackRequest(req: unknown): boolean;
15
+ export declare function readJsonBody(req: IncomingMessage, maxBytes?: number): Promise<unknown>;
16
+ export declare function writeJson(res: ServerResponse, status: number, value: unknown): void;
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>;