fix: finalize v1.21.0 updater release

This commit is contained in:
coso
2026-04-28 17:32:57 +08:00
parent 32825054e8
commit 5a05575cde
6 changed files with 122 additions and 80 deletions
+95 -4
View File
@@ -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
View File
@@ -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 按钮超时的问题。
---
+1 -55
View File
@@ -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({
+7 -13
View File
@@ -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": [
+4 -4
View File
@@ -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" };
}