mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
fix: finalize v1.21.0 updater release
This commit is contained in:
@@ -76,7 +76,11 @@ jobs:
|
||||
fi
|
||||
|
||||
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
echo "Release $TAG already exists"
|
||||
gh release edit "$TAG" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "Lime $TAG" \
|
||||
--notes-file "$NOTES_FILE"
|
||||
echo "Release $TAG already exists; notes refreshed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -115,7 +119,7 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
LIME_UPDATER_PUBLIC_KEY: ${{ secrets.LIME_UPDATER_PUBLIC_KEY }}
|
||||
LIME_UPDATER_PUBLIC_KEY_RAW: ${{ secrets.LIME_UPDATER_PUBLIC_KEY }}
|
||||
LIME_UPDATES_BASE_URL: ${{ secrets.LIME_UPDATES_BASE_URL || vars.LIME_UPDATES_BASE_URL }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_RAW: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
@@ -161,7 +165,7 @@ jobs:
|
||||
id: updater_mode
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "${LIME_UPDATER_PUBLIC_KEY}" ] && [ -n "${TAURI_SIGNING_PRIVATE_KEY_RAW}" ] && [ -n "${TAURI_SIGNING_PRIVATE_KEY_PASSWORD}" ]; then
|
||||
if [ -n "${LIME_UPDATER_PUBLIC_KEY_RAW}" ] && [ -n "${TAURI_SIGNING_PRIVATE_KEY_RAW}" ] && [ -n "${TAURI_SIGNING_PRIVATE_KEY_PASSWORD}" ]; then
|
||||
echo "enabled=true" >> "$GITHUB_OUTPUT"
|
||||
echo "LIME_ENABLE_UPDATER_ARTIFACTS=true" >> "$GITHUB_ENV"
|
||||
echo "Updater artifacts enabled"
|
||||
@@ -172,6 +176,83 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Normalize updater public key
|
||||
if: steps.updater_mode.outputs.enabled == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
python - <<'PY'
|
||||
import base64
|
||||
import binascii
|
||||
import os
|
||||
|
||||
raw = os.environ.get("LIME_UPDATER_PUBLIC_KEY_RAW", "")
|
||||
if not raw.strip():
|
||||
raise SystemExit("LIME_UPDATER_PUBLIC_KEY secret is empty")
|
||||
|
||||
candidates = []
|
||||
|
||||
def add(value: str) -> None:
|
||||
normalized = value.replace("\r\n", "\n")
|
||||
if normalized and normalized not in candidates:
|
||||
candidates.append(normalized)
|
||||
|
||||
add(raw)
|
||||
if "\\n" in raw and "\n" not in raw:
|
||||
add(raw.replace("\\r\\n", "\n").replace("\\n", "\n"))
|
||||
|
||||
def to_tauri_pubkey(value: str) -> str:
|
||||
text = value.strip()
|
||||
compact = "".join(text.split())
|
||||
|
||||
# Tauri signer public key file content is already base64 encoded.
|
||||
try:
|
||||
decoded = base64.b64decode(compact, validate=True).decode("utf-8")
|
||||
except (binascii.Error, UnicodeDecodeError, ValueError):
|
||||
decoded = ""
|
||||
if decoded:
|
||||
decoded_lines = [line.strip() for line in decoded.splitlines() if line.strip()]
|
||||
if len(decoded_lines) >= 2:
|
||||
return compact
|
||||
|
||||
# Also accept the decoded minisign public key file content.
|
||||
lines = [line.strip() for line in text.splitlines() if line.strip()]
|
||||
if len(lines) >= 2:
|
||||
normalized_text = "\n".join(lines[:2]) + "\n"
|
||||
return base64.b64encode(normalized_text.encode("utf-8")).decode("ascii")
|
||||
|
||||
# Some setups paste only the raw minisign public key line.
|
||||
if len(compact) >= 40:
|
||||
try:
|
||||
key_bytes = base64.b64decode(compact, validate=True)
|
||||
except (binascii.Error, ValueError):
|
||||
key_bytes = b""
|
||||
if len(key_bytes) == 42:
|
||||
normalized_text = f"untrusted comment: minisign public key\n{compact}\n"
|
||||
return base64.b64encode(normalized_text.encode("utf-8")).decode("ascii")
|
||||
|
||||
return ""
|
||||
|
||||
normalized_key = next(
|
||||
(
|
||||
key
|
||||
for candidate in candidates
|
||||
for key in [to_tauri_pubkey(candidate)]
|
||||
if key
|
||||
),
|
||||
"",
|
||||
)
|
||||
if not normalized_key:
|
||||
raise SystemExit(
|
||||
"Unable to normalize LIME_UPDATER_PUBLIC_KEY into Tauri updater pubkey content. "
|
||||
"Expected the public key value generated by `npx tauri signer generate --write-keys`."
|
||||
)
|
||||
|
||||
with open(os.environ["GITHUB_ENV"], "a", encoding="utf-8") as env_file:
|
||||
env_file.write(f"LIME_UPDATER_PUBLIC_KEY={normalized_key}\n")
|
||||
|
||||
print("Normalized updater public key verified for Tauri config and runtime")
|
||||
PY
|
||||
|
||||
- name: Sync version from tag to tauri configs
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -181,11 +262,21 @@ jobs:
|
||||
cd src-tauri
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const updaterPublicKey = process.env.LIME_UPDATER_PUBLIC_KEY?.trim();
|
||||
const createUpdaterArtifacts = process.env.LIME_ENABLE_UPDATER_ARTIFACTS === 'true';
|
||||
if (createUpdaterArtifacts && !updaterPublicKey) {
|
||||
throw new Error('LIME_UPDATER_PUBLIC_KEY is required when updater artifacts are enabled');
|
||||
}
|
||||
for (const file of ['tauri.conf.json', 'tauri.conf.headless.json']) {
|
||||
const conf = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
conf.version = '$VERSION';
|
||||
conf.bundle = conf.bundle || {};
|
||||
conf.bundle.createUpdaterArtifacts = process.env.LIME_ENABLE_UPDATER_ARTIFACTS === 'true';
|
||||
conf.bundle.createUpdaterArtifacts = createUpdaterArtifacts;
|
||||
if (createUpdaterArtifacts) {
|
||||
conf.plugins = conf.plugins || {};
|
||||
conf.plugins.updater = conf.plugins.updater || {};
|
||||
conf.plugins.updater.pubkey = updaterPublicKey;
|
||||
}
|
||||
fs.writeFileSync(file, JSON.stringify(conf, null, 2) + '\n');
|
||||
}
|
||||
"
|
||||
|
||||
+4
-4
@@ -89,8 +89,10 @@
|
||||
- `cargo test --manifest-path "src-tauri/Cargo.toml"` — 1109 passed / 0 failed / 2 ignored
|
||||
- `cargo clippy --manifest-path "src-tauri/Cargo.toml" --all-targets --all-features`
|
||||
- `npm run lint`
|
||||
- `npm test` — 43 个 Vitest smart 批次通过
|
||||
- `npm test` — 44 个 Vitest smart 批次通过
|
||||
- `npm run test:contracts`
|
||||
- `npm run smoke:agent-runtime-tool-surface`
|
||||
- `npm run smoke:agent-runtime-tool-surface-page`
|
||||
- `git diff --check`
|
||||
- `cargo test` 通过,当前存在 1 条预存 warning:
|
||||
- `write_auxiliary_runtime_projection_fixture` 的 `dead_code`
|
||||
@@ -99,9 +101,7 @@
|
||||
- `crates/skills/src/lime_llm_provider.rs` 的 `too_many_arguments`
|
||||
- `crates/agent/src/session_execution_runtime.rs` 的 `needless_lifetimes`
|
||||
- `src/services/runtime_evidence_pack_service.rs` 的 `dead_code`
|
||||
- GUI 主路径校验未完全通过:
|
||||
- `npm run verify:gui-smoke` 已通过 `bridge:health`、`smoke:workspace-ready`、`smoke:browser-runtime`、`smoke:site-adapters`、`smoke:agent-service-skill-entry`、`smoke:agent-runtime-tool-surface`。
|
||||
- `smoke:agent-runtime-tool-surface-page` 在等待 Harness 按钮出现时超时;单独复测时托管 Chrome 会话在提交后丢失,需继续定位真实页面发送后的会话保持 / Harness 入口暴露问题。
|
||||
- GUI 主路径补充复测已通过:`smoke:agent-runtime-tool-surface` 与 `smoke:agent-runtime-tool-surface-page` 均确认 Harness 入口在执行态可见,修复此前等待 Harness 按钮超时的问题。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
getPublicAuthCatalog,
|
||||
getClientProviderOffer,
|
||||
getClientReferralDashboard,
|
||||
listPublicOAuthProviders,
|
||||
listClientPaymentConfigs,
|
||||
listClientPlans,
|
||||
listClientProviderOfferModels,
|
||||
@@ -156,59 +155,6 @@ describe("oemCloudControlPlane desktop auth", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("应读取公开 OAuth Provider 目录供启动登录判断使用", async () => {
|
||||
const fetchMock = vi.fn(async () => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({
|
||||
code: 200,
|
||||
message: "success",
|
||||
data: {
|
||||
items: [
|
||||
{
|
||||
provider: "google",
|
||||
displayName: "Google",
|
||||
authorizeUrl: "https://user.limeai.run/oauth/google",
|
||||
redirectUri: "https://user.limeai.run/oauth/callback",
|
||||
scopes: ["openid", "email"],
|
||||
enabled: true,
|
||||
loginHint: "使用 Google 登录",
|
||||
},
|
||||
],
|
||||
authPolicy: {
|
||||
required: true,
|
||||
startupTrigger: "oauth",
|
||||
primaryProvider: "google",
|
||||
},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
const providers = await listPublicOAuthProviders("tenant-0001");
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
"https://user.limeai.run/api/v1/public/tenants/tenant-0001/oauth/providers",
|
||||
expect.objectContaining({
|
||||
method: "GET",
|
||||
headers: expect.objectContaining({
|
||||
Accept: "application/json",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(providers).toEqual([
|
||||
{
|
||||
provider: "google",
|
||||
displayName: "Google",
|
||||
authorizeUrl: "https://user.limeai.run/oauth/google",
|
||||
redirectUri: "https://user.limeai.run/oauth/callback",
|
||||
scopes: ["openid", "email"],
|
||||
enabled: true,
|
||||
loginHint: "使用 Google 登录",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("应读取公开登录目录与启动策略", async () => {
|
||||
const fetchMock = vi.fn(async () => ({
|
||||
ok: true,
|
||||
@@ -238,7 +184,7 @@ describe("oemCloudControlPlane desktop auth", () => {
|
||||
const catalog = await getPublicAuthCatalog("tenant-0001");
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
"https://user.limeai.run/api/v1/public/tenants/tenant-0001/oauth/providers",
|
||||
"https://user.limeai.run/api/v1/public/tenants/tenant-0001/client/auth-catalog",
|
||||
expect.objectContaining({
|
||||
method: "GET",
|
||||
headers: expect.objectContaining({
|
||||
|
||||
@@ -47,7 +47,7 @@ export interface OemCloudUserSession {
|
||||
expiresAt: string;
|
||||
}
|
||||
|
||||
export interface OemCloudPublicOAuthProvider {
|
||||
export interface OemCloudAuthCatalogProvider {
|
||||
provider: string;
|
||||
displayName: string;
|
||||
authorizeUrl?: string;
|
||||
@@ -66,7 +66,7 @@ export interface OemCloudAuthPolicy {
|
||||
}
|
||||
|
||||
export interface OemCloudPublicAuthCatalog {
|
||||
providers: OemCloudPublicOAuthProvider[];
|
||||
providers: OemCloudAuthCatalogProvider[];
|
||||
authPolicy: OemCloudAuthPolicy;
|
||||
}
|
||||
|
||||
@@ -1120,15 +1120,15 @@ function parseCurrentSession(value: unknown): OemCloudCurrentSession {
|
||||
};
|
||||
}
|
||||
|
||||
function parsePublicOAuthProvider(value: unknown): OemCloudPublicOAuthProvider {
|
||||
function parseAuthCatalogProvider(value: unknown): OemCloudAuthCatalogProvider {
|
||||
if (!isRecord(value)) {
|
||||
throw new OemCloudControlPlaneError("OAuth Provider 格式非法");
|
||||
throw new OemCloudControlPlaneError("登录方式格式非法");
|
||||
}
|
||||
|
||||
const provider = normalizeText(value.provider);
|
||||
const displayName = normalizeText(value.displayName) ?? provider;
|
||||
if (!provider || !displayName) {
|
||||
throw new OemCloudControlPlaneError("OAuth Provider 格式非法");
|
||||
throw new OemCloudControlPlaneError("登录方式格式非法");
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -1157,7 +1157,7 @@ function parsePublicAuthCatalog(value: unknown): OemCloudPublicAuthCatalog {
|
||||
const record = isRecord(value) ? value : {};
|
||||
return {
|
||||
providers: Array.isArray(record.items)
|
||||
? record.items.map(parsePublicOAuthProvider)
|
||||
? record.items.map(parseAuthCatalogProvider)
|
||||
: [],
|
||||
authPolicy: parseAuthPolicy(record.authPolicy),
|
||||
};
|
||||
@@ -2470,18 +2470,12 @@ export async function pollClientDesktopAuthSession(
|
||||
);
|
||||
}
|
||||
|
||||
export async function listPublicOAuthProviders(
|
||||
tenantId: string,
|
||||
): Promise<OemCloudPublicOAuthProvider[]> {
|
||||
return (await getPublicAuthCatalog(tenantId)).providers;
|
||||
}
|
||||
|
||||
export async function getPublicAuthCatalog(
|
||||
tenantId: string,
|
||||
): Promise<OemCloudPublicAuthCatalog> {
|
||||
return parsePublicAuthCatalog(
|
||||
await requestControlPlane<unknown>(
|
||||
`/v1/public/tenants/${encodeURIComponent(tenantId)}/oauth/providers`,
|
||||
`/v1/public/tenants/${encodeURIComponent(tenantId)}/client/auth-catalog`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3054,6 +3054,17 @@
|
||||
"listPosterMaterialsByMood(",
|
||||
"usePosterMaterial("
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "oem-cloud-public-oauth-provider-list-surface",
|
||||
"classification": "dead-candidate",
|
||||
"description": "云端启动登录不允许恢复旧公开 OAuth Provider 列表入口,客户端只能通过 auth catalog/authPolicy 判断登录策略",
|
||||
"patterns": [
|
||||
"listPublicOAuthProviders(",
|
||||
"OemCloudPublicOAuthProvider",
|
||||
"/oauth/providers"
|
||||
],
|
||||
"allowedPaths": []
|
||||
}
|
||||
],
|
||||
"rustText": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
getPublicAuthCatalog,
|
||||
type OemCloudPublicOAuthProvider,
|
||||
type OemCloudAuthCatalogProvider,
|
||||
} from "@/lib/api/oemCloudControlPlane";
|
||||
import {
|
||||
resolveOemCloudRuntimeContext,
|
||||
@@ -30,8 +30,8 @@ function normalizeProvider(value: string | undefined): string {
|
||||
return value?.trim().toLowerCase() ?? "";
|
||||
}
|
||||
|
||||
function hasGoogleOAuthProvider(
|
||||
providers: OemCloudPublicOAuthProvider[],
|
||||
function hasGoogleAuthProvider(
|
||||
providers: OemCloudAuthCatalogProvider[],
|
||||
): boolean {
|
||||
return providers.some(
|
||||
(provider) =>
|
||||
@@ -54,7 +54,7 @@ function shouldStartGoogleOauth(
|
||||
return { status: "unsupported_policy" };
|
||||
}
|
||||
|
||||
if (!hasGoogleOAuthProvider(catalog.providers)) {
|
||||
if (!hasGoogleAuthProvider(catalog.providers)) {
|
||||
return { status: "no_google_provider" };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user