From 0c2c53e809cd74933932ca3f1b0635afde898fd3 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Thu, 9 Apr 2026 18:13:39 +0000 Subject: [PATCH] feat: add getOctokit factory with deep merge, stripUndefined, and plugin dedup --- src/create-configured-getoctokit.ts | 64 +++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/create-configured-getoctokit.ts diff --git a/src/create-configured-getoctokit.ts b/src/create-configured-getoctokit.ts new file mode 100644 index 0000000..00c78e1 --- /dev/null +++ b/src/create-configured-getoctokit.ts @@ -0,0 +1,64 @@ +import {getOctokit} from '@actions/github' + +/** + * Strip keys whose value is `undefined` so they don't clobber defaults + * during object spread (e.g. `{baseUrl: undefined}` would wipe a GHES URL). + */ +function stripUndefined(obj: Record): Record { + return Object.fromEntries( + Object.entries(obj).filter(([, v]) => v !== undefined) + ) +} + +/** + * Creates a wrapped getOctokit that inherits default options and plugins. + * Secondary clients created via the wrapper get the same retry, logging, + * orchestration ID, and retries count as the pre-built `github` client. + * + * - `request` and `retry` are deep-merged so partial overrides + * (e.g. `{request: {timeout: 5000}}`) don't clobber inherited values. + * - `undefined` values in both default and user options are stripped to prevent + * accidental clobbering (e.g. GHES `baseUrl`, or `log: undefined` from defaults). + * - Default plugins (retry, requestLog) are always included; duplicates are skipped. + */ +export function createConfiguredGetOctokit( + rawGetOctokit: typeof getOctokit, + defaultOptions: Record, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...defaultPlugins: any[] +): typeof getOctokit { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return ((token: string, options?: any, ...plugins: any[]) => { + const cleanDefaults = stripUndefined(defaultOptions) + const userOpts = stripUndefined(options ?? {}) + + const defaultRequest = + (cleanDefaults.request as Record | undefined) ?? {} + const userRequest = stripUndefined( + (userOpts.request as Record | undefined) ?? {} + ) + + const defaultRetry = + (cleanDefaults.retry as Record | undefined) ?? {} + const userRetry = stripUndefined( + (userOpts.retry as Record | undefined) ?? {} + ) + + const merged = { + ...cleanDefaults, + ...userOpts, + request: {...defaultRequest, ...userRequest}, + retry: {...defaultRetry, ...userRetry} + } + + // Deduplicate: default plugins first, then user plugins that aren't already present + const allPlugins = [...defaultPlugins] + for (const plugin of plugins) { + if (!allPlugins.includes(plugin)) { + allPlugins.push(plugin) + } + } + + return rawGetOctokit(token, merged, ...allPlugins) + }) as typeof getOctokit +}