Compare commits
4 Commits
main
...
cursor/ci-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dd4ff4ba1 | ||
|
|
1c3d8c40fe | ||
|
|
80c56eef36 | ||
|
|
1704d1cec9 |
28
.github/workflows/ci.yml
vendored
28
.github/workflows/ci.yml
vendored
@ -246,6 +246,7 @@ jobs:
|
||||
publish-mobile-update:
|
||||
name: Publish compatible mobile update
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
continue-on-error: true
|
||||
needs: [lint, check, build, test, test-integration, test-e2e]
|
||||
runs-on: [self-hosted, evoo-ci, Linux]
|
||||
timeout-minutes: 20
|
||||
@ -344,23 +345,30 @@ jobs:
|
||||
fi
|
||||
echo "publish=$publish" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Require Expo automation token
|
||||
- name: Check Expo automation token
|
||||
id: expo_token
|
||||
if: steps.ota.outputs.publish == 'true'
|
||||
env:
|
||||
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
|
||||
run: |
|
||||
if [[ -z "$EXPO_TOKEN" ]]; then
|
||||
echo "Add an EXPO_TOKEN repository secret with access to the inbox-zero/rakazo Expo project." >&2
|
||||
exit 1
|
||||
{
|
||||
echo "### Mobile OTA skipped"
|
||||
echo
|
||||
echo "No \`EXPO_TOKEN\` repository secret is configured, so CI will not publish a production update. Add a token with access to the inbox-zero/rakazo Expo project when OTA from main is desired."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "ready=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "ready=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
|
||||
if: steps.ota.outputs.publish == 'true'
|
||||
if: steps.ota.outputs.publish == 'true' && steps.expo_token.outputs.ready == 'true'
|
||||
id: pnpm-setup
|
||||
with:
|
||||
dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.job }}
|
||||
- name: Isolate pnpm store and PATH
|
||||
if: steps.ota.outputs.publish == 'true'
|
||||
if: steps.ota.outputs.publish == 'true' && steps.expo_token.outputs.ready == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@ -377,25 +385,25 @@ jobs:
|
||||
mkdir -p "$store"
|
||||
echo "npm_config_store_dir=${store}" >> "$GITHUB_ENV"
|
||||
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
if: steps.ota.outputs.publish == 'true'
|
||||
if: steps.ota.outputs.publish == 'true' && steps.expo_token.outputs.ready == 'true'
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
- name: Set up Expo
|
||||
if: steps.ota.outputs.publish == 'true'
|
||||
if: steps.ota.outputs.publish == 'true' && steps.expo_token.outputs.ready == 'true'
|
||||
uses: expo/expo-github-action@eab7a230208c952974db8c3245cfd78402c7b385 # v9
|
||||
with:
|
||||
eas-version: latest
|
||||
packager: pnpm
|
||||
token: ${{ secrets.EXPO_TOKEN }}
|
||||
- name: Install dependencies
|
||||
if: steps.ota.outputs.publish == 'true'
|
||||
if: steps.ota.outputs.publish == 'true' && steps.expo_token.outputs.ready == 'true'
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Verify mobile project
|
||||
if: steps.ota.outputs.publish == 'true'
|
||||
if: steps.ota.outputs.publish == 'true' && steps.expo_token.outputs.ready == 'true'
|
||||
run: pnpm --filter @rakazo/mobile check
|
||||
- name: Publish production update
|
||||
if: steps.ota.outputs.publish == 'true'
|
||||
if: steps.ota.outputs.publish == 'true' && steps.expo_token.outputs.ready == 'true'
|
||||
working-directory: apps/mobile
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@ -75,7 +75,11 @@ test("local settings open and save integrations without an app session", async (
|
||||
});
|
||||
|
||||
test("ordinary browsers cannot use local settings", async ({ page }) => {
|
||||
await page.goto("/desktop-settings");
|
||||
await expect(page.getByRole("alert")).toContainText("Could not open local settings");
|
||||
await expect(async () => {
|
||||
await page.goto("/desktop-settings", { waitUntil: "domcontentloaded" });
|
||||
await expect(page.getByRole("alert")).toContainText("Could not open local settings", {
|
||||
timeout: 5_000,
|
||||
});
|
||||
}).toPass({ timeout: 45_000 });
|
||||
await expect(page.getByRole("button", { name: "Models", exact: true })).toBeHidden();
|
||||
});
|
||||
|
||||
@ -14,7 +14,6 @@ import {
|
||||
showSessionUnavailable,
|
||||
} from "./lib/session-gate";
|
||||
import { IntegrationSetupPage } from "./pages/IntegrationSetup";
|
||||
import { LocalSettingsPage } from "./pages/LocalSettings";
|
||||
import { McpOAuthCallbackPage } from "./pages/McpOAuthCallback";
|
||||
import { ShellPage } from "./pages/Shell";
|
||||
|
||||
@ -30,9 +29,18 @@ const OnboardingPage = lazyWithRecovery("pages/Onboarding", () =>
|
||||
const WelcomePage = lazyWithRecovery("pages/Welcome", () =>
|
||||
import("./pages/Welcome").then((module) => ({ default: module.WelcomePage })),
|
||||
);
|
||||
const LocalSettingsPage = lazyWithRecovery("pages/LocalSettings", () =>
|
||||
import("./pages/LocalSettings").then((module) => ({ default: module.LocalSettingsPage })),
|
||||
);
|
||||
|
||||
export function App() {
|
||||
if (window.location.pathname === LOCAL_SETTINGS_PAGE) return <LocalSettingsPage />;
|
||||
if (window.location.pathname === LOCAL_SETTINGS_PAGE) {
|
||||
return (
|
||||
<Suspense fallback={<div className="h-full bg-background" data-rakazo-app-state="loading" />}>
|
||||
<LocalSettingsPage />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
return <SessionApp />;
|
||||
}
|
||||
|
||||
|
||||
@ -1,20 +1,31 @@
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import { Button } from "@rakazo/ui-web";
|
||||
import { useEffect, useState } from "react";
|
||||
import { IntegrationSetup } from "../components/integrations/IntegrationSetup";
|
||||
import { Suspense, useEffect, useState } from "react";
|
||||
import { desktopBridge } from "../lib/desktop";
|
||||
import { lazyWithRecovery } from "../lib/lazy-recovery";
|
||||
import { rpc } from "../lib/rpc";
|
||||
import { ModelSettingsOverlay } from "./ModelSettingsOverlay";
|
||||
|
||||
const IntegrationSetup = lazyWithRecovery("integrations/IntegrationSetup", () =>
|
||||
import("../components/integrations/IntegrationSetup").then((module) => ({
|
||||
default: module.IntegrationSetup,
|
||||
})),
|
||||
);
|
||||
const ModelSettingsOverlay = lazyWithRecovery("pages/ModelSettingsOverlay", () =>
|
||||
import("./ModelSettingsOverlay").then((module) => ({ default: module.ModelSettingsOverlay })),
|
||||
);
|
||||
|
||||
export function LocalSettingsPage() {
|
||||
const [section, setSection] = useState<"models" | "integrations" | null>(null);
|
||||
const hasLocalBridge = Boolean(desktopBridge()?.localSettings);
|
||||
const [ready, setReady] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const [error, setError] = useState(!hasLocalBridge);
|
||||
useEffect(() => {
|
||||
if (!hasLocalBridge) return;
|
||||
void rpc.integrationSetup
|
||||
.get()
|
||||
.then(() => setReady(true))
|
||||
.catch(() => setError(true));
|
||||
}, []);
|
||||
}, [hasLocalBridge]);
|
||||
return (
|
||||
<main className="h-full overflow-auto bg-background px-6 py-12">
|
||||
<div className="mx-auto max-w-xl space-y-6">
|
||||
@ -43,9 +54,15 @@ export function LocalSettingsPage() {
|
||||
</Button>
|
||||
</nav>
|
||||
{section === "models" ? (
|
||||
<Suspense fallback={null}>
|
||||
<ModelSettingsOverlay onClose={() => setSection(null)} localOwner />
|
||||
</Suspense>
|
||||
) : null}
|
||||
{section === "integrations" ? (
|
||||
<Suspense fallback={null}>
|
||||
<IntegrationSetup serverSetup managedOnly />
|
||||
</Suspense>
|
||||
) : null}
|
||||
{section === "integrations" ? <IntegrationSetup serverSetup managedOnly /> : null}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
47
infra/updater/src/ci-workflow.test.ts
Normal file
47
infra/updater/src/ci-workflow.test.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const workflowText = readFileSync(
|
||||
path.resolve(import.meta.dirname, "../../../.github/workflows/ci.yml"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
interface WorkflowJob {
|
||||
if?: string;
|
||||
needs?: string[];
|
||||
"continue-on-error"?: boolean;
|
||||
}
|
||||
|
||||
const workflow = parse(workflowText) as {
|
||||
jobs: Record<string, WorkflowJob>;
|
||||
};
|
||||
|
||||
describe("ci workflow merge gate", () => {
|
||||
it("aggregates only product jobs in Product CI", () => {
|
||||
const productGate = workflow.jobs["product-gate"];
|
||||
expect(productGate?.needs).toEqual([
|
||||
"lint",
|
||||
"check",
|
||||
"build",
|
||||
"test",
|
||||
"test-integration",
|
||||
"test-e2e",
|
||||
]);
|
||||
expect(productGate?.if).toBe("always()");
|
||||
});
|
||||
|
||||
it("soft-fails mobile OTA so missing Expo credentials do not fail the workflow", () => {
|
||||
const mobile = workflow.jobs["publish-mobile-update"];
|
||||
expect(mobile?.["continue-on-error"]).toBe(true);
|
||||
expect(workflowText).toContain('echo "ready=false" >> "$GITHUB_OUTPUT"');
|
||||
expect(workflowText).not.toMatch(
|
||||
/Check Expo automation token[\s\S]*?exit 1[\s\S]*?Require Expo/,
|
||||
);
|
||||
expect(workflowText).not.toContain(
|
||||
"Add an EXPO_TOKEN repository secret with access to the inbox-zero/rakazo Expo project.",
|
||||
);
|
||||
expect(workflowText).toContain("steps.expo_token.outputs.ready == 'true'");
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user