{"_id":"@async/github-app","name":"@async/github-app","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.1":{"name":"@async/github-app","version":"0.1.1","description":"Reusable GitHub App, webhook, Actions bridge, and content change-set helpers for Async packages.","license":"MIT","type":"module","packageManager":"pnpm@10.20.0","engines":{"node":">=24"},"repository":{"type":"git","url":"git+https://github.com/async/github-app.git"},"bugs":{"url":"https://github.com/async/github-app/issues"},"homepage":"https://github.com/async/github-app#readme","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./server":{"types":"./dist/server.d.ts","default":"./dist/server.js"},"./actions":{"types":"./dist/actions.d.ts","default":"./dist/actions.js"},"./content":{"types":"./dist/content.d.ts","default":"./dist/content.js"}},"bin":{"async-github-app":"dist/cli.js"},"scripts":{"api-surface:check":"api-contract check --manifest api-contract.json && api-contract ledger --manifest api-contract.json --check API_SURFACE.md","api-surface:generate":"api-contract ledger --manifest api-contract.json --out API_SURFACE.md","async-pipeline":"async-pipeline","build":"tsc -p tsconfig.json && chmod +x dist/cli.js","check":"pnpm run build && pnpm run test && pnpm run api-surface:check && pnpm run pack:check","pack:check":"npm --cache .async/npm-cache pack --dry-run","pipeline:github:check":"async-pipeline github check","pipeline:github:generate":"async-pipeline github generate","pipeline:publish":"async-pipeline run publish","pipeline:publish:github:main":"async-pipeline publish github main --package .","pipeline:publish:github:pr":"async-pipeline publish github pr --package .","pipeline:publish:github:release":"async-pipeline publish github release --package .","pipeline:publish:npm":"async-pipeline publish npm --package .","pipeline:release-doctor":"async-pipeline run release-doctor","pipeline:release:doctor":"async-pipeline release doctor --package .","pipeline:release:ensure":"async-pipeline release ensure --package .","pipeline:snapshot":"async-pipeline run snapshot","pipeline:sync:check":"async-pipeline sync check","pipeline:sync:generate":"async-pipeline sync generate","pipeline:verify":"async-pipeline run verify","pipeline:verify:force":"async-pipeline run verify --force","release:check":"pnpm run pipeline:verify:force","test":"pnpm run build && node --test tests/*.test.js","typecheck":"tsc --noEmit -p tsconfig.json"},"sideEffects":false,"publishConfig":{"access":"public"},"devDependencies":{"@async/api-contract":"0.1.1","@async/pipeline":"0.9.2","@types/node":"24.12.4","typescript":"6.0.3"},"gitHead":"ad81fbcaf765002bdad3a2c715da3f2937c76a6a","_id":"@async/github-app@0.1.1","_nodeVersion":"24.16.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-GsrU2P2j54kUKRRiXHZkcyDVcut641c73fu12ImdLoOigX76yBhoYtRgiD8mxHL60tjYaaml3WU5TrLP1wWPAA==","shasum":"18b8d877ed2036b95a01c37c12a3f07e172e9266","tarball":"https://registry.npmjs.org/@async/github-app/-/github-app-0.1.1.tgz","fileCount":39,"unpackedSize":91858,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@async%2fgithub-app@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDqZSdsFxnlvt3X2rANg9ia5CqTLXR7onzIKalziPZPTQIhAJR7A+/q/p3e6bujJd+Q2Y7t57uKfhizxqzGEiq1U8MP"}]},"_npmUser":{"name":"patrickjs","email":"npm.patrickjs@gdi2290.com"},"directories":{},"maintainers":[{"name":"jackshelton","email":"me@jackshelton.com"},{"name":"async-npm","email":"async.npm@gdi2290.com"},{"name":"patrickjs","email":"npm.patrickjs@gdi2290.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/github-app_0.1.1_1781827939805_0.39902295770582175"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-19T00:12:19.694Z","0.1.1":"2026-06-19T00:12:19.964Z","modified":"2026-06-19T00:12:20.459Z"},"maintainers":[{"name":"jackshelton","email":"me@jackshelton.com"},{"name":"async-npm","email":"async.npm@gdi2290.com"},{"name":"patrickjs","email":"npm.patrickjs@gdi2290.com"}],"description":"Reusable GitHub App, webhook, Actions bridge, and content change-set helpers for Async packages.","homepage":"https://github.com/async/github-app#readme","repository":{"type":"git","url":"git+https://github.com/async/github-app.git"},"bugs":{"url":"https://github.com/async/github-app/issues"},"license":"MIT","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) => {\n        await queueReindex(event.payload);\n      },\n      pull_request: async (event) => {\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","readmeFilename":"README.md","_rev":"1-d6451b5955867d91915cf2841447329e"}