add workflows: build-push-ghcr-images

This commit is contained in:
Haitao Pan 2026-04-02 17:44:29 +08:00
parent c894924a57
commit 484734352d
4 changed files with 138 additions and 0 deletions

View File

@ -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

View File

@ -90,6 +90,7 @@ export type IntegrationDefaults = {
vaultSecretKey: string
apisixUrl: string
apisixTokenConfigured: boolean
externalServices?: string[]
}
export function normalizeMainSessionKey(value?: string | null): string {

View File

@ -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",
]);
});
});

View File

@ -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)),
}
}