index.ts 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. /** Signed GitHub HTTP adapter for the provider-neutral webhook runtime. */
  2. import type { Context } from '@deepseek-ai/cordis'
  3. import { credentialRef } from '@deepseek-ai/dsh-credentials'
  4. import type {} from '@deepseek-ai/dsh-host-webserver'
  5. import z from '@deepseek-ai/schemastery'
  6. import { createGitHubWebhookHandler } from './handler.ts'
  7. export type * from './types.ts'
  8. /** Cordis function-plugin name. */
  9. export const name = 'webhook-github'
  10. /** Host services required before the exact route can register. */
  11. export const inject = ['webServer', 'webhookRuntime', 'credentials']
  12. /** Required GitHub ingress configuration. */
  13. export interface Config {
  14. /** Adapter instance name carried to rules. */
  15. readonly source: string
  16. /** Exact absolute route path. */
  17. readonly path: string
  18. /** Credential reference containing the shared webhook secret. */
  19. readonly secretEnv: string
  20. /** Positive raw body ceiling in bytes. */
  21. readonly maxBodyBytes: number
  22. }
  23. export const Config: z<Config> = z.object({
  24. source: z.string().required(),
  25. path: z.string().required(),
  26. secretEnv: z.string().role('credential-ref').required(),
  27. maxBodyBytes: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).required(),
  28. })
  29. /** Validate route and source facts that Schemastery cannot express. */
  30. function assertConfig(config: Config): void {
  31. if (config.source.trim() !== config.source || config.source === '') {
  32. throw new Error('webhook-github source must be a non-empty trimmed string')
  33. }
  34. if (!config.path.startsWith('/') || config.path === '/' || config.path.endsWith('/')
  35. || config.path.includes('?') || config.path.includes('#')) {
  36. throw new Error('webhook-github path must be an absolute non-root pathname without a trailing slash, query, or fragment')
  37. }
  38. }
  39. /** Register one signed GitHub endpoint on the injected WebServer. */
  40. export function apply(ctx: Context, config: Config): void {
  41. assertConfig(config)
  42. const route = {
  43. kind: 'exact' as const,
  44. path: config.path,
  45. handler: createGitHubWebhookHandler(ctx, {
  46. source: config.source,
  47. secretEnv: credentialRef(config.secretEnv),
  48. maxBodyBytes: config.maxBodyBytes,
  49. }),
  50. }
  51. ctx.effect(
  52. () => ctx.webServer.register(route),
  53. `webhook-github: ${config.path}`,
  54. )
  55. }