fix: unify GitHub sync token resolution with integrations env file
This commit is contained in:
parent
8386cfbec0
commit
e2ddf09a72
|
|
@ -41,7 +41,7 @@ export async function GET(request: NextRequest) {
|
|||
return NextResponse.json({ error: 'repo query parameter required (owner/repo format)' }, { status: 400 })
|
||||
}
|
||||
|
||||
const token = getGitHubToken()
|
||||
const token = await getGitHubToken()
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'GITHUB_TOKEN not configured' }, { status: 400 })
|
||||
}
|
||||
|
|
@ -109,7 +109,7 @@ async function handleSync(
|
|||
return NextResponse.json({ error: 'repo is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const token = getGitHubToken()
|
||||
const token = await getGitHubToken()
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'GITHUB_TOKEN not configured' }, { status: 400 })
|
||||
}
|
||||
|
|
@ -344,7 +344,7 @@ function handleStatus(workspaceId: number) {
|
|||
// ── Stats: GitHub user profile + repo overview ──────────────────
|
||||
|
||||
async function handleGitHubStats() {
|
||||
const token = getGitHubToken()
|
||||
const token = await getGitHubToken()
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'GITHUB_TOKEN not configured' }, { status: 400 })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
|
||||
import { getEffectiveEnvValue } from '../runtime-env'
|
||||
|
||||
describe('getEffectiveEnvValue', () => {
|
||||
afterEach(() => {
|
||||
delete process.env.TEST_RUNTIME_ENV
|
||||
})
|
||||
|
||||
it('reads values from the OpenClaw env file before process.env', async () => {
|
||||
const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'mc-runtime-env-'))
|
||||
try {
|
||||
const envFilePath = path.join(tmpDir, '.env')
|
||||
await writeFile(envFilePath, 'TEST_RUNTIME_ENV=from-file\n', 'utf-8')
|
||||
process.env.TEST_RUNTIME_ENV = 'from-process'
|
||||
|
||||
await expect(getEffectiveEnvValue('TEST_RUNTIME_ENV', { envFilePath })).resolves.toBe('from-file')
|
||||
} finally {
|
||||
await rm(tmpDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('falls back to process.env when the env file does not define the key', async () => {
|
||||
const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'mc-runtime-env-'))
|
||||
try {
|
||||
const envFilePath = path.join(tmpDir, '.env')
|
||||
await writeFile(envFilePath, 'OTHER_KEY=value\n', 'utf-8')
|
||||
process.env.TEST_RUNTIME_ENV = 'from-process'
|
||||
|
||||
await expect(getEffectiveEnvValue('TEST_RUNTIME_ENV', { envFilePath })).resolves.toBe('from-process')
|
||||
} finally {
|
||||
await rm(tmpDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('returns an empty string when the key is missing everywhere', async () => {
|
||||
const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'mc-runtime-env-'))
|
||||
try {
|
||||
const envFilePath = path.join(tmpDir, '.env')
|
||||
await writeFile(envFilePath, '', 'utf-8')
|
||||
|
||||
await expect(getEffectiveEnvValue('TEST_RUNTIME_ENV', { envFilePath })).resolves.toBe('')
|
||||
} finally {
|
||||
await rm(tmpDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
/**
|
||||
* GitHub API client for Mission Control issue sync.
|
||||
* Uses GITHUB_TOKEN from env (integration key, not core config).
|
||||
* Resolves GITHUB_TOKEN from the OpenClaw integration env file first,
|
||||
* then falls back to process.env for deployments that export it directly.
|
||||
*/
|
||||
import { getEffectiveEnvValue } from '@/lib/runtime-env'
|
||||
|
||||
export interface GitHubLabel {
|
||||
name: string
|
||||
|
|
@ -25,8 +27,8 @@ export interface GitHubIssue {
|
|||
updated_at: string
|
||||
}
|
||||
|
||||
export function getGitHubToken(): string | null {
|
||||
return process.env.GITHUB_TOKEN || null
|
||||
export async function getGitHubToken(): Promise<string | null> {
|
||||
return await getEffectiveEnvValue('GITHUB_TOKEN') || null
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -36,7 +38,7 @@ export async function githubFetch(
|
|||
path: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<Response> {
|
||||
const token = getGitHubToken()
|
||||
const token = await getGitHubToken()
|
||||
if (!token) {
|
||||
throw new Error('GITHUB_TOKEN not configured')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
import { readFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import { config } from '@/lib/config'
|
||||
|
||||
function parseEnvLine(line: string): { key: string; value: string } | null {
|
||||
const trimmed = line.trim()
|
||||
if (!trimmed || trimmed.startsWith('#')) return null
|
||||
|
||||
const eqIdx = line.indexOf('=')
|
||||
if (eqIdx <= 0) return null
|
||||
|
||||
const key = line.slice(0, eqIdx).trim()
|
||||
const value = line.slice(eqIdx + 1).trim()
|
||||
if (!key) return null
|
||||
return { key, value }
|
||||
}
|
||||
|
||||
async function readOpenClawEnvFile(envFilePath: string): Promise<Map<string, string>> {
|
||||
try {
|
||||
const raw = await readFile(envFilePath, 'utf-8')
|
||||
const envMap = new Map<string, string>()
|
||||
for (const line of raw.split('\n')) {
|
||||
const parsed = parseEnvLine(line)
|
||||
if (parsed) envMap.set(parsed.key, parsed.value)
|
||||
}
|
||||
return envMap
|
||||
} catch (error: any) {
|
||||
if (error?.code === 'ENOENT') return new Map<string, string>()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function getEffectiveEnvValue(
|
||||
key: string,
|
||||
options?: { envFilePath?: string }
|
||||
): Promise<string> {
|
||||
const envFilePath = options?.envFilePath || join(config.openclawStateDir, '.env')
|
||||
const envMap = await readOpenClawEnvFile(envFilePath)
|
||||
const fromFile = envMap.get(key)
|
||||
if (typeof fromFile === 'string' && fromFile.length > 0) return fromFile
|
||||
|
||||
const fromProcess = process.env[key]
|
||||
if (typeof fromProcess === 'string' && fromProcess.length > 0) return fromProcess
|
||||
|
||||
return ''
|
||||
}
|
||||
Loading…
Reference in New Issue