Skip to content

Commit 76d2221

Browse files
ci: commit demo workspace + pin bun version
The lockfile references packages/demo as a workspace member, but the directory was never committed — CI's `bun install --frozen-lockfile` fails because workspace resolution diverges from the lockfile. Pin bun-version in both workflows to match `packageManager` so future lockfile-format drift across bun majors doesn't silently break CI. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 9c00715 commit 76d2221

10 files changed

Lines changed: 459 additions & 3 deletions

File tree

.github/workflows/release.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ jobs:
3232
fetch-depth: 0
3333

3434
- name: Set up Bun
35+
# Pin to package.json `packageManager` — same reason as test.yaml.
3536
uses: oven-sh/setup-bun@v2
3637
with:
37-
bun-version: latest
38+
bun-version: "1.2.21"
3839

3940
- name: Set up Node.js
4041
uses: actions/setup-node@v4

.github/workflows/test.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ jobs:
1818
uses: actions/checkout@v4
1919

2020
- name: Set up Bun
21+
# Pin to the version in package.json `packageManager` so the
22+
# lockfile format matches and `bun install --frozen-lockfile`
23+
# never trips on bun-version drift. Bump intentionally.
2124
uses: oven-sh/setup-bun@v2
2225
with:
23-
bun-version: latest
26+
bun-version: "1.2.21"
2427

2528
- name: Install dependencies
2629
run: bun install --frozen-lockfile

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@
1818
"prettier": "^3.3.0",
1919
"typescript": "^5.6.0"
2020
},
21-
"packageManager": "bun@1.1.30"
21+
"packageManager": "bun@1.2.21"
2222
}

packages/demo/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Kernel Managed Auth — Demo</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/src/main.tsx"></script>
11+
</body>
12+
</html>

packages/demo/package.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "@onkernel/managed-auth-react-demo",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "vite build",
9+
"preview": "vite preview"
10+
},
11+
"dependencies": {
12+
"@onkernel/managed-auth-react": "workspace:*",
13+
"react": "^18.3.1",
14+
"react-dom": "^18.3.1"
15+
},
16+
"devDependencies": {
17+
"@types/react": "^18",
18+
"@types/react-dom": "^18",
19+
"@vitejs/plugin-react": "^4.3.0",
20+
"typescript": "^5.6.0",
21+
"vite": "^5.4.0"
22+
}
23+
}

packages/demo/src/App.tsx

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import { useMemo, useState } from "react";
2+
import {
3+
KernelManagedAuth,
4+
type AuthErrorPayload,
5+
type AuthSuccessPayload,
6+
type ManagedAuthResponse,
7+
} from "@onkernel/managed-auth-react";
8+
import "@onkernel/managed-auth-react/styles.css";
9+
import { createMockFetch } from "./mockFetch";
10+
11+
type Mode = "mock" | "live";
12+
13+
export function App() {
14+
const params = useMemo(() => new URLSearchParams(window.location.search), []);
15+
const initialMode: Mode = params.get("session") ? "live" : "mock";
16+
17+
const [mode, setMode] = useState<Mode>(initialMode);
18+
const [theme, setTheme] = useState<"light" | "dark" | "auto">("auto");
19+
const [domain, setDomain] = useState(params.get("domain") ?? "github.com");
20+
const [sessionId, setSessionId] = useState(
21+
params.get("session") ?? "demo-session-id",
22+
);
23+
const [handoffCode, setHandoffCode] = useState(
24+
params.get("code") ?? "demo-handoff-code",
25+
);
26+
const [event, setEvent] = useState<string | null>(null);
27+
28+
const mockFetch = useMemo(
29+
() => (mode === "mock" ? createMockFetch({ domain }) : undefined),
30+
[mode, domain],
31+
);
32+
33+
const handleSuccess = (p: AuthSuccessPayload) => {
34+
setEvent(`onSuccess → ${JSON.stringify(p)}`);
35+
};
36+
const handleError = (p: AuthErrorPayload) => {
37+
setEvent(`onError → ${JSON.stringify(p)}`);
38+
};
39+
40+
return (
41+
<div style={styles.page}>
42+
<aside style={styles.sidebar}>
43+
<h1 style={styles.h1}>@onkernel/managed-auth-react</h1>
44+
<p style={styles.muted}>
45+
A live render of <code>&lt;KernelManagedAuth /&gt;</code> exactly as a
46+
user would see it.
47+
</p>
48+
49+
<section style={styles.section}>
50+
<label style={styles.label}>Mode</label>
51+
<div style={styles.row}>
52+
<button
53+
style={mode === "mock" ? styles.btnActive : styles.btn}
54+
onClick={() => setMode("mock")}
55+
>
56+
Mock backend
57+
</button>
58+
<button
59+
style={mode === "live" ? styles.btnActive : styles.btn}
60+
onClick={() => setMode("live")}
61+
>
62+
Live (api.onkernel.com)
63+
</button>
64+
</div>
65+
<p style={styles.hint}>
66+
{mode === "mock"
67+
? "Uses an in-browser mock fetch so you can click through the entire flow without real Kernel credentials."
68+
: "Calls the real Kernel API. Provide a valid sessionId + handoffCode below."}
69+
</p>
70+
</section>
71+
72+
{mode === "mock" ? (
73+
<section style={styles.section}>
74+
<label style={styles.label}>Target domain</label>
75+
<input
76+
style={styles.input}
77+
value={domain}
78+
onChange={(e) => setDomain(e.target.value)}
79+
placeholder="github.com"
80+
/>
81+
<p style={styles.hint}>
82+
Drives the mocked discovery response (site name, icon, MFA
83+
fan-out).
84+
</p>
85+
</section>
86+
) : (
87+
<section style={styles.section}>
88+
<label style={styles.label}>Session ID</label>
89+
<input
90+
style={styles.input}
91+
value={sessionId}
92+
onChange={(e) => setSessionId(e.target.value)}
93+
/>
94+
<label style={{ ...styles.label, marginTop: 12 }}>
95+
Handoff code
96+
</label>
97+
<input
98+
style={styles.input}
99+
value={handoffCode}
100+
onChange={(e) => setHandoffCode(e.target.value)}
101+
/>
102+
</section>
103+
)}
104+
105+
<section style={styles.section}>
106+
<label style={styles.label}>Theme</label>
107+
<div style={styles.row}>
108+
{(["light", "dark", "auto"] as const).map((t) => (
109+
<button
110+
key={t}
111+
style={theme === t ? styles.btnActive : styles.btn}
112+
onClick={() => setTheme(t)}
113+
>
114+
{t}
115+
</button>
116+
))}
117+
</div>
118+
</section>
119+
120+
<section style={styles.section}>
121+
<label style={styles.label}>Last event</label>
122+
<pre style={styles.event}>{event ?? "(none yet)"}</pre>
123+
</section>
124+
</aside>
125+
126+
<main style={styles.stage}>
127+
<div style={styles.surface} data-theme={theme}>
128+
<KernelManagedAuth
129+
key={`${mode}-${domain}-${sessionId}-${handoffCode}`}
130+
sessionId={sessionId}
131+
handoffCode={handoffCode}
132+
appearance={{ theme }}
133+
onSuccess={handleSuccess}
134+
onError={handleError}
135+
fetch={mockFetch as typeof fetch | undefined}
136+
/>
137+
</div>
138+
</main>
139+
</div>
140+
);
141+
}
142+
143+
const styles: Record<string, React.CSSProperties> = {
144+
page: {
145+
display: "grid",
146+
gridTemplateColumns: "360px 1fr",
147+
minHeight: "100vh",
148+
fontFamily:
149+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif',
150+
background: "#0b0d12",
151+
color: "#e6e8ee",
152+
},
153+
sidebar: {
154+
padding: 24,
155+
borderRight: "1px solid #1d2230",
156+
background: "#0f121a",
157+
overflowY: "auto",
158+
},
159+
stage: {
160+
display: "flex",
161+
alignItems: "center",
162+
justifyContent: "center",
163+
padding: 32,
164+
background:
165+
"radial-gradient(1200px 600px at 50% -10%, rgba(99,102,241,0.18), transparent 60%), #0b0d12",
166+
},
167+
surface: {
168+
width: "100%",
169+
maxWidth: 460,
170+
},
171+
h1: { fontSize: 16, margin: "0 0 8px", fontWeight: 600 },
172+
muted: {
173+
fontSize: 13,
174+
color: "#8a93a6",
175+
margin: "0 0 20px",
176+
lineHeight: 1.5,
177+
},
178+
section: { marginBottom: 20 },
179+
label: {
180+
display: "block",
181+
fontSize: 11,
182+
textTransform: "uppercase",
183+
letterSpacing: "0.08em",
184+
color: "#8a93a6",
185+
marginBottom: 8,
186+
},
187+
hint: { fontSize: 12, color: "#6b7387", marginTop: 8, lineHeight: 1.5 },
188+
row: { display: "flex", gap: 6, flexWrap: "wrap" },
189+
btn: {
190+
padding: "6px 10px",
191+
fontSize: 12,
192+
background: "#161b27",
193+
color: "#cfd4e1",
194+
border: "1px solid #232a3a",
195+
borderRadius: 6,
196+
cursor: "pointer",
197+
},
198+
btnActive: {
199+
padding: "6px 10px",
200+
fontSize: 12,
201+
background: "#3b82f6",
202+
color: "white",
203+
border: "1px solid #3b82f6",
204+
borderRadius: 6,
205+
cursor: "pointer",
206+
},
207+
input: {
208+
width: "100%",
209+
padding: "8px 10px",
210+
fontSize: 13,
211+
background: "#161b27",
212+
color: "#e6e8ee",
213+
border: "1px solid #232a3a",
214+
borderRadius: 6,
215+
boxSizing: "border-box",
216+
},
217+
event: {
218+
margin: 0,
219+
padding: 10,
220+
background: "#0a0d14",
221+
border: "1px solid #1d2230",
222+
borderRadius: 6,
223+
fontSize: 12,
224+
color: "#cfd4e1",
225+
whiteSpace: "pre-wrap",
226+
wordBreak: "break-all",
227+
},
228+
};
229+
230+
// Re-export the type for explicit typing in styles map.
231+
export type { ManagedAuthResponse };

packages/demo/src/main.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from "react";
2+
import { createRoot } from "react-dom/client";
3+
import { App } from "./App";
4+
5+
const container = document.getElementById("root");
6+
if (!container) throw new Error("#root not found");
7+
createRoot(container).render(
8+
<React.StrictMode>
9+
<App />
10+
</React.StrictMode>,
11+
);

0 commit comments

Comments
 (0)