blodemd 0.0.11 → 0.0.12
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/dist/cli.mjs +708 -120
- 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 +1 -1
- 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/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/src/docs-config.ts +1 -0
- 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/src/index.ts +56 -0
- 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
package/dist/cli.mjs
CHANGED
|
@@ -3,8 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import { spawn, spawnSync } from "node:child_process";
|
|
4
4
|
import fs, { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
5
|
import path, { join } from "node:path";
|
|
6
|
-
import { confirm, intro, isCancel, log,
|
|
7
|
-
import { shouldIgnoreRootDocsFile, slugify } from "@repo/common";
|
|
6
|
+
import { confirm, intro, isCancel, log, select, spinner, text } from "@clack/prompts";
|
|
8
7
|
import chalk from "chalk";
|
|
9
8
|
import { Command, InvalidArgumentError } from "commander";
|
|
10
9
|
import open from "open";
|
|
@@ -13,11 +12,34 @@ import { once } from "node:events";
|
|
|
13
12
|
import { createServer } from "node:net";
|
|
14
13
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
15
14
|
import { fileURLToPath } from "node:url";
|
|
16
|
-
import {
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
import "yaml";
|
|
17
17
|
import { watch } from "chokidar";
|
|
18
18
|
import { createServer as createServer$1 } from "node:http";
|
|
19
19
|
import { createHash, randomBytes } from "node:crypto";
|
|
20
20
|
import { readFileSync } from "node:fs";
|
|
21
|
+
//#region ../../packages/common/dist/index.js
|
|
22
|
+
const BACKSLASH_TO_SLASH_REGEX = /\\/g;
|
|
23
|
+
const TRAILING_SLASHES_REGEX = /\/+$/g;
|
|
24
|
+
const LEADING_SLASHES_REGEX = /^\/+/;
|
|
25
|
+
const normalizePath = (value) => {
|
|
26
|
+
return value.replace(BACKSLASH_TO_SLASH_REGEX, "/").replace(TRAILING_SLASHES_REGEX, "").replace(LEADING_SLASHES_REGEX, "");
|
|
27
|
+
};
|
|
28
|
+
const slugify = (value) => value.toLowerCase().trim().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/(^-|-$)+/g, "");
|
|
29
|
+
const IGNORED_ROOT_DOCS_FILES = new Set([
|
|
30
|
+
".gitignore",
|
|
31
|
+
"AGENTS.md",
|
|
32
|
+
"CLAUDE.md",
|
|
33
|
+
"LICENSE",
|
|
34
|
+
"LICENSE.md",
|
|
35
|
+
"README.md"
|
|
36
|
+
]);
|
|
37
|
+
const shouldIgnoreRootDocsFile = (value) => {
|
|
38
|
+
const normalized = normalizePath(value);
|
|
39
|
+
if (!normalized || normalized.includes("/")) return false;
|
|
40
|
+
return IGNORED_ROOT_DOCS_FILES.has(normalized);
|
|
41
|
+
};
|
|
42
|
+
//#endregion
|
|
21
43
|
//#region src/constants.ts
|
|
22
44
|
const CLI_NAME = "blodemd";
|
|
23
45
|
const BLODE_PROJECT_ENV = "BLODEMD_PROJECT";
|
|
@@ -31,30 +53,6 @@ const getDefaultConfigBaseDir = () => {
|
|
|
31
53
|
const CONFIG_DIR = join(getDefaultConfigBaseDir(), CLI_NAME);
|
|
32
54
|
const CREDENTIALS_FILE = join(CONFIG_DIR, "credentials.json");
|
|
33
55
|
//#endregion
|
|
34
|
-
//#region src/jwt.ts
|
|
35
|
-
const parseJwtBase64Url = (input) => {
|
|
36
|
-
const normalized = input.replaceAll("-", "+").replaceAll("_", "/");
|
|
37
|
-
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
|
|
38
|
-
return Buffer.from(padded, "base64").toString("utf8");
|
|
39
|
-
};
|
|
40
|
-
const parseJwtClaims = (token) => {
|
|
41
|
-
const payloadPart = token.split(".").at(1);
|
|
42
|
-
if (!payloadPart) return null;
|
|
43
|
-
try {
|
|
44
|
-
const payload = parseJwtBase64Url(payloadPart);
|
|
45
|
-
const parsed = JSON.parse(payload);
|
|
46
|
-
if (typeof parsed !== "object" || parsed === null) return null;
|
|
47
|
-
const claims = parsed;
|
|
48
|
-
return {
|
|
49
|
-
email: typeof claims.email === "string" ? claims.email : void 0,
|
|
50
|
-
exp: typeof claims.exp === "number" ? claims.exp : void 0,
|
|
51
|
-
sub: typeof claims.sub === "string" ? claims.sub : void 0
|
|
52
|
-
};
|
|
53
|
-
} catch {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
//#endregion
|
|
58
56
|
//#region src/errors.ts
|
|
59
57
|
const EXIT_CODES = {
|
|
60
58
|
AUTH_REQUIRED: 4,
|
|
@@ -150,14 +148,6 @@ const parseStoredAuthSession = (value) => {
|
|
|
150
148
|
user
|
|
151
149
|
};
|
|
152
150
|
};
|
|
153
|
-
const parseApiKeyCredentials = (value) => {
|
|
154
|
-
if (!isRecord(value)) return null;
|
|
155
|
-
if (typeof value.apiKey !== "string") return null;
|
|
156
|
-
return {
|
|
157
|
-
apiKey: value.apiKey,
|
|
158
|
-
type: "api-key"
|
|
159
|
-
};
|
|
160
|
-
};
|
|
161
151
|
const createInvalidCredentialsError = (detail) => new CliError(detail ? `Invalid credentials format in ${CREDENTIALS_FILE}: ${detail}` : `Invalid credentials format in ${CREDENTIALS_FILE}`, EXIT_CODES.ERROR);
|
|
162
152
|
const parseAuthFile = (raw) => {
|
|
163
153
|
let parsed;
|
|
@@ -168,13 +158,9 @@ const parseAuthFile = (raw) => {
|
|
|
168
158
|
}
|
|
169
159
|
if (!isRecord(parsed) || parsed.version !== 1) throw createInvalidCredentialsError();
|
|
170
160
|
const hasSession = Object.hasOwn(parsed, "session");
|
|
171
|
-
const hasApiKey = Object.hasOwn(parsed, "apiKey");
|
|
172
161
|
const session = hasSession && parsed.session !== void 0 ? parseStoredAuthSession(parsed.session) : void 0;
|
|
173
|
-
const apiKey = hasApiKey && parsed.apiKey !== void 0 ? parseApiKeyCredentials(parsed.apiKey) : void 0;
|
|
174
162
|
if (hasSession && parsed.session !== void 0 && !session) throw createInvalidCredentialsError("stored session is malformed.");
|
|
175
|
-
if (hasApiKey && parsed.apiKey !== void 0 && !apiKey) throw createInvalidCredentialsError("stored API key is malformed.");
|
|
176
163
|
return {
|
|
177
|
-
apiKey: apiKey ?? void 0,
|
|
178
164
|
session: session ?? void 0,
|
|
179
165
|
version: 1
|
|
180
166
|
};
|
|
@@ -204,16 +190,34 @@ const writeStoredAuthSession = async (session) => {
|
|
|
204
190
|
version: 1
|
|
205
191
|
});
|
|
206
192
|
};
|
|
207
|
-
const writeStoredApiKey = async (apiKey) => {
|
|
208
|
-
await writeAuthFile({
|
|
209
|
-
apiKey,
|
|
210
|
-
version: 1
|
|
211
|
-
});
|
|
212
|
-
};
|
|
213
193
|
const clearStoredCredentials = async () => {
|
|
214
194
|
await rm(CREDENTIALS_FILE, { force: true });
|
|
215
195
|
};
|
|
216
196
|
//#endregion
|
|
197
|
+
//#region src/jwt.ts
|
|
198
|
+
const parseJwtBase64Url = (input) => {
|
|
199
|
+
const normalized = input.replaceAll("-", "+").replaceAll("_", "/");
|
|
200
|
+
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
|
|
201
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
202
|
+
};
|
|
203
|
+
const parseJwtClaims = (token) => {
|
|
204
|
+
const payloadPart = token.split(".").at(1);
|
|
205
|
+
if (!payloadPart) return null;
|
|
206
|
+
try {
|
|
207
|
+
const payload = parseJwtBase64Url(payloadPart);
|
|
208
|
+
const parsed = JSON.parse(payload);
|
|
209
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
210
|
+
const claims = parsed;
|
|
211
|
+
return {
|
|
212
|
+
email: typeof claims.email === "string" ? claims.email : void 0,
|
|
213
|
+
exp: typeof claims.exp === "number" ? claims.exp : void 0,
|
|
214
|
+
sub: typeof claims.sub === "string" ? claims.sub : void 0
|
|
215
|
+
};
|
|
216
|
+
} catch {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
//#endregion
|
|
217
221
|
//#region src/supabase.ts
|
|
218
222
|
const resolveSupabaseConfig = () => {
|
|
219
223
|
return { url: process.env.SUPABASE_URL ?? process.env.NEXT_PUBLIC_SUPABASE_URL ?? "https://bwnxwgkgyklzzmpbzuoz.supabase.co" };
|
|
@@ -254,53 +258,30 @@ const shouldRefresh = (session) => {
|
|
|
254
258
|
const ms = expiresInMs(session);
|
|
255
259
|
return ms !== null && ms <= 6e4;
|
|
256
260
|
};
|
|
257
|
-
const tokenFromRaw = (token, source) => {
|
|
258
|
-
const claims = parseJwtClaims(token);
|
|
259
|
-
return {
|
|
260
|
-
expiresAt: typeof claims?.exp === "number" ? (/* @__PURE__ */ new Date(claims.exp * 1e3)).toISOString() : null,
|
|
261
|
-
source,
|
|
262
|
-
token,
|
|
263
|
-
user: claims?.sub || claims?.email ? {
|
|
264
|
-
email: claims.email ?? null,
|
|
265
|
-
id: claims.sub ?? "unknown"
|
|
266
|
-
} : null
|
|
267
|
-
};
|
|
268
|
-
};
|
|
269
261
|
const sessionToResolvedToken = (session) => ({
|
|
270
262
|
expiresAt: session.expiresAt,
|
|
271
263
|
source: "stored",
|
|
272
264
|
token: session.accessToken,
|
|
273
265
|
user: session.user
|
|
274
266
|
});
|
|
275
|
-
const resolveAuthToken = async (
|
|
276
|
-
const
|
|
277
|
-
if (
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if (isExpired(session)) {
|
|
292
|
-
await clearStoredCredentials();
|
|
293
|
-
return null;
|
|
294
|
-
}
|
|
295
|
-
return sessionToResolvedToken(session);
|
|
267
|
+
const resolveAuthToken = async () => {
|
|
268
|
+
const session = (await readAuthFile())?.session;
|
|
269
|
+
if (!session) return null;
|
|
270
|
+
if (!(shouldRefresh(session) || isExpired(session))) return sessionToResolvedToken(session);
|
|
271
|
+
if (session.refreshToken) try {
|
|
272
|
+
const { tokenUrl } = buildOAuthUrls(resolveSupabaseConfig());
|
|
273
|
+
const updatedSession = tokenResponseToStoredSession(await refreshAccessToken({
|
|
274
|
+
clientId: OAUTH_CLIENT_ID,
|
|
275
|
+
tokenUrl
|
|
276
|
+
}, session.refreshToken));
|
|
277
|
+
await writeStoredAuthSession(updatedSession);
|
|
278
|
+
return sessionToResolvedToken(updatedSession);
|
|
279
|
+
} catch {}
|
|
280
|
+
if (isExpired(session)) {
|
|
281
|
+
await clearStoredCredentials();
|
|
282
|
+
return null;
|
|
296
283
|
}
|
|
297
|
-
|
|
298
|
-
expiresAt: null,
|
|
299
|
-
source: "stored",
|
|
300
|
-
token: data.apiKey.apiKey,
|
|
301
|
-
user: null
|
|
302
|
-
};
|
|
303
|
-
return null;
|
|
284
|
+
return sessionToResolvedToken(session);
|
|
304
285
|
};
|
|
305
286
|
const resolveTokenStatus = (token) => {
|
|
306
287
|
if (!token.expiresAt) return {
|
|
@@ -334,6 +315,642 @@ const parsePort = (value, label = "Port") => {
|
|
|
334
315
|
return parsed;
|
|
335
316
|
};
|
|
336
317
|
//#endregion
|
|
318
|
+
//#region ../../packages/models/dist/docs-config.js
|
|
319
|
+
const UrlOrPathSchema = z.string().min(1);
|
|
320
|
+
const SlugSchema = z.string().min(1).regex(/^[a-z0-9-]+$/);
|
|
321
|
+
const DocsColorsSchema = z.object({
|
|
322
|
+
background: z.string().optional(),
|
|
323
|
+
border: z.string().optional(),
|
|
324
|
+
dark: z.string().optional(),
|
|
325
|
+
light: z.string().optional(),
|
|
326
|
+
muted: z.string().optional(),
|
|
327
|
+
primary: z.string().min(1),
|
|
328
|
+
surface: z.string().optional()
|
|
329
|
+
}).strict();
|
|
330
|
+
const DocsFontsSchema = z.object({
|
|
331
|
+
body: z.string().optional(),
|
|
332
|
+
cssUrl: z.string().optional(),
|
|
333
|
+
heading: z.string().optional(),
|
|
334
|
+
mono: z.string().optional(),
|
|
335
|
+
provider: z.enum([
|
|
336
|
+
"google",
|
|
337
|
+
"local",
|
|
338
|
+
"custom"
|
|
339
|
+
]).optional()
|
|
340
|
+
}).strict();
|
|
341
|
+
const DocsLogoSchema = z.object({
|
|
342
|
+
alt: z.string().optional(),
|
|
343
|
+
dark: UrlOrPathSchema.optional(),
|
|
344
|
+
href: z.string().min(1).optional(),
|
|
345
|
+
light: UrlOrPathSchema.optional()
|
|
346
|
+
}).strict();
|
|
347
|
+
const DocsNavLinkSchema = z.object({
|
|
348
|
+
href: z.string().min(1),
|
|
349
|
+
label: z.string().min(1)
|
|
350
|
+
}).strict();
|
|
351
|
+
const DocsNavAnchorSchema = z.object({
|
|
352
|
+
href: z.string().min(1),
|
|
353
|
+
label: z.string().min(1)
|
|
354
|
+
}).strict();
|
|
355
|
+
const DocsNavLocaleSchema = z.object({
|
|
356
|
+
label: z.string().min(1),
|
|
357
|
+
locale: z.string().optional(),
|
|
358
|
+
url: z.string().min(1)
|
|
359
|
+
}).strict();
|
|
360
|
+
const DocsNavVersionSchema = z.object({
|
|
361
|
+
label: z.string().min(1),
|
|
362
|
+
url: z.string().min(1)
|
|
363
|
+
}).strict();
|
|
364
|
+
const DocsOpenApiSourceSchema = z.object({
|
|
365
|
+
basePath: z.string().optional(),
|
|
366
|
+
directory: z.string().optional(),
|
|
367
|
+
include: z.array(z.string()).optional(),
|
|
368
|
+
source: z.string().min(1)
|
|
369
|
+
}).strict();
|
|
370
|
+
const DocsNavGroupSchema = z.object({
|
|
371
|
+
expanded: z.boolean().optional(),
|
|
372
|
+
group: z.string().optional(),
|
|
373
|
+
hidden: z.boolean().optional(),
|
|
374
|
+
openapi: z.union([z.string().min(1), DocsOpenApiSourceSchema]).optional(),
|
|
375
|
+
pages: z.array(z.string()).optional()
|
|
376
|
+
}).strict();
|
|
377
|
+
const DocsNavTabSchema = z.object({
|
|
378
|
+
groups: z.array(DocsNavGroupSchema).optional(),
|
|
379
|
+
href: z.string().min(1).optional(),
|
|
380
|
+
icon: z.string().optional(),
|
|
381
|
+
label: z.string().min(1),
|
|
382
|
+
pages: z.array(z.string()).optional()
|
|
383
|
+
}).strict().refine((value) => Boolean(value.groups?.length || value.pages?.length || value.href), {
|
|
384
|
+
message: "tab must define groups, pages, or href",
|
|
385
|
+
path: []
|
|
386
|
+
});
|
|
387
|
+
const DocsNavigationSchema = z.object({
|
|
388
|
+
global: z.object({
|
|
389
|
+
anchors: z.array(DocsNavAnchorSchema).optional(),
|
|
390
|
+
links: z.array(DocsNavLinkSchema).optional()
|
|
391
|
+
}).strict().optional(),
|
|
392
|
+
groups: z.array(DocsNavGroupSchema).optional(),
|
|
393
|
+
hidden: z.array(z.string()).optional(),
|
|
394
|
+
languages: z.array(DocsNavLocaleSchema).optional(),
|
|
395
|
+
pages: z.array(z.string()).optional(),
|
|
396
|
+
tabs: z.array(DocsNavTabSchema).optional(),
|
|
397
|
+
versions: z.array(DocsNavVersionSchema).optional()
|
|
398
|
+
}).strict();
|
|
399
|
+
const DocsScriptsSchema = z.object({
|
|
400
|
+
body: z.array(z.string()).optional(),
|
|
401
|
+
head: z.array(z.string()).optional()
|
|
402
|
+
}).strict();
|
|
403
|
+
const DocsSeoSchema = z.object({ indexing: z.enum(["all", "default"]).optional() }).strict();
|
|
404
|
+
const DocsFeatureFlagsSchema = z.object({
|
|
405
|
+
rightToc: z.boolean().optional(),
|
|
406
|
+
search: z.boolean().optional(),
|
|
407
|
+
themeToggle: z.boolean().optional(),
|
|
408
|
+
toc: z.boolean().optional()
|
|
409
|
+
}).strict();
|
|
410
|
+
const DocsOpenApiProxySchema = z.object({
|
|
411
|
+
allowedHosts: z.array(z.string()).optional(),
|
|
412
|
+
enabled: z.boolean().optional()
|
|
413
|
+
}).strict();
|
|
414
|
+
const MintlifyLogoSchema = z.union([UrlOrPathSchema, z.object({
|
|
415
|
+
dark: UrlOrPathSchema,
|
|
416
|
+
href: z.string().min(1).optional(),
|
|
417
|
+
light: UrlOrPathSchema
|
|
418
|
+
}).strict()]);
|
|
419
|
+
const MintlifyFaviconSchema = z.union([UrlOrPathSchema, z.object({
|
|
420
|
+
dark: UrlOrPathSchema,
|
|
421
|
+
light: UrlOrPathSchema
|
|
422
|
+
}).strict()]);
|
|
423
|
+
const MintlifyNavbarLinkSchema = z.object({
|
|
424
|
+
href: z.string().min(1),
|
|
425
|
+
icon: z.string().optional(),
|
|
426
|
+
iconType: z.string().optional(),
|
|
427
|
+
label: z.string().optional(),
|
|
428
|
+
type: z.enum(["discord", "github"]).optional()
|
|
429
|
+
}).strict();
|
|
430
|
+
const MintlifyNavbarPrimarySchema = z.object({
|
|
431
|
+
href: z.string().min(1),
|
|
432
|
+
label: z.string().optional(),
|
|
433
|
+
type: z.enum([
|
|
434
|
+
"button",
|
|
435
|
+
"discord",
|
|
436
|
+
"github"
|
|
437
|
+
])
|
|
438
|
+
}).strict();
|
|
439
|
+
const MintlifyNavbarSchema = z.object({
|
|
440
|
+
links: z.array(MintlifyNavbarLinkSchema).optional(),
|
|
441
|
+
primary: MintlifyNavbarPrimarySchema.optional()
|
|
442
|
+
}).strict();
|
|
443
|
+
const MintlifyNavigationGlobalSchema = z.object({ anchors: z.array(z.object({
|
|
444
|
+
anchor: z.string().min(1),
|
|
445
|
+
color: z.object({
|
|
446
|
+
dark: z.string().optional(),
|
|
447
|
+
light: z.string().optional()
|
|
448
|
+
}).strict().optional(),
|
|
449
|
+
hidden: z.boolean().optional(),
|
|
450
|
+
href: z.string().min(1),
|
|
451
|
+
icon: z.string().optional(),
|
|
452
|
+
iconType: z.string().optional()
|
|
453
|
+
}).strict()).optional() }).strict();
|
|
454
|
+
const MintlifyNavigationGroupSchema = z.object({
|
|
455
|
+
expanded: z.boolean().optional(),
|
|
456
|
+
group: z.string().min(1),
|
|
457
|
+
hidden: z.boolean().optional(),
|
|
458
|
+
icon: z.string().optional(),
|
|
459
|
+
pages: z.array(z.string()).optional(),
|
|
460
|
+
root: z.string().optional(),
|
|
461
|
+
tag: z.string().optional()
|
|
462
|
+
}).strict();
|
|
463
|
+
const MintlifyNavTabSchema = z.object({
|
|
464
|
+
groups: z.array(MintlifyNavigationGroupSchema).optional(),
|
|
465
|
+
hidden: z.boolean().optional(),
|
|
466
|
+
href: z.string().min(1).optional(),
|
|
467
|
+
icon: z.string().optional(),
|
|
468
|
+
pages: z.array(z.string()).optional(),
|
|
469
|
+
tab: z.string().min(1)
|
|
470
|
+
}).strict();
|
|
471
|
+
const MintlifyNavigationSchema = z.object({
|
|
472
|
+
global: MintlifyNavigationGlobalSchema.optional(),
|
|
473
|
+
groups: z.array(MintlifyNavigationGroupSchema).optional(),
|
|
474
|
+
languages: z.array(z.object({
|
|
475
|
+
default: z.boolean().optional(),
|
|
476
|
+
hidden: z.boolean().optional(),
|
|
477
|
+
href: z.string().min(1),
|
|
478
|
+
language: z.string().min(1)
|
|
479
|
+
}).strict()).optional(),
|
|
480
|
+
pages: z.array(z.string()).optional(),
|
|
481
|
+
tabs: z.array(MintlifyNavTabSchema).optional(),
|
|
482
|
+
versions: z.array(z.object({
|
|
483
|
+
default: z.boolean().optional(),
|
|
484
|
+
hidden: z.boolean().optional(),
|
|
485
|
+
href: z.string().min(1),
|
|
486
|
+
version: z.string().min(1)
|
|
487
|
+
}).strict()).optional()
|
|
488
|
+
}).strict().refine((value) => Boolean(value.groups?.length || value.pages?.length || value.tabs?.length || value.languages?.length || value.versions?.length), {
|
|
489
|
+
message: "navigation must define at least one of groups, pages, tabs, languages, or versions",
|
|
490
|
+
path: []
|
|
491
|
+
});
|
|
492
|
+
const MintlifyApiSchema = z.object({
|
|
493
|
+
asyncapi: z.union([
|
|
494
|
+
z.string(),
|
|
495
|
+
z.array(z.string()),
|
|
496
|
+
DocsOpenApiSourceSchema
|
|
497
|
+
]).optional(),
|
|
498
|
+
examples: z.object({
|
|
499
|
+
autogenerate: z.boolean().optional(),
|
|
500
|
+
defaults: z.enum(["all", "required"]).optional(),
|
|
501
|
+
languages: z.array(z.string()).optional(),
|
|
502
|
+
prefill: z.boolean().optional()
|
|
503
|
+
}).strict().optional(),
|
|
504
|
+
mdx: z.object({
|
|
505
|
+
auth: z.object({
|
|
506
|
+
method: z.enum([
|
|
507
|
+
"basic",
|
|
508
|
+
"bearer",
|
|
509
|
+
"cobo",
|
|
510
|
+
"key"
|
|
511
|
+
]).optional(),
|
|
512
|
+
name: z.string().optional()
|
|
513
|
+
}).strict().optional(),
|
|
514
|
+
server: z.union([z.string().min(1), z.array(z.string().min(1))]).optional()
|
|
515
|
+
}).strict().optional(),
|
|
516
|
+
openapi: z.union([
|
|
517
|
+
z.string(),
|
|
518
|
+
z.array(z.string()),
|
|
519
|
+
DocsOpenApiSourceSchema
|
|
520
|
+
]).optional(),
|
|
521
|
+
params: z.object({ expanded: z.enum(["all", "closed"]).optional() }).strict().optional(),
|
|
522
|
+
playground: z.object({
|
|
523
|
+
credentials: z.boolean().optional(),
|
|
524
|
+
display: z.enum([
|
|
525
|
+
"auth",
|
|
526
|
+
"interactive",
|
|
527
|
+
"none",
|
|
528
|
+
"simple"
|
|
529
|
+
]).optional(),
|
|
530
|
+
proxy: z.boolean().optional()
|
|
531
|
+
}).strict().optional(),
|
|
532
|
+
url: z.literal("full").optional()
|
|
533
|
+
}).strict();
|
|
534
|
+
const MintlifyAppearanceSchema = z.object({
|
|
535
|
+
default: z.enum([
|
|
536
|
+
"dark",
|
|
537
|
+
"light",
|
|
538
|
+
"system"
|
|
539
|
+
]).optional(),
|
|
540
|
+
strict: z.boolean().optional()
|
|
541
|
+
}).strict();
|
|
542
|
+
const MintlifyMetadataSchema = z.object({ timestamp: z.boolean().optional() }).strict();
|
|
543
|
+
const MintlifySearchSchema = z.object({ prompt: z.string().optional() }).strict();
|
|
544
|
+
const ContextualBuiltinOptionSchema = z.enum([
|
|
545
|
+
"add-mcp",
|
|
546
|
+
"aistudio",
|
|
547
|
+
"assistant",
|
|
548
|
+
"chatgpt",
|
|
549
|
+
"claude",
|
|
550
|
+
"copy",
|
|
551
|
+
"cursor",
|
|
552
|
+
"devin",
|
|
553
|
+
"devin-mcp",
|
|
554
|
+
"gemini",
|
|
555
|
+
"grok",
|
|
556
|
+
"mcp",
|
|
557
|
+
"perplexity",
|
|
558
|
+
"view",
|
|
559
|
+
"vscode",
|
|
560
|
+
"windsurf"
|
|
561
|
+
]);
|
|
562
|
+
const ContextualCustomHrefQuerySchema = z.object({
|
|
563
|
+
key: z.string().min(1),
|
|
564
|
+
value: z.string().min(1)
|
|
565
|
+
}).strict();
|
|
566
|
+
const ContextualCustomHrefObjectSchema = z.object({
|
|
567
|
+
base: z.string().min(1),
|
|
568
|
+
query: z.array(ContextualCustomHrefQuerySchema)
|
|
569
|
+
}).strict();
|
|
570
|
+
const ContextualCustomOptionSchema = z.object({
|
|
571
|
+
description: z.string().min(1),
|
|
572
|
+
href: z.union([z.string().min(1), ContextualCustomHrefObjectSchema]),
|
|
573
|
+
icon: z.string().min(1),
|
|
574
|
+
iconType: z.string().optional(),
|
|
575
|
+
title: z.string().min(1)
|
|
576
|
+
}).strict();
|
|
577
|
+
const ContextualOptionSchema = z.union([ContextualBuiltinOptionSchema, ContextualCustomOptionSchema]);
|
|
578
|
+
const DocsContextualSchema = z.object({
|
|
579
|
+
display: z.enum(["header", "toc"]).optional(),
|
|
580
|
+
options: z.array(ContextualOptionSchema)
|
|
581
|
+
}).strict();
|
|
582
|
+
const DocsConfigSchema = z.object({
|
|
583
|
+
$schema: z.string().optional(),
|
|
584
|
+
api: MintlifyApiSchema.optional(),
|
|
585
|
+
appearance: MintlifyAppearanceSchema.optional(),
|
|
586
|
+
contextual: DocsContextualSchema.optional(),
|
|
587
|
+
description: z.string().optional(),
|
|
588
|
+
favicon: MintlifyFaviconSchema.optional(),
|
|
589
|
+
logo: MintlifyLogoSchema.optional(),
|
|
590
|
+
metadata: MintlifyMetadataSchema.optional(),
|
|
591
|
+
name: z.string().min(1),
|
|
592
|
+
navbar: MintlifyNavbarSchema.optional(),
|
|
593
|
+
navigation: MintlifyNavigationSchema,
|
|
594
|
+
search: MintlifySearchSchema.optional(),
|
|
595
|
+
seo: DocsSeoSchema.optional(),
|
|
596
|
+
slug: SlugSchema.optional()
|
|
597
|
+
}).strict();
|
|
598
|
+
const ContentTypeSchema = z.enum([
|
|
599
|
+
"site",
|
|
600
|
+
"blog",
|
|
601
|
+
"docs",
|
|
602
|
+
"courses",
|
|
603
|
+
"products",
|
|
604
|
+
"notes",
|
|
605
|
+
"forms",
|
|
606
|
+
"sheets",
|
|
607
|
+
"slides",
|
|
608
|
+
"todos"
|
|
609
|
+
]);
|
|
610
|
+
const FrontmatterBaseSchema = z.object({
|
|
611
|
+
description: z.string().optional(),
|
|
612
|
+
hidden: z.boolean().optional(),
|
|
613
|
+
title: z.string().min(1)
|
|
614
|
+
}).passthrough();
|
|
615
|
+
FrontmatterBaseSchema.extend({
|
|
616
|
+
date: z.string().min(1),
|
|
617
|
+
tags: z.array(z.string()).optional()
|
|
618
|
+
}).passthrough();
|
|
619
|
+
FrontmatterBaseSchema.extend({ order: z.number() }).passthrough();
|
|
620
|
+
FrontmatterBaseSchema.extend({
|
|
621
|
+
currency: z.string().min(1),
|
|
622
|
+
price: z.number(),
|
|
623
|
+
sku: z.string().min(1)
|
|
624
|
+
}).passthrough();
|
|
625
|
+
FrontmatterBaseSchema.extend({ date: z.string().min(1) }).passthrough();
|
|
626
|
+
const FormFieldSchema = z.object({
|
|
627
|
+
id: z.string().min(1),
|
|
628
|
+
label: z.string().min(1),
|
|
629
|
+
options: z.array(z.string()).optional(),
|
|
630
|
+
required: z.boolean().optional(),
|
|
631
|
+
type: z.string().min(1)
|
|
632
|
+
}).passthrough();
|
|
633
|
+
FrontmatterBaseSchema.extend({ fields: z.array(FormFieldSchema).min(1) }).passthrough();
|
|
634
|
+
FrontmatterBaseSchema.extend({ columns: z.array(z.string()).min(1) }).passthrough();
|
|
635
|
+
FrontmatterBaseSchema.extend({ date: z.string().min(1) }).passthrough();
|
|
636
|
+
const PageModeSchema = z.enum([
|
|
637
|
+
"default",
|
|
638
|
+
"wide",
|
|
639
|
+
"custom",
|
|
640
|
+
"frame",
|
|
641
|
+
"center"
|
|
642
|
+
]);
|
|
643
|
+
FrontmatterBaseSchema.extend({
|
|
644
|
+
deprecated: z.boolean().optional(),
|
|
645
|
+
hideApiMarker: z.boolean().optional(),
|
|
646
|
+
hideFooterPagination: z.boolean().optional(),
|
|
647
|
+
icon: z.string().optional(),
|
|
648
|
+
iconType: z.enum([
|
|
649
|
+
"regular",
|
|
650
|
+
"solid",
|
|
651
|
+
"light",
|
|
652
|
+
"thin",
|
|
653
|
+
"sharp-solid",
|
|
654
|
+
"duotone",
|
|
655
|
+
"brands"
|
|
656
|
+
]).optional(),
|
|
657
|
+
keywords: z.array(z.string()).optional(),
|
|
658
|
+
mode: PageModeSchema.optional(),
|
|
659
|
+
noindex: z.boolean().optional(),
|
|
660
|
+
sidebarTitle: z.string().optional(),
|
|
661
|
+
tag: z.string().optional(),
|
|
662
|
+
url: z.string().url().optional()
|
|
663
|
+
}).passthrough();
|
|
664
|
+
const CollectionIndexSchema = z.object({
|
|
665
|
+
description: z.string().optional(),
|
|
666
|
+
hidden: z.boolean().optional(),
|
|
667
|
+
slug: z.string().min(1),
|
|
668
|
+
title: z.string().optional()
|
|
669
|
+
}).strict();
|
|
670
|
+
const CollectionSortSchema = z.object({
|
|
671
|
+
direction: z.enum(["asc", "desc"]).optional(),
|
|
672
|
+
field: z.enum([
|
|
673
|
+
"date",
|
|
674
|
+
"order",
|
|
675
|
+
"title",
|
|
676
|
+
"price"
|
|
677
|
+
]).optional()
|
|
678
|
+
}).strict();
|
|
679
|
+
const CollectionConfigSchema = z.object({
|
|
680
|
+
id: z.string().min(1),
|
|
681
|
+
index: CollectionIndexSchema.optional(),
|
|
682
|
+
navigation: DocsNavigationSchema.optional(),
|
|
683
|
+
openapi: z.union([
|
|
684
|
+
z.string(),
|
|
685
|
+
z.array(z.string()),
|
|
686
|
+
DocsOpenApiSourceSchema
|
|
687
|
+
]).optional(),
|
|
688
|
+
root: z.string().optional(),
|
|
689
|
+
slugPrefix: z.string().optional(),
|
|
690
|
+
sort: CollectionSortSchema.optional(),
|
|
691
|
+
type: ContentTypeSchema
|
|
692
|
+
}).strict();
|
|
693
|
+
const SiteConfigSchema = z.object({
|
|
694
|
+
collections: z.array(CollectionConfigSchema).min(1),
|
|
695
|
+
colors: DocsColorsSchema.optional(),
|
|
696
|
+
contextual: DocsContextualSchema.optional(),
|
|
697
|
+
description: z.string().optional(),
|
|
698
|
+
favicon: UrlOrPathSchema.optional(),
|
|
699
|
+
features: DocsFeatureFlagsSchema.optional(),
|
|
700
|
+
fonts: DocsFontsSchema.optional(),
|
|
701
|
+
logo: DocsLogoSchema.optional(),
|
|
702
|
+
metadata: z.object({
|
|
703
|
+
defaultTitle: z.string().optional(),
|
|
704
|
+
ogImage: UrlOrPathSchema.optional(),
|
|
705
|
+
titleTemplate: z.string().optional()
|
|
706
|
+
}).strict().optional(),
|
|
707
|
+
name: z.string().min(1),
|
|
708
|
+
navigation: DocsNavigationSchema.optional(),
|
|
709
|
+
openapiProxy: DocsOpenApiProxySchema.optional(),
|
|
710
|
+
scripts: DocsScriptsSchema.optional(),
|
|
711
|
+
seo: DocsSeoSchema.optional(),
|
|
712
|
+
slug: SlugSchema.optional(),
|
|
713
|
+
theme: z.string().optional()
|
|
714
|
+
}).strict();
|
|
715
|
+
//#endregion
|
|
716
|
+
//#region ../../packages/validation/dist/index.js
|
|
717
|
+
const formatIssues = (issues) => issues.map((issue) => {
|
|
718
|
+
return `${issue.path.length ? issue.path.map(String).join(".") : "root"}: ${issue.message}`;
|
|
719
|
+
});
|
|
720
|
+
const validateSiteConfig = (input) => {
|
|
721
|
+
const result = SiteConfigSchema.safeParse(input);
|
|
722
|
+
if (result.success) return {
|
|
723
|
+
data: result.data,
|
|
724
|
+
success: true
|
|
725
|
+
};
|
|
726
|
+
return {
|
|
727
|
+
errors: formatIssues(result.error.issues),
|
|
728
|
+
success: false
|
|
729
|
+
};
|
|
730
|
+
};
|
|
731
|
+
const validateDocsConfig = (input) => {
|
|
732
|
+
const result = DocsConfigSchema.safeParse(input);
|
|
733
|
+
if (result.success) return {
|
|
734
|
+
data: result.data,
|
|
735
|
+
success: true
|
|
736
|
+
};
|
|
737
|
+
return {
|
|
738
|
+
errors: formatIssues(result.error.issues),
|
|
739
|
+
success: false
|
|
740
|
+
};
|
|
741
|
+
};
|
|
742
|
+
//#endregion
|
|
743
|
+
//#region ../../packages/previewing/dist/fs-source.js
|
|
744
|
+
const IGNORED_DIRECTORIES = new Set([
|
|
745
|
+
"app",
|
|
746
|
+
"lib",
|
|
747
|
+
"node_modules",
|
|
748
|
+
"public"
|
|
749
|
+
]);
|
|
750
|
+
const isNotFoundError = (error) => Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
751
|
+
const isWithinRoot = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path.sep}`);
|
|
752
|
+
const resolveWithinRoot = (root, relativePath) => {
|
|
753
|
+
const normalized = normalizePath(relativePath);
|
|
754
|
+
const absolutePath = path.resolve(root, normalized);
|
|
755
|
+
if (!isWithinRoot(root, absolutePath)) throw new Error(`Path "${relativePath}" escapes the content source root.`);
|
|
756
|
+
return absolutePath;
|
|
757
|
+
};
|
|
758
|
+
const walkFiles = async (directory, prefix) => {
|
|
759
|
+
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
760
|
+
const files = [];
|
|
761
|
+
for (const entry of entries) {
|
|
762
|
+
if (entry.name.startsWith(".")) continue;
|
|
763
|
+
const absolutePath = path.join(directory, entry.name);
|
|
764
|
+
const relativePath = prefix ? path.join(prefix, entry.name) : entry.name;
|
|
765
|
+
if (entry.isDirectory()) {
|
|
766
|
+
if (IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
767
|
+
files.push(...await walkFiles(absolutePath, relativePath));
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
if (entry.isFile()) {
|
|
771
|
+
if (!prefix && shouldIgnoreRootDocsFile(entry.name)) continue;
|
|
772
|
+
files.push(normalizePath(relativePath));
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return files;
|
|
776
|
+
};
|
|
777
|
+
var FsContentSource = class {
|
|
778
|
+
root;
|
|
779
|
+
constructor(root) {
|
|
780
|
+
this.root = path.resolve(root);
|
|
781
|
+
}
|
|
782
|
+
async readFile(relativePath) {
|
|
783
|
+
return await fs.readFile(resolveWithinRoot(this.root, relativePath), "utf8");
|
|
784
|
+
}
|
|
785
|
+
async listFiles(directory) {
|
|
786
|
+
return await walkFiles(resolveWithinRoot(this.root, directory), "");
|
|
787
|
+
}
|
|
788
|
+
async exists(relativePath) {
|
|
789
|
+
try {
|
|
790
|
+
await fs.access(resolveWithinRoot(this.root, relativePath));
|
|
791
|
+
return true;
|
|
792
|
+
} catch (error) {
|
|
793
|
+
if (isNotFoundError(error)) return false;
|
|
794
|
+
throw error;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
resolveUrl() {
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
const createFsSource = (root) => new FsContentSource(root);
|
|
802
|
+
//#endregion
|
|
803
|
+
//#region ../../packages/previewing/dist/index.js
|
|
804
|
+
const LEGACY_PROJECT_NAME_FALLBACK_WARNING$1 = "docs.json.slug is recommended. Falling back to docs.json.name as the deployment slug is deprecated.";
|
|
805
|
+
new Set(PageModeSchema.options);
|
|
806
|
+
const DOCS_CONFIG_FILE = "docs.json";
|
|
807
|
+
const defaultLinkLabel = (input) => {
|
|
808
|
+
if (input.label) return input.label;
|
|
809
|
+
if (input.type === "github") return "GitHub";
|
|
810
|
+
if (input.type === "discord") return "Discord";
|
|
811
|
+
try {
|
|
812
|
+
return new URL(input.href).hostname;
|
|
813
|
+
} catch {
|
|
814
|
+
return input.href;
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
const mapDocsConfig = (docs) => {
|
|
818
|
+
const navigation = {
|
|
819
|
+
global: docs.navbar?.links?.length || docs.navigation.global?.anchors?.length ? {
|
|
820
|
+
anchors: docs.navigation.global?.anchors?.map((anchor) => ({
|
|
821
|
+
href: anchor.href,
|
|
822
|
+
label: anchor.anchor
|
|
823
|
+
})),
|
|
824
|
+
links: docs.navbar?.links?.map((link) => ({
|
|
825
|
+
href: link.href,
|
|
826
|
+
label: defaultLinkLabel(link)
|
|
827
|
+
}))
|
|
828
|
+
} : void 0,
|
|
829
|
+
groups: docs.navigation.groups?.map((group) => ({
|
|
830
|
+
expanded: group.expanded,
|
|
831
|
+
group: group.group,
|
|
832
|
+
hidden: group.hidden,
|
|
833
|
+
pages: group.root ? [group.root, ...(group.pages ?? []).filter((page) => page !== group.root)] : group.pages
|
|
834
|
+
})),
|
|
835
|
+
languages: docs.navigation.languages?.map((language) => ({
|
|
836
|
+
label: language.language,
|
|
837
|
+
locale: language.language,
|
|
838
|
+
url: language.href
|
|
839
|
+
})),
|
|
840
|
+
pages: docs.navigation.pages,
|
|
841
|
+
tabs: docs.navigation.tabs?.map((tab) => ({
|
|
842
|
+
groups: tab.groups?.map((group) => ({
|
|
843
|
+
expanded: group.expanded,
|
|
844
|
+
group: group.group,
|
|
845
|
+
hidden: group.hidden,
|
|
846
|
+
pages: group.root ? [group.root, ...(group.pages ?? []).filter((page) => page !== group.root)] : group.pages
|
|
847
|
+
})),
|
|
848
|
+
href: tab.href,
|
|
849
|
+
icon: tab.icon,
|
|
850
|
+
label: tab.tab,
|
|
851
|
+
pages: tab.pages
|
|
852
|
+
})),
|
|
853
|
+
versions: docs.navigation.versions?.map((version) => ({
|
|
854
|
+
label: version.version,
|
|
855
|
+
url: version.href
|
|
856
|
+
}))
|
|
857
|
+
};
|
|
858
|
+
return {
|
|
859
|
+
collections: [{
|
|
860
|
+
id: "docs",
|
|
861
|
+
navigation,
|
|
862
|
+
openapi: docs.api?.openapi,
|
|
863
|
+
root: "",
|
|
864
|
+
type: "docs"
|
|
865
|
+
}],
|
|
866
|
+
contextual: docs.contextual,
|
|
867
|
+
description: docs.description,
|
|
868
|
+
favicon: typeof docs.favicon === "string" ? docs.favicon : docs.favicon?.light,
|
|
869
|
+
features: {
|
|
870
|
+
rightToc: true,
|
|
871
|
+
search: true,
|
|
872
|
+
themeToggle: docs.appearance?.strict !== true,
|
|
873
|
+
toc: true
|
|
874
|
+
},
|
|
875
|
+
logo: docs.logo ? {
|
|
876
|
+
dark: typeof docs.logo === "string" ? docs.logo : docs.logo.dark,
|
|
877
|
+
href: typeof docs.logo === "string" ? void 0 : docs.logo.href,
|
|
878
|
+
light: typeof docs.logo === "string" ? docs.logo : docs.logo.light
|
|
879
|
+
} : void 0,
|
|
880
|
+
name: docs.name,
|
|
881
|
+
navigation,
|
|
882
|
+
openapiProxy: { enabled: docs.api?.playground?.proxy !== false && Boolean(docs.api?.openapi || docs.api?.asyncapi) },
|
|
883
|
+
seo: docs.seo,
|
|
884
|
+
slug: docs.slug
|
|
885
|
+
};
|
|
886
|
+
};
|
|
887
|
+
const getProjectWarnings = (config) => config.slug ? [] : [LEGACY_PROJECT_NAME_FALLBACK_WARNING$1];
|
|
888
|
+
const readJsonConfig = async (source, relativePath) => JSON.parse(await source.readFile(relativePath));
|
|
889
|
+
const normalizeRefPath = (baseDirectory, reference) => {
|
|
890
|
+
if (reference.startsWith("/") || reference.startsWith("\\") || reference.startsWith("http://") || reference.startsWith("https://")) throw new Error(`Invalid $ref "${reference}". Only relative JSON files are supported.`);
|
|
891
|
+
const normalized = normalizePath(path.posix.join(baseDirectory, reference));
|
|
892
|
+
if (!normalized || normalized === "." || normalized.startsWith("../") || normalized.includes("/../")) throw new Error(`Invalid $ref "${reference}".`);
|
|
893
|
+
return normalized;
|
|
894
|
+
};
|
|
895
|
+
const resolveJsonRefs = async (source, value, baseDirectory, seen) => {
|
|
896
|
+
if (Array.isArray(value)) return await Promise.all(value.map((item) => resolveJsonRefs(source, item, baseDirectory, seen)));
|
|
897
|
+
if (!value || typeof value !== "object") return value;
|
|
898
|
+
const record = value;
|
|
899
|
+
const reference = record.$ref;
|
|
900
|
+
if (typeof reference === "string") {
|
|
901
|
+
const resolvedPath = normalizeRefPath(baseDirectory, reference);
|
|
902
|
+
if (seen.has(resolvedPath)) throw new Error(`Circular $ref detected for "${resolvedPath}".`);
|
|
903
|
+
const nextSeen = new Set(seen);
|
|
904
|
+
nextSeen.add(resolvedPath);
|
|
905
|
+
const referencedValue = await resolveJsonRefs(source, await readJsonConfig(source, resolvedPath), path.posix.dirname(resolvedPath) === "." ? "" : normalizePath(path.posix.dirname(resolvedPath)), nextSeen);
|
|
906
|
+
const siblingEntries = Object.entries(record).filter(([key]) => key !== "$ref");
|
|
907
|
+
if (!siblingEntries.length || !referencedValue || typeof referencedValue !== "object" || Array.isArray(referencedValue)) return referencedValue;
|
|
908
|
+
const siblingValue = await resolveJsonRefs(source, Object.fromEntries(siblingEntries), baseDirectory, seen);
|
|
909
|
+
return {
|
|
910
|
+
...referencedValue,
|
|
911
|
+
...siblingValue
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
const resolvedEntries = await Promise.all(Object.entries(record).map(async ([key, entryValue]) => [key, await resolveJsonRefs(source, entryValue, baseDirectory, seen)]));
|
|
915
|
+
return Object.fromEntries(resolvedEntries);
|
|
916
|
+
};
|
|
917
|
+
const readResolvedJsonConfig = async (source, relativePath) => await resolveJsonRefs(source, await readJsonConfig(source, relativePath), path.posix.dirname(relativePath) === "." ? "" : normalizePath(path.posix.dirname(relativePath)), new Set([relativePath]));
|
|
918
|
+
const loadDocsConfig = async (source) => {
|
|
919
|
+
if (!await source.exists(DOCS_CONFIG_FILE)) return null;
|
|
920
|
+
try {
|
|
921
|
+
const parsed = await readResolvedJsonConfig(source, DOCS_CONFIG_FILE);
|
|
922
|
+
const siteResult = validateSiteConfig(parsed);
|
|
923
|
+
if (siteResult.success) return {
|
|
924
|
+
config: siteResult.data,
|
|
925
|
+
ok: true,
|
|
926
|
+
warnings: getProjectWarnings(siteResult.data)
|
|
927
|
+
};
|
|
928
|
+
const docsResult = validateDocsConfig(parsed);
|
|
929
|
+
if (docsResult.success) return {
|
|
930
|
+
config: mapDocsConfig(docsResult.data),
|
|
931
|
+
ok: true,
|
|
932
|
+
warnings: getProjectWarnings(docsResult.data)
|
|
933
|
+
};
|
|
934
|
+
return {
|
|
935
|
+
errors: docsResult.errors,
|
|
936
|
+
ok: false
|
|
937
|
+
};
|
|
938
|
+
} catch (error) {
|
|
939
|
+
return {
|
|
940
|
+
errors: [error instanceof Error ? error.message : `Failed to load ${DOCS_CONFIG_FILE}`],
|
|
941
|
+
ok: false
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
const loadSiteConfig = async (source) => {
|
|
946
|
+
const docsConfig = await loadDocsConfig(source);
|
|
947
|
+
if (docsConfig) return docsConfig;
|
|
948
|
+
return {
|
|
949
|
+
errors: [`${DOCS_CONFIG_FILE} not found.`],
|
|
950
|
+
ok: false
|
|
951
|
+
};
|
|
952
|
+
};
|
|
953
|
+
//#endregion
|
|
337
954
|
//#region src/site-config.ts
|
|
338
955
|
const CONFIG_FILE$2 = "docs.json";
|
|
339
956
|
const getSiteConfigHint = (errors) => {
|
|
@@ -1616,7 +2233,7 @@ const resolvePushConfig = async (config, options) => {
|
|
|
1616
2233
|
envProject: process.env[BLODE_PROJECT_ENV]
|
|
1617
2234
|
});
|
|
1618
2235
|
const apiUrl = options.apiUrl ?? process.env["BLODEMD_API_URL"] ?? "https://api.blode.md";
|
|
1619
|
-
const authToken = (await resolveAuthToken(
|
|
2236
|
+
const authToken = (await resolveAuthToken())?.token;
|
|
1620
2237
|
const branch = options.branch ?? process.env["BLODEMD_BRANCH"] ?? process.env.GITHUB_REF_NAME ?? readGitValue([
|
|
1621
2238
|
"rev-parse",
|
|
1622
2239
|
"--abbrev-ref",
|
|
@@ -1633,7 +2250,7 @@ const resolvePushConfig = async (config, options) => {
|
|
|
1633
2250
|
if (usedLegacyNameFallback) throw new Error(`docs.json.name is not a valid deployment slug. Add "slug" to docs.json, pass --project, or set BLODEMD_PROJECT. ${projectSlugError}`);
|
|
1634
2251
|
throw new Error(`Invalid project slug "${project}". ${projectSlugError}`);
|
|
1635
2252
|
}
|
|
1636
|
-
if (!authToken) throw new Error("
|
|
2253
|
+
if (!authToken) throw new Error("Not logged in. Run \"blodemd login\" to authenticate.");
|
|
1637
2254
|
return {
|
|
1638
2255
|
apiUrl,
|
|
1639
2256
|
authToken,
|
|
@@ -1656,8 +2273,7 @@ const autoCreateProject = async (project, projectDisplayName, apiUrl, headers) =
|
|
|
1656
2273
|
headers,
|
|
1657
2274
|
method: "POST"
|
|
1658
2275
|
}, "Failed to create project");
|
|
1659
|
-
log.success(`Project ${chalk.cyan(createResult.
|
|
1660
|
-
log.info(`API key for CI: ${chalk.dim(createResult.token)}`);
|
|
2276
|
+
log.success(`Project ${chalk.cyan(createResult.slug)} created`);
|
|
1661
2277
|
return true;
|
|
1662
2278
|
};
|
|
1663
2279
|
const scaffoldDocsSite = async (directory, options) => {
|
|
@@ -1727,29 +2343,9 @@ program.name("blodemd").description("Blode.md CLI").version(cliVersion);
|
|
|
1727
2343
|
program.hook("preAction", () => {
|
|
1728
2344
|
assertSupportedNodeVersion();
|
|
1729
2345
|
});
|
|
1730
|
-
program.command("login").description("Authenticate with Blode.md
|
|
2346
|
+
program.command("login").description("Authenticate with Blode.md via GitHub in your browser").option("--port <port>", "Loopback callback port", String(DEFAULT_OAUTH_CALLBACK_PORT)).option("--timeout <seconds>", "OAuth timeout in seconds", String(180)).option("--no-open", "Print URL instead of opening the browser").action(async (options) => {
|
|
1731
2347
|
intro(chalk.bold("blodemd login"));
|
|
1732
2348
|
try {
|
|
1733
|
-
if (options.token) {
|
|
1734
|
-
const apiKey = await password({
|
|
1735
|
-
message: "Enter your API key",
|
|
1736
|
-
validate: (value) => {
|
|
1737
|
-
if (!value) return "API key is required.";
|
|
1738
|
-
}
|
|
1739
|
-
});
|
|
1740
|
-
if (isCancel(apiKey)) {
|
|
1741
|
-
log.warn("Cancelled");
|
|
1742
|
-
return;
|
|
1743
|
-
}
|
|
1744
|
-
await writeStoredApiKey({
|
|
1745
|
-
apiKey,
|
|
1746
|
-
type: "api-key"
|
|
1747
|
-
});
|
|
1748
|
-
const prefix = apiKey.split(".")[0] ?? apiKey.slice(0, 12);
|
|
1749
|
-
log.success(`Authenticated as ${chalk.cyan(prefix)}`);
|
|
1750
|
-
log.info("Done");
|
|
1751
|
-
return;
|
|
1752
|
-
}
|
|
1753
2349
|
const { authorizeUrl, tokenUrl } = buildOAuthUrls(resolveSupabaseConfig());
|
|
1754
2350
|
const clientId = OAUTH_CLIENT_ID;
|
|
1755
2351
|
const port = parsePort(options.port);
|
|
@@ -1766,6 +2362,7 @@ program.command("login").description("Authenticate with Blode.md").option("--tok
|
|
|
1766
2362
|
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
1767
2363
|
authUrl.searchParams.set("state", state);
|
|
1768
2364
|
authUrl.searchParams.set("scope", "openid email profile");
|
|
2365
|
+
authUrl.searchParams.set("provider", "github");
|
|
1769
2366
|
const callbackPromise = waitForOAuthCode({
|
|
1770
2367
|
expectedState: state,
|
|
1771
2368
|
redirectUrl,
|
|
@@ -1818,15 +2415,6 @@ program.command("whoami").description("Show current authentication").action(asyn
|
|
|
1818
2415
|
log.warn("Not logged in. Run \"blodemd login\" to authenticate.");
|
|
1819
2416
|
return;
|
|
1820
2417
|
}
|
|
1821
|
-
if (resolved.source === "environment") {
|
|
1822
|
-
log.info("Authenticated via BLODEMD_API_KEY environment variable");
|
|
1823
|
-
return;
|
|
1824
|
-
}
|
|
1825
|
-
if (!resolved.expiresAt && !resolved.user) {
|
|
1826
|
-
const prefix = resolved.token.split(".")[0] ?? resolved.token.slice(0, 12);
|
|
1827
|
-
log.info(`Logged in with API key ${chalk.cyan(prefix)}`);
|
|
1828
|
-
return;
|
|
1829
|
-
}
|
|
1830
2418
|
const status = resolveTokenStatus(resolved);
|
|
1831
2419
|
const email = resolved.user?.email ?? await fetchUserEmail(process.env["BLODEMD_API_URL"] ?? "https://api.blode.md", resolved.token);
|
|
1832
2420
|
if (email) log.info(`Logged in as ${chalk.cyan(email)}`);
|
|
@@ -1866,7 +2454,7 @@ program.command("validate").description("Validate docs.json").argument("[dir]",
|
|
|
1866
2454
|
reportCommandError("Validation failed", error);
|
|
1867
2455
|
}
|
|
1868
2456
|
});
|
|
1869
|
-
program.command("push").description("Deploy docs").argument("[dir]", "docs directory").option("--project <slug>", "project slug (env: BLODEMD_PROJECT)").option("--api-url <url>", "API URL (env: BLODEMD_API_URL)").option("--
|
|
2457
|
+
program.command("push").description("Deploy docs").argument("[dir]", "docs directory").option("--project <slug>", "project slug (env: BLODEMD_PROJECT)").option("--api-url <url>", "API URL (env: BLODEMD_API_URL)").option("--branch <name>", "git branch (env: BLODEMD_BRANCH)").option("--message <msg>", "deploy message (env: BLODEMD_COMMIT_MESSAGE)").action(async (dir, options) => {
|
|
1870
2458
|
intro(chalk.bold("blodemd push"));
|
|
1871
2459
|
const s = spinner();
|
|
1872
2460
|
try {
|