Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  gh-read.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import { execFileSync, spawnSync } from "node:child_process";
import { createPrivateKey, createSign } from "node:crypto";
import { readFileSync } from "node:fs";
import { pathToFileURL } from "node:url";

const APP_ID_ENV = "OPENCLAW_GH_READ_APP_ID";
const KEY_FILE_ENV = "OPENCLAW_GH_READ_PRIVATE_KEY_FILE";
const INSTALLATION_ID_ENV = "OPENCLAW_GH_READ_INSTALLATION_ID";
const PERMISSIONS_ENV = "OPENCLAW_GH_READ_PERMISSIONS";
const API_VERSION = "2022-11-28";
const DEFAULT_READ_PERMISSION_KEYS = [
  "actions",
  "checks",
  "contents",
  "issues",
  "metadata",
  "pull_requests",
  "statuses",
] as const;

type GrantedPermissionLevel = "read" | "write" | "admin" | null | undefined;
type RequestedPermissionLevel = "read" | "write";
type GrantedPermissions = Record<string, GrantedPermissionLevel>;
type RequestedPermissions = Record<string, RequestedPermissionLevel>;

type InstallationResponse = {
  id: number;
  permissions?: GrantedPermissions;
};

type AccessTokenResponse = {
  token: string;
};

export function parseRepoArg(args: string[]): string | null {
  for (let i = 0; i < args.length; i += 1) {
    const arg = args[i];
    if (arg === "-R" || arg === "--repo") {
      return normalizeRepo(args[i + 1] ?? null);
    }
    if (arg.startsWith("--repo=")) {
      return normalizeRepo(arg.slice("--repo=".length));
    }
    if (arg.startsWith("-R") && arg.length > 2) {
      return normalizeRepo(arg.slice(2));
    }
  }
  return null;
}

export function normalizeRepo(value: string | null | undefined): string | null {
  const trimmed = value?.trim();
  if (!trimmed) {
    return null;
  }

  const withoutProtocol = trimmed.replace(/^[a-z]+:\/\//i, "");
  const withoutHost = withoutProtocol.replace(/^(?:[^@/]+@)?github\.com[:/]/i, "");
  const normalized = withoutHost.replace(/\.git$/i, "").replace(/^\/+|\/+$/g, "");
  const parts = normalized.split("/").filter(Boolean);
  if (parts.length < 2) {
    return null;
  }

  return `${parts[parts.length - 2]}/${parts[parts.length - 1]}`;
}

export function parsePermissionKeys(raw: string | null | undefined): string[] {
  const trimmed = raw?.trim();
  if (!trimmed) {
    return [...DEFAULT_READ_PERMISSION_KEYS];
  }

  return trimmed
    .split(",")
    .map((value) => value.trim())
    .filter(Boolean);
}

export function buildReadPermissions(
  grantedPermissions: GrantedPermissions | null | undefined,
  requestedKeys: readonly string[],
): RequestedPermissions {
  const permissions: RequestedPermissions = {};
  for (const key of requestedKeys) {
    const granted = grantedPermissions?.[key];
    if (granted === "read" || granted === "write") {
      permissions[key] = "read";
    }
  }
  return permissions;
}

function isMainModule() {
  const entry = process.argv[1];
  return entry ? import.meta.url === pathToFileURL(entry).href : false;
}

function fail(message: string): never {
  console.error(`gh-read: ${message}`);
  process.exit(1);
}

function readRequiredEnv(name: string): string {
  const value = process.env[name]?.trim();
  if (!value) {
    fail(`missing ${name}`);
  }
  return value;
}

function resolveRepo(args: string[]): string | null {
  const fromArgs = parseRepoArg(args);
  if (fromArgs) {
    return fromArgs;
  }

  const fromEnv = normalizeRepo(process.env.GH_REPO);
  if (fromEnv) {
    return fromEnv;
  }

  try {
    const remote = execFileSync("git", ["config", "--get", "remote.origin.url"], {
      encoding: "utf8",
      stdio: ["ignore", "pipe", "ignore"],
    }).trim();
    return normalizeRepo(remote);
  } catch {
    return null;
  }
}

function base64UrlEncode(value: string | Uint8Array) {
  return Buffer.from(value)
    .toString("base64")
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/g, "");
}

function createAppJwt(appId: string, privateKeyPem: string) {
  const now = Math.floor(Date.now() / 1000);
  const header = base64UrlEncode(JSON.stringify({ alg: "RS256", typ: "JWT" }));
  const payload = base64UrlEncode(JSON.stringify({ iat: now - 60, exp: now + 9 * 60, iss: appId }));
  const signingInput = `${header}.${payload}`;
  const signer = createSign("RSA-SHA256");
  signer.update(signingInput);
  signer.end();
  const signature = signer.sign(createPrivateKey(privateKeyPem));
  return `${signingInput}.${base64UrlEncode(signature)}`;
}

async function githubJson<T>(
  path: string,
  bearerToken: string,
  init?: {
    method?: "GET" | "POST";
    body?: unknown;
  },
): Promise<T> {
  const response = await fetch(`https://api.github.com${path}`, {
    method: init?.method ?? "GET",
    headers: {
      Accept: "application/vnd.github+json",
      Authorization: `Bearer ${bearerToken}`,
      "Content-Type": "application/json",
      "User-Agent": "openclaw-gh-read",
      "X-GitHub-Api-Version": API_VERSION,
    },
    body: init?.body === undefined ? undefined : JSON.stringify(init.body),
  });

  if (!response.ok) {
    const text = await response.text();
    fail(`${init?.method ?? "GET"} ${path} failed (${response.status}): ${text}`);
  }

  return (await response.json()) as T;
}

async function resolveInstallation(
  appJwt: string,
  repo: string | null,
): Promise<InstallationResponse> {
  const installationId = process.env[INSTALLATION_ID_ENV]?.trim();
  if (repo) {
    return githubJson<InstallationResponse>(`/repos/${repo}/installation`, appJwt);
  }
  if (installationId) {
    return githubJson<InstallationResponse>(`/app/installations/${installationId}`, appJwt);
  }
  fail(
    `missing repo context; pass -R owner/repo, set GH_REPO, or set ${INSTALLATION_ID_ENV} for a direct installation lookup`,
  );
  throw new Error("unreachable");
}

async function createInstallationToken(
  appJwt: string,
  installation: InstallationResponse,
  repo: string | null,
): Promise<string> {
  const repoName = repo?.split("/")[1] ?? null;
  const requestedPermissionKeys = parsePermissionKeys(process.env[PERMISSIONS_ENV]);
  const permissions = buildReadPermissions(installation.permissions, requestedPermissionKeys);
  const body: {
    repositories?: string[];
    permissions?: RequestedPermissions;
  } = {};

  if (repoName) {
    body.repositories = [repoName];
  }
  if (Object.keys(permissions).length > 0) {
    body.permissions = permissions;
  }

  const tokenResponse = await githubJson<AccessTokenResponse>(
    `/app/installations/${installation.id}/access_tokens`,
    appJwt,
    { method: "POST", body },
  );
  return tokenResponse.token;
}

async function main() {
  if (process.argv.length <= 2) {
    fail(
      "usage: scripts/gh-read <gh args...>\nset OPENCLAW_GH_READ_APP_ID and OPENCLAW_GH_READ_PRIVATE_KEY_FILE first",
    );
  }

  const ghArgs = process.argv.slice(2);
  const appId = readRequiredEnv(APP_ID_ENV);
  const privateKeyPath = readRequiredEnv(KEY_FILE_ENV);
  const privateKeyPem = readFileSync(privateKeyPath, "utf8");
  const repo = resolveRepo(ghArgs);
  const appJwt = createAppJwt(appId, privateKeyPem);
  const installation = await resolveInstallation(appJwt, repo);
  const token = await createInstallationToken(appJwt, installation, repo);
  const child = spawnSync("gh", ghArgs, {
    stdio: "inherit",
    env: {
      ...process.env,
      GH_TOKEN: token,
      GITHUB_TOKEN: token,
    },
  });

  if (child.error) {
    fail(child.error.message);
  }

  process.exit(child.status ?? 1);
}

if (isMainModule()) {
  await main();
}

¤ Dauer der Verarbeitung: 0.28 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge