From 484734352d42ef5a884ce82af2b722269a0c67a5 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Thu, 2 Apr 2026 17:44:29 +0800 Subject: [PATCH] add workflows: build-push-ghcr-images --- .github/workflows/build-push-ghcr-image.yml | 69 +++++++++++++++++++++ src/lib/openclaw/types.ts | 1 + src/server/consoleIntegrations.test.ts | 49 +++++++++++++++ src/server/consoleIntegrations.ts | 19 ++++++ 4 files changed, 138 insertions(+) create mode 100644 .github/workflows/build-push-ghcr-image.yml create mode 100644 src/server/consoleIntegrations.test.ts diff --git a/.github/workflows/build-push-ghcr-image.yml b/.github/workflows/build-push-ghcr-image.yml new file mode 100644 index 0000000..f4792cf --- /dev/null +++ b/.github/workflows/build-push-ghcr-image.yml @@ -0,0 +1,69 @@ +name: Build And Push GHCR Image + +on: + workflow_call: + inputs: + image_tag: + description: Optional image tag. Defaults to the current commit SHA. + required: false + type: string + push_latest: + description: Also publish the `latest` tag. + required: false + default: false + type: boolean + workflow_dispatch: + inputs: + image_tag: + description: Optional image tag. Defaults to the current commit SHA. + required: false + type: string + push_latest: + description: Also publish the `latest` tag. + required: false + default: false + type: boolean + +permissions: + contents: read + packages: write + +concurrency: + group: build-push-ghcr-image-console-${{ github.ref_name }} + cancel-in-progress: false + +jobs: + build-and-push: + runs-on: ubuntu-latest + env: + PRIMARY_DOMAIN: console.svc.plus + NEXT_PUBLIC_RUNTIME_ENVIRONMENT: prod + NEXT_PUBLIC_RUNTIME_REGION: cn + steps: + - name: Check Out Repository + uses: actions/checkout@v4 + + - name: Set Up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log In To GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ vars.GHCR_USERNAME || github.repository_owner }} + password: ${{ secrets.GHCR_TOKEN || github.token }} + + - name: Compute Image Metadata + id: metadata + env: + IMAGE_TAG_INPUT: ${{ inputs.image_tag }} + run: | + bash scripts/github-actions/compute-frontend-release-metadata.sh "${IMAGE_TAG_INPUT}" + + - name: Publish Frontend Image + env: + IMAGE_REF: ${{ steps.metadata.outputs.image_ref }} + IMAGE_LATEST_REF: ${{ steps.metadata.outputs.image_latest_ref }} + PUSH_LATEST: ${{ inputs.push_latest }} + run: | + bash scripts/github-actions/build-and-push-frontend-image.sh diff --git a/src/lib/openclaw/types.ts b/src/lib/openclaw/types.ts index f28ae11..f299f32 100644 --- a/src/lib/openclaw/types.ts +++ b/src/lib/openclaw/types.ts @@ -90,6 +90,7 @@ export type IntegrationDefaults = { vaultSecretKey: string apisixUrl: string apisixTokenConfigured: boolean + externalServices?: string[] } export function normalizeMainSessionKey(value?: string | null): string { diff --git a/src/server/consoleIntegrations.test.ts b/src/server/consoleIntegrations.test.ts new file mode 100644 index 0000000..e4113e2 --- /dev/null +++ b/src/server/consoleIntegrations.test.ts @@ -0,0 +1,49 @@ +// @vitest-environment node + +import { afterAll, beforeEach, describe, expect, it } from "vitest"; + +const ORIGINAL_ENV = { ...process.env }; + +describe("consoleIntegrations", () => { + beforeEach(() => { + process.env = { ...ORIGINAL_ENV }; + delete process.env.EXTERNAL_SERVICES; + delete process.env.NEXT_PUBLIC_EXTERNAL_SERVICES; + delete process.env.EXTERNAL_SERVICE; + delete process.env.NEXT_PUBLIC_EXTERNAL_SERVICE; + }); + + afterAll(() => { + process.env = ORIGINAL_ENV; + }); + + it("parses configured external services into a stable list", async () => { + process.env.EXTERNAL_SERVICES = + "docs.svc.plus, xworkmate.svc.plus\nopenclaw-gateway.svc.plus"; + + const { getConsoleIntegrationDefaults } = await import("./consoleIntegrations"); + + expect(getConsoleIntegrationDefaults().externalServices).toEqual([ + "docs.svc.plus", + "xworkmate.svc.plus", + "openclaw-gateway.svc.plus", + ]); + }); + + it("defaults to an empty external services list when unset", async () => { + const { getConsoleIntegrationDefaults } = await import("./consoleIntegrations"); + + expect(getConsoleIntegrationDefaults().externalServices).toEqual([]); + }); + + it("accepts the singular external service env var as a fallback", async () => { + process.env.EXTERNAL_SERVICE = "docs.svc.plus;xworkmate.svc.plus"; + + const { getConsoleIntegrationDefaults } = await import("./consoleIntegrations"); + + expect(getConsoleIntegrationDefaults().externalServices).toEqual([ + "docs.svc.plus", + "xworkmate.svc.plus", + ]); + }); +}); diff --git a/src/server/consoleIntegrations.ts b/src/server/consoleIntegrations.ts index f3190bb..2410b3a 100644 --- a/src/server/consoleIntegrations.ts +++ b/src/server/consoleIntegrations.ts @@ -23,6 +23,12 @@ const APISIX_TOKEN_KEYS = ['AI_GATEWAY_ACCESS_TOKEN', 'AI_GATEWAY_API_KEY'] as c const VAULT_URL_KEYS = ['VAULT_SERVER_URL', 'VAULT_ADDR', 'vault_addr'] as const const VAULT_NAMESPACE_KEYS = ['VAULT_NAMESPACE'] as const const VAULT_TOKEN_KEYS = ['VAULT_TOKEN', 'VAULT_SERVER_ROOT_ACCESS_TOKEN'] as const +const EXTERNAL_SERVICES_KEYS = [ + 'EXTERNAL_SERVICES', + 'NEXT_PUBLIC_EXTERNAL_SERVICES', + 'EXTERNAL_SERVICE', + 'NEXT_PUBLIC_EXTERNAL_SERVICE', +] as const function readEnvValue(...keys: readonly string[]): string | undefined { for (const key of keys) { @@ -59,6 +65,18 @@ function normalizeHttpUrl(value?: string): string { } } +function parseServiceList(value?: string): string[] { + const raw = value?.trim() ?? '' + if (!raw) { + return [] + } + + return raw + .split(/[\n,;]+/) + .map((item) => item.trim()) + .filter((item) => item.length > 0) +} + function normalizeWsUrl(value?: string): string { const raw = value?.trim() ?? '' if (raw.length === 0) { @@ -92,6 +110,7 @@ export function getConsoleIntegrationDefaults(): IntegrationDefaults { vaultSecretKey: '', apisixUrl: normalizeHttpUrl(readEnvValue(...APISIX_URL_KEYS)), apisixTokenConfigured: Boolean(readEnvValue(...APISIX_TOKEN_KEYS)), + externalServices: parseServiceList(readEnvValue(...EXTERNAL_SERVICES_KEYS)), } }