{"id":51339642,"url":"https://github.com/async/github-app","last_synced_at":"2026-07-02T06:04:44.867Z","repository":{"id":365401201,"uuid":"1271917690","full_name":"async/github-app","owner":"async","description":"Reusable GitHub App, webhook, Actions bridge, and content change-set helpers for Async packages.","archived":false,"fork":false,"pushed_at":"2026-06-17T06:22:33.000Z","size":32,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-17T08:16:41.028Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/async.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-17T06:00:06.000Z","updated_at":"2026-06-17T06:22:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/async/github-app","commit_stats":null,"previous_names":["async/github-app"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/async/github-app","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/async%2Fgithub-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/async%2Fgithub-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/async%2Fgithub-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/async%2Fgithub-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/async","download_url":"https://codeload.github.com/async/github-app/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/async%2Fgithub-app/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35035005,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-02T02:00:06.368Z","response_time":173,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-02T06:04:44.200Z","updated_at":"2026-07-02T06:04:44.855Z","avatar_url":"https://github.com/async.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @async/github-app\n\nReusable GitHub integration layer for Async packages.\n\nIt supports two operating modes:\n\n- **GitHub App mode** for the normal SaaS path. Use the Async-owned app metadata by default, or pass a consumer-owned app definition with `defineGithubApp`.\n- **GitHub Actions bridge mode** for organizations that cannot approve a GitHub App installation. The repo installs a generated workflow and uses its own `GITHUB_TOKEN`.\n\nThe package is content-format agnostic. JSON, JSONC read/index support, Markdown, and MDX use the same branch, commit, pull request, webhook, and receipt machinery.\n\n## Install\n\n```bash\npnpm add @async/github-app\n```\n\nRequires Node.js 24 or newer.\n\n## Package Exports\n\n```ts\nimport {\n  asyncGithubApp,\n  createGitHubClient,\n  defineGithubApp,\n  githubAppAuth\n} from \"@async/github-app\";\n\nimport { createGithubWebhookHandler } from \"@async/github-app/server\";\nimport { renderActionsBridgeWorkflow } from \"@async/github-app/actions\";\nimport { contentMapping, renderJsonContent } from \"@async/github-app/content\";\n```\n\n## GitHub App Mode\n\nThe Async-owned app metadata is exported for product wiring:\n\n```ts\nimport { asyncGithubApp } from \"@async/github-app\";\n\nconsole.log(asyncGithubApp.installUrl);\n```\n\nUse installation auth at runtime:\n\n```ts\nimport { createGitHubClient, githubAppAuth } from \"@async/github-app\";\n\nconst auth = githubAppAuth({\n  appId: process.env.GITHUB_APP_ID,\n  privateKey: process.env.GITHUB_APP_PRIVATE_KEY,\n  installationId: process.env.GITHUB_INSTALLATION_ID\n});\n\nconst github = createGitHubClient(auth);\n\nawait github.ensureBranch({\n  repo: \"acme/site\",\n  from: \"main\",\n  branch: \"async/update-homepage\"\n});\n\nconst receipt = await github.commitChangeSet({\n  repo: \"acme/site\",\n  branch: \"async/update-homepage\",\n  baseBranch: \"main\",\n  message: \"Update homepage content\",\n  files: [\n    {\n      path: \"content/settings.json\",\n      action: \"upsert\",\n      content: renderJsonContent({ title: \"Hello\" })\n    }\n  ],\n  allowedPathGlobs: [\"content/**\"]\n});\n```\n\nDo not commit private keys, webhook secrets, installation tokens, PATs, or customer tokens. This package never ships Async-owned credentials.\n\nConsumers can bring their own app definition:\n\n```ts\nimport { defineGithubApp } from \"@async/github-app\";\n\nexport const customerApp = defineGithubApp({\n  metadata: {\n    slug: \"acme-content-app\",\n    installUrl: \"https://github.com/apps/acme-content-app/installations/new\",\n    callbackUrl: \"https://acme.example/github/callback\"\n  },\n  permissions: {\n    contents: \"write\",\n    metadata: \"read\",\n    pull_requests: \"write\"\n  }\n});\n```\n\n## Webhooks\n\n`@async/github-app/server` exports Fetch-compatible handlers that work in Workers-style runtimes and can be adapted to Node HTTP.\n\n```ts\nimport { createGithubWebhookHandler } from \"@async/github-app/server\";\n\nexport default {\n  fetch: createGithubWebhookHandler({\n    verify: { secret: process.env.GITHUB_WEBHOOK_SECRET },\n    route: {\n      push: async (event) =\u003e {\n        await queueReindex(event.payload);\n      },\n      pull_request: async (event) =\u003e {\n        await queueReindex(event.payload);\n      }\n    }\n  })\n};\n```\n\nThe handler verifies `X-Hub-Signature-256` before parsing trusted JSON, limits body size, and treats duplicate GitHub delivery IDs as idempotent.\n\n## GitHub Actions Bridge Mode\n\nFor organizations that cannot approve a GitHub App install, render a repo-local workflow:\n\n```ts\nimport { renderActionsBridgeWorkflow } from \"@async/github-app/actions\";\n\nconst yaml = renderActionsBridgeWorkflow({\n  asyncEndpoint: \"${{ vars.ASYNC_PROJECT_URL }}\",\n  branchPrefix: \"async/bridge/\",\n  allowedPathGlobs: [\"pipeline.ts\", \"package.json\", \"docs/**\"]\n});\n```\n\nPrefer `@async/pipeline` generated workflows for new repos so workflow triggers,\npermissions, action pins, locks, and secret routing stay centrally managed. The\nstandalone renderer remains available for compatibility.\n\nThe generated workflow:\n\n- supports `workflow_dispatch`\n- runs on a documented five-minute schedule by default\n- requests `contents: write` and `pull-requests: write`\n- uses `ASYNC_PROJECT_TOKEN` plus repo-local `GITHUB_TOKEN`\n- pulls approved change sets from Async\n- enforces configured branch-prefix and allowed-path constraints\n- commits branches and optionally opens PRs\n- posts lease-aware receipts back to Async\n\nRepo setting required for PR creation: enable “Allow GitHub Actions to create and approve pull requests”. If that is unavailable, Async can use branch-only mode and let a human open the PR.\n\nExternal dispatch is optional. Async can trigger `workflow_dispatch` only when the customer provides a token with Actions write permission. Without that token, schedule or manual run is the fallback.\n\n## Content Helpers\n\nJSON writes are canonical and stable:\n\n```ts\nimport { renderJsonContent } from \"@async/github-app/content\";\n\nconst content = renderJsonContent({ enabled: true });\n```\n\nJSONC is readable by default, but writes are opt-in because comments and formatting cannot be preserved safely:\n\n```ts\nimport { parseJsoncContent } from \"@async/github-app/content\";\n\nconst value = parseJsoncContent(`{\n  // allowed on read\n  \"enabled\": true,\n}`);\n```\n\nMarkdown and MDX helpers preserve body text and use frontmatter for record fields:\n\n```ts\nimport { parseMarkdownRecord, renderMarkdownRecord } from \"@async/github-app/content\";\n\nconst record = parseMarkdownRecord(\"---\\ntitle: \\\"Hello\\\"\\n---\\nBody text\\n\");\nconst file = renderMarkdownRecord(record);\n```\n\nGeneric mappings let future `@async/db` integration point resources at files without hard-coding formats into GitHub auth:\n\n```ts\nimport { contentMapping } from \"@async/github-app/content\";\n\nconst posts = contentMapping({\n  resource: \"posts\",\n  pattern: \"content/posts/{id}.json\",\n  format: \"json\"\n});\n\nconst path = posts.pathFromRecord({ id: \"hello\", title: \"Hello\" });\n```\n\n## Safety Defaults\n\nChange-set paths are rejected when they are absolute, include `..`, include empty segments, duplicate another file in the same change set, or write `.github/workflows/**` without `allowWorkflowPaths`.\n\nUse `allowedPathGlobs` to constrain writes:\n\n```ts\nawait github.commitChangeSet({\n  repo: \"acme/site\",\n  branch: \"async/content\",\n  message: \"Update content\",\n  files,\n  allowedPathGlobs: [\"content/**\", \"docs/**\"]\n});\n```\n\nReceipts include commit SHAs, branch names, PR URLs, file paths, and index hints. They do not include file contents.\n\n## Verification\n\n```bash\npnpm install\npnpm run release:check\nnpm pack --dry-run\n```\n\nCI is generated from `pipeline.ts` by `@async/pipeline`; workflow YAML should not be hand-edited.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasync%2Fgithub-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fasync%2Fgithub-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasync%2Fgithub-app/lists"}