blodemd 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -47
- package/dev-server/app/layout.tsx +1 -1
- package/dev-server/next.config.js +19 -9
- package/dev-server/tsconfig.json +0 -3
- package/dist/cli.mjs +732 -123
- package/dist/cli.mjs.map +1 -1
- package/docs/app/globals.css +15 -1
- package/docs/components/api/api-playground.tsx +2 -2
- package/docs/components/docs/copy-page-menu.tsx +55 -27
- package/docs/components/docs/doc-header.tsx +1 -1
- package/docs/components/docs/doc-shell.tsx +89 -88
- package/docs/components/docs/doc-sidebar.tsx +6 -3
- package/docs/components/docs/doc-toc.tsx +1 -1
- package/docs/components/docs/mobile-nav.tsx +8 -16
- package/docs/components/docs/sidebar-scroll-area.tsx +58 -0
- package/docs/components/git/repo-picker.tsx +526 -0
- package/docs/components/mdx/agent-instructions.tsx +17 -0
- package/docs/components/mdx/code-block.tsx +6 -1
- package/docs/components/mdx/code-group.tsx +1 -1
- package/docs/components/mdx/iframe.tsx +62 -0
- package/docs/components/mdx/index.tsx +4 -0
- package/docs/components/mdx/tabs.tsx +5 -5
- package/docs/components/mdx/video.tsx +45 -12
- package/docs/components/third-parties.tsx +29 -0
- package/docs/components/ui/badge.tsx +61 -0
- package/docs/components/ui/breadcrumb.tsx +61 -41
- package/docs/components/ui/button-group.tsx +83 -0
- package/docs/components/ui/button.tsx +30 -55
- package/docs/components/ui/command.tsx +32 -4
- package/docs/components/ui/copy-button.tsx +12 -19
- package/docs/components/ui/dialog.tsx +50 -1
- package/docs/components/ui/input.tsx +16 -97
- package/docs/components/ui/kbd.tsx +98 -0
- package/docs/components/ui/morph-icon.tsx +79 -0
- package/docs/components/ui/popover.tsx +225 -30
- package/docs/components/ui/search.tsx +0 -9
- package/docs/components/ui/sheet.tsx +30 -1
- package/docs/components/ui/sidebar.tsx +332 -7
- package/docs/components/ui/site-footer.tsx +6 -4
- package/docs/components/ui/skeleton.tsx +11 -0
- package/docs/components/ui/switch.tsx +32 -0
- package/docs/components/ui/tabs.tsx +138 -0
- package/docs/lib/api-client.ts +72 -0
- package/docs/lib/contextual-options.ts +9 -0
- package/docs/lib/dashboard-session.ts +167 -0
- package/docs/lib/db.ts +13 -0
- package/docs/lib/env.ts +4 -3
- package/docs/lib/etag.ts +22 -0
- package/docs/lib/github-install.ts +33 -0
- package/docs/lib/project-authz.ts +46 -0
- package/docs/lib/routes.ts +5 -1
- package/docs/lib/supabase.ts +30 -6
- package/docs/lib/tenancy.ts +1 -0
- package/docs/lib/tenant-static.ts +206 -4
- package/docs/lib/tenants.ts +5 -1
- package/docs/lib/time-ago.ts +24 -0
- package/docs/lib/use-tab-observer.ts +71 -0
- package/package.json +3 -1
- package/packages/@repo/common/package.json +2 -2
- package/packages/@repo/contracts/dist/git.d.ts +28 -0
- package/packages/@repo/contracts/dist/git.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/git.js +24 -0
- package/packages/@repo/contracts/dist/index.d.ts +1 -1
- package/packages/@repo/contracts/dist/index.d.ts.map +1 -1
- package/packages/@repo/contracts/dist/index.js +1 -1
- package/packages/@repo/contracts/package.json +2 -2
- package/packages/@repo/contracts/src/git.ts +31 -0
- package/packages/@repo/contracts/src/index.ts +1 -1
- package/packages/@repo/models/dist/docs-config.d.ts +6 -0
- package/packages/@repo/models/dist/docs-config.d.ts.map +1 -1
- package/packages/@repo/models/dist/docs-config.js +1 -0
- package/packages/@repo/models/package.json +2 -2
- package/packages/@repo/models/src/docs-config.ts +1 -0
- package/packages/@repo/prebuild/package.json +2 -2
- package/packages/@repo/previewing/dist/index.d.ts +3 -0
- package/packages/@repo/previewing/dist/index.d.ts.map +1 -1
- package/packages/@repo/previewing/dist/index.js +48 -0
- package/packages/@repo/previewing/package.json +2 -2
- package/packages/@repo/previewing/src/index.ts +56 -0
- package/packages/@repo/validation/package.json +2 -2
- package/packages/@repo/validation/src/blodemd-docs-schema.json +1 -0
- package/scripts/prepare-package.mjs +14 -0
- package/packages/@repo/contracts/dist/api-key.d.ts +0 -30
- package/packages/@repo/contracts/dist/api-key.d.ts.map +0 -1
- package/packages/@repo/contracts/dist/api-key.js +0 -20
- package/packages/@repo/contracts/src/api-key.ts +0 -27
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { docsApiBase } from "./env";
|
|
2
|
+
|
|
3
|
+
interface ApiRequestOptions extends Omit<RequestInit, "body" | "headers"> {
|
|
4
|
+
accessToken?: string | null;
|
|
5
|
+
body?: unknown;
|
|
6
|
+
headers?: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class ApiError extends Error {
|
|
10
|
+
status: number;
|
|
11
|
+
details?: unknown;
|
|
12
|
+
|
|
13
|
+
constructor(message: string, status: number, details?: unknown) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "ApiError";
|
|
16
|
+
this.status = status;
|
|
17
|
+
this.details = details;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const buildUrl = (path: string): string => {
|
|
22
|
+
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
23
|
+
return path;
|
|
24
|
+
}
|
|
25
|
+
const url = new URL(path.startsWith("/") ? path : `/${path}`, docsApiBase);
|
|
26
|
+
return url.toString();
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const apiFetch = async <T = unknown>(
|
|
30
|
+
path: string,
|
|
31
|
+
options: ApiRequestOptions = {}
|
|
32
|
+
): Promise<T> => {
|
|
33
|
+
const { accessToken, body, headers, ...rest } = options;
|
|
34
|
+
const finalHeaders: Record<string, string> = {
|
|
35
|
+
Accept: "application/json",
|
|
36
|
+
...headers,
|
|
37
|
+
};
|
|
38
|
+
if (accessToken) {
|
|
39
|
+
finalHeaders.Authorization = `Bearer ${accessToken}`;
|
|
40
|
+
}
|
|
41
|
+
let serializedBody: string | undefined;
|
|
42
|
+
if (body !== undefined) {
|
|
43
|
+
finalHeaders["Content-Type"] ??= "application/json";
|
|
44
|
+
serializedBody = JSON.stringify(body);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const response = await fetch(buildUrl(path), {
|
|
48
|
+
...rest,
|
|
49
|
+
body: serializedBody,
|
|
50
|
+
headers: finalHeaders,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const text = await response.text();
|
|
54
|
+
let json: unknown;
|
|
55
|
+
if (text) {
|
|
56
|
+
try {
|
|
57
|
+
json = JSON.parse(text);
|
|
58
|
+
} catch {
|
|
59
|
+
json = text;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
const message =
|
|
65
|
+
typeof json === "object" && json && "error" in json
|
|
66
|
+
? String((json as { error: unknown }).error)
|
|
67
|
+
: `Request failed: ${response.status}`;
|
|
68
|
+
throw new ApiError(message, response.status, json);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return json as T;
|
|
72
|
+
};
|
|
@@ -69,6 +69,12 @@ export const builtinOptions: Record<
|
|
|
69
69
|
title: "Connect to Devin",
|
|
70
70
|
type: "link",
|
|
71
71
|
},
|
|
72
|
+
gemini: {
|
|
73
|
+
description: "Ask questions about this page",
|
|
74
|
+
iconName: "GoogleColoredIcon",
|
|
75
|
+
title: "Open in Gemini",
|
|
76
|
+
type: "link",
|
|
77
|
+
},
|
|
72
78
|
grok: {
|
|
73
79
|
description: "Ask questions about this page",
|
|
74
80
|
iconName: "GrokIcon",
|
|
@@ -160,6 +166,9 @@ export const buildBuiltinUrl = (
|
|
|
160
166
|
case "aistudio": {
|
|
161
167
|
return `https://aistudio.google.com/prompts/new_chat?q=${encoded(askPrompt(pageUrl))}`;
|
|
162
168
|
}
|
|
169
|
+
case "gemini": {
|
|
170
|
+
return `https://gemini.google.com/app?q=${encoded(askPrompt(pageUrl))}`;
|
|
171
|
+
}
|
|
163
172
|
case "devin": {
|
|
164
173
|
return `https://app.devin.ai/sessions?url=${encoded(pageUrl)}`;
|
|
165
174
|
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRemoteJWKSet,
|
|
3
|
+
decodeProtectedHeader,
|
|
4
|
+
errors as joseErrors,
|
|
5
|
+
jwtVerify,
|
|
6
|
+
} from "jose";
|
|
7
|
+
import { cookies } from "next/headers";
|
|
8
|
+
import { cache } from "react";
|
|
9
|
+
|
|
10
|
+
export interface DashboardSession {
|
|
11
|
+
accessToken: string;
|
|
12
|
+
authId: string;
|
|
13
|
+
userEmail: string;
|
|
14
|
+
userName: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL ?? "";
|
|
18
|
+
const supabaseJwtSecret = process.env.SUPABASE_JWT_SECRET ?? "";
|
|
19
|
+
|
|
20
|
+
let cachedSecret: Uint8Array | null = null;
|
|
21
|
+
const getHmacKey = (): Uint8Array | null => {
|
|
22
|
+
if (!supabaseJwtSecret) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
if (!cachedSecret) {
|
|
26
|
+
cachedSecret = new TextEncoder().encode(supabaseJwtSecret);
|
|
27
|
+
}
|
|
28
|
+
return cachedSecret;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
let cachedJwks: ReturnType<typeof createRemoteJWKSet> | null = null;
|
|
32
|
+
const getJwks = () => {
|
|
33
|
+
if (!supabaseUrl) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
if (!cachedJwks) {
|
|
37
|
+
cachedJwks = createRemoteJWKSet(
|
|
38
|
+
new URL(`${supabaseUrl.replace(/\/$/, "")}/auth/v1/.well-known/jwks.json`)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return cachedJwks;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const getIssuer = (): string | undefined =>
|
|
45
|
+
supabaseUrl ? `${supabaseUrl.replace(/\/$/, "")}/auth/v1` : undefined;
|
|
46
|
+
|
|
47
|
+
const verifyAccessToken = (token: string) => {
|
|
48
|
+
const header = decodeProtectedHeader(token);
|
|
49
|
+
const options = {
|
|
50
|
+
audience: "authenticated",
|
|
51
|
+
clockTolerance: "5s",
|
|
52
|
+
issuer: getIssuer(),
|
|
53
|
+
};
|
|
54
|
+
if (header.alg === "HS256") {
|
|
55
|
+
const secret = getHmacKey();
|
|
56
|
+
if (!secret) {
|
|
57
|
+
throw new Error("SUPABASE_JWT_SECRET required for HS256 tokens.");
|
|
58
|
+
}
|
|
59
|
+
return jwtVerify(token, secret, { ...options, algorithms: ["HS256"] });
|
|
60
|
+
}
|
|
61
|
+
const jwks = getJwks();
|
|
62
|
+
if (!jwks) {
|
|
63
|
+
throw new Error("SUPABASE_URL required for asymmetric token verification.");
|
|
64
|
+
}
|
|
65
|
+
return jwtVerify(token, jwks, options);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Supabase's auth-helpers cookie name is derived from the project ref. We
|
|
69
|
+
// match the default pattern the browser client writes: `sb-<ref>-auth-token`.
|
|
70
|
+
const getProjectRef = (): string | null => {
|
|
71
|
+
if (!supabaseUrl) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const match = supabaseUrl.match(/https?:\/\/([^.]+)\./);
|
|
75
|
+
return match?.[1] ?? null;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
interface StoredSupabaseSession {
|
|
79
|
+
access_token?: unknown;
|
|
80
|
+
user?: {
|
|
81
|
+
email?: unknown;
|
|
82
|
+
user_metadata?: { full_name?: unknown; name?: unknown };
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const parseCookieValue = (value: string): StoredSupabaseSession | null => {
|
|
87
|
+
// Supabase SSR stores either a JSON string or an array; both are URL-encoded.
|
|
88
|
+
try {
|
|
89
|
+
const decoded = value.startsWith("base64-")
|
|
90
|
+
? Buffer.from(value.slice(7), "base64").toString("utf8")
|
|
91
|
+
: decodeURIComponent(value);
|
|
92
|
+
const parsed = JSON.parse(decoded);
|
|
93
|
+
if (Array.isArray(parsed)) {
|
|
94
|
+
const [sessionBlob] = parsed;
|
|
95
|
+
return typeof sessionBlob === "object"
|
|
96
|
+
? (sessionBlob as StoredSupabaseSession)
|
|
97
|
+
: null;
|
|
98
|
+
}
|
|
99
|
+
return parsed as StoredSupabaseSession;
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const readChunkedCookie = async (
|
|
106
|
+
baseName: string
|
|
107
|
+
): Promise<StoredSupabaseSession | null> => {
|
|
108
|
+
const store = await cookies();
|
|
109
|
+
const direct = store.get(baseName)?.value;
|
|
110
|
+
if (direct) {
|
|
111
|
+
return parseCookieValue(direct);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Supabase splits large cookies into baseName.0, baseName.1, ...
|
|
115
|
+
const chunks: string[] = [];
|
|
116
|
+
for (let i = 0; i < 10; i += 1) {
|
|
117
|
+
const chunk = store.get(`${baseName}.${i}`)?.value;
|
|
118
|
+
if (!chunk) {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
chunks.push(chunk);
|
|
122
|
+
}
|
|
123
|
+
if (chunks.length === 0) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return parseCookieValue(chunks.join(""));
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const asString = (value: unknown): string | null =>
|
|
130
|
+
typeof value === "string" ? value : null;
|
|
131
|
+
|
|
132
|
+
export const getDashboardSession = cache(
|
|
133
|
+
async (): Promise<DashboardSession | null> => {
|
|
134
|
+
const ref = getProjectRef();
|
|
135
|
+
if (!ref) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const stored = await readChunkedCookie(`sb-${ref}-auth-token`);
|
|
140
|
+
const accessToken = asString(stored?.access_token);
|
|
141
|
+
if (!accessToken) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let authId: string;
|
|
146
|
+
try {
|
|
147
|
+
const { payload } = await verifyAccessToken(accessToken);
|
|
148
|
+
const sub = asString(payload.sub);
|
|
149
|
+
if (!sub) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
authId = sub;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (!(error instanceof joseErrors.JOSEError)) {
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const userEmail = asString(stored?.user?.email) ?? "";
|
|
161
|
+
const metadata = stored?.user?.user_metadata;
|
|
162
|
+
const userName =
|
|
163
|
+
asString(metadata?.full_name) ?? asString(metadata?.name) ?? userEmail;
|
|
164
|
+
|
|
165
|
+
return { accessToken, authId, userEmail, userName };
|
|
166
|
+
}
|
|
167
|
+
);
|
package/docs/lib/db.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeploymentDao,
|
|
3
|
+
DomainDao,
|
|
4
|
+
GitConnectionDao,
|
|
5
|
+
ProjectDao,
|
|
6
|
+
UserDao,
|
|
7
|
+
} from "@repo/db";
|
|
8
|
+
|
|
9
|
+
export const projectDao = new ProjectDao();
|
|
10
|
+
export const deploymentDao = new DeploymentDao();
|
|
11
|
+
export const userDao = new UserDao();
|
|
12
|
+
export const gitConnectionDao = new GitConnectionDao();
|
|
13
|
+
export const domainDao = new DomainDao();
|
package/docs/lib/env.ts
CHANGED
|
@@ -10,10 +10,11 @@ const readTrimmedEnv = (name: string) => {
|
|
|
10
10
|
return trimmed;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
// Must reference process.env.NEXT_PUBLIC_API_URL as a literal so Next.js
|
|
14
|
+
// inlines it into the client bundle. Dynamic access via readTrimmedEnv is
|
|
15
|
+
// not replaced at build time and would always be undefined on the client.
|
|
13
16
|
export const docsApiBase =
|
|
14
|
-
|
|
15
|
-
readTrimmedEnv("NEXT_PUBLIC_API_URL") ??
|
|
16
|
-
"http://localhost:4000";
|
|
17
|
+
process.env.NEXT_PUBLIC_API_URL?.trim() || "http://localhost:4000";
|
|
17
18
|
|
|
18
19
|
export const platformAssetPrefix =
|
|
19
20
|
readTrimmedEnv("PLATFORM_ASSET_PREFIX") ?? "";
|
package/docs/lib/etag.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { NextResponse } from "next/server";
|
|
4
|
+
|
|
5
|
+
export const computeETag = (content: string): string => {
|
|
6
|
+
const hash = createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
7
|
+
return `"${hash}"`;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const handleIfNoneMatch = (
|
|
11
|
+
request: Request,
|
|
12
|
+
etag: string
|
|
13
|
+
): NextResponse | null => {
|
|
14
|
+
const ifNoneMatch = request.headers.get("if-none-match");
|
|
15
|
+
if (ifNoneMatch && ifNoneMatch === etag) {
|
|
16
|
+
return new NextResponse(null, {
|
|
17
|
+
headers: { ETag: etag },
|
|
18
|
+
status: 304,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { apiFetch } from "@/lib/api-client";
|
|
2
|
+
|
|
3
|
+
export const GITHUB_INSTALL_STATE_KEY = "blodemd:install-state";
|
|
4
|
+
|
|
5
|
+
export interface PendingGithubInstall {
|
|
6
|
+
projectId: string;
|
|
7
|
+
projectSlug: string;
|
|
8
|
+
state: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const startGithubInstall = async ({
|
|
12
|
+
accessToken,
|
|
13
|
+
projectId,
|
|
14
|
+
projectSlug,
|
|
15
|
+
}: {
|
|
16
|
+
accessToken: string;
|
|
17
|
+
projectId: string;
|
|
18
|
+
projectSlug: string;
|
|
19
|
+
}): Promise<void> => {
|
|
20
|
+
const result = await apiFetch<{ url: string; state: string }>(
|
|
21
|
+
`/projects/${projectId}/git/install-url`,
|
|
22
|
+
{ accessToken, method: "POST" }
|
|
23
|
+
);
|
|
24
|
+
sessionStorage.setItem(
|
|
25
|
+
GITHUB_INSTALL_STATE_KEY,
|
|
26
|
+
JSON.stringify({
|
|
27
|
+
projectId,
|
|
28
|
+
projectSlug,
|
|
29
|
+
state: result.state,
|
|
30
|
+
})
|
|
31
|
+
);
|
|
32
|
+
window.location.assign(result.url);
|
|
33
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ProjectRecord, UserRecord } from "@repo/db";
|
|
2
|
+
import { cache } from "react";
|
|
3
|
+
|
|
4
|
+
import { getDashboardSession } from "./dashboard-session";
|
|
5
|
+
import { projectDao, userDao } from "./db";
|
|
6
|
+
|
|
7
|
+
export interface AuthorizedProjectContext {
|
|
8
|
+
accessToken: string;
|
|
9
|
+
project: ProjectRecord;
|
|
10
|
+
user: UserRecord;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const resolveCurrentUser = cache(
|
|
14
|
+
async (): Promise<{
|
|
15
|
+
accessToken: string;
|
|
16
|
+
user: UserRecord;
|
|
17
|
+
} | null> => {
|
|
18
|
+
const session = await getDashboardSession();
|
|
19
|
+
if (!session) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const user = await userDao.getByAuthId(session.authId);
|
|
23
|
+
if (!user) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return { accessToken: session.accessToken, user };
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
export const getAuthorizedProjectBySlug = cache(
|
|
31
|
+
async (slug: string): Promise<AuthorizedProjectContext | null> => {
|
|
32
|
+
const session = await getDashboardSession();
|
|
33
|
+
if (!session) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const result = await projectDao.getAuthorizedBySlug(session.authId, slug);
|
|
37
|
+
if (!result) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
accessToken: session.accessToken,
|
|
42
|
+
project: result.project,
|
|
43
|
+
user: result.user,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
);
|
package/docs/lib/routes.ts
CHANGED
|
@@ -27,7 +27,11 @@ export const toDocHref = (path: string, basePath = "") => {
|
|
|
27
27
|
|
|
28
28
|
export const toMarkdownDocHref = (path: string, basePath = "") => {
|
|
29
29
|
const href = toDocHref(path, basePath);
|
|
30
|
-
|
|
30
|
+
// Index pages use /index.md, not just .md appended to the path
|
|
31
|
+
if (href === "/" || href === basePath) {
|
|
32
|
+
return `${href}/index.md`.replaceAll(/\/+/g, "/");
|
|
33
|
+
}
|
|
34
|
+
return `${href}.md`;
|
|
31
35
|
};
|
|
32
36
|
|
|
33
37
|
export const getMarkdownExportSourcePath = (pathname: string) => {
|
package/docs/lib/supabase.ts
CHANGED
|
@@ -1,13 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createBrowserClient, createServerClient } from "@supabase/ssr";
|
|
2
|
+
import type { CookieOptions } from "@supabase/ssr";
|
|
3
|
+
import type { SupabaseClient } from "@supabase/supabase-js";
|
|
2
4
|
|
|
3
5
|
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL ?? "";
|
|
4
6
|
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? "";
|
|
5
7
|
|
|
6
|
-
let
|
|
8
|
+
let browserClient: SupabaseClient | null = null;
|
|
7
9
|
|
|
8
|
-
export const createSupabaseClient = () => {
|
|
9
|
-
if (!
|
|
10
|
-
|
|
10
|
+
export const createSupabaseClient = (): SupabaseClient => {
|
|
11
|
+
if (!browserClient) {
|
|
12
|
+
browserClient = createBrowserClient(supabaseUrl, supabaseAnonKey);
|
|
11
13
|
}
|
|
12
|
-
return
|
|
14
|
+
return browserClient;
|
|
13
15
|
};
|
|
16
|
+
|
|
17
|
+
interface CookieStore {
|
|
18
|
+
get: (name: string) => { value: string } | undefined;
|
|
19
|
+
set?: (name: string, value: string, options?: CookieOptions) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const createSupabaseServerClient = (
|
|
23
|
+
cookieStore: CookieStore
|
|
24
|
+
): SupabaseClient =>
|
|
25
|
+
createServerClient(supabaseUrl, supabaseAnonKey, {
|
|
26
|
+
cookies: {
|
|
27
|
+
get(name: string) {
|
|
28
|
+
return cookieStore.get(name)?.value;
|
|
29
|
+
},
|
|
30
|
+
remove(name: string, options?: CookieOptions) {
|
|
31
|
+
cookieStore.set?.(name, "", { ...options, maxAge: 0 });
|
|
32
|
+
},
|
|
33
|
+
set(name: string, value: string, options?: CookieOptions) {
|
|
34
|
+
cookieStore.set?.(name, value, options);
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
});
|