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
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) => {
|
|
@@ -579,6 +1196,19 @@ const createStandaloneRuntimeRoot = async (configDir = CONFIG_DIR) => {
|
|
|
579
1196
|
await cleanupStandaloneRuntimeRoots(configDir);
|
|
580
1197
|
return await fs.mkdtemp(path.join(configDir, STANDALONE_RUNTIME_PREFIX));
|
|
581
1198
|
};
|
|
1199
|
+
/**
|
|
1200
|
+
* Locate the `node_modules` directory that actually contains the CLI's
|
|
1201
|
+
* transitive dependencies. Package managers like npm/yarn-classic (and
|
|
1202
|
+
* `npx` caches) hoist shared deps above the package directory, so
|
|
1203
|
+
* `<cliPackageRoot>/node_modules` may not exist or may not contain `next`.
|
|
1204
|
+
* Resolve `next/package.json` and use the directory that owns it.
|
|
1205
|
+
*/
|
|
1206
|
+
const resolveRuntimeNodeModules = async (cliPackageRoot) => {
|
|
1207
|
+
const localNodeModules = path.join(cliPackageRoot, "node_modules");
|
|
1208
|
+
if (await fileExists(path.join(localNodeModules, "next", "package.json"))) return localNodeModules;
|
|
1209
|
+
const nextPkgPath = createRequire(path.join(cliPackageRoot, "package.json")).resolve("next/package.json");
|
|
1210
|
+
return path.dirname(path.dirname(nextPkgPath));
|
|
1211
|
+
};
|
|
582
1212
|
const materializeStandaloneRuntime = async (cliPackageRoot) => {
|
|
583
1213
|
const runtimeRoot = await createStandaloneRuntimeRoot();
|
|
584
1214
|
try {
|
|
@@ -587,9 +1217,17 @@ const materializeStandaloneRuntime = async (cliPackageRoot) => {
|
|
|
587
1217
|
"docs",
|
|
588
1218
|
"packages"
|
|
589
1219
|
]) await copyStandaloneTree(path.join(cliPackageRoot, dir), path.join(runtimeRoot, dir));
|
|
590
|
-
await
|
|
591
|
-
await fs.
|
|
592
|
-
|
|
1220
|
+
const runtimeNodeModules = await resolveRuntimeNodeModules(cliPackageRoot);
|
|
1221
|
+
await fs.symlink(runtimeNodeModules, path.join(runtimeRoot, "node_modules"), process.platform === "win32" ? "junction" : "dir");
|
|
1222
|
+
const linkTarget = path.join(runtimeRoot, "packages", "@repo");
|
|
1223
|
+
for (const consumer of [
|
|
1224
|
+
"dev-server",
|
|
1225
|
+
"docs",
|
|
1226
|
+
"packages"
|
|
1227
|
+
]) {
|
|
1228
|
+
await fs.mkdir(path.join(runtimeRoot, consumer, "node_modules"), { recursive: true });
|
|
1229
|
+
await fs.symlink(linkTarget, path.join(runtimeRoot, consumer, "node_modules", "@repo"), process.platform === "win32" ? "junction" : "dir");
|
|
1230
|
+
}
|
|
593
1231
|
await fs.writeFile(path.join(runtimeRoot, "dev-server", "package.json"), `${JSON.stringify({
|
|
594
1232
|
dependencies: {
|
|
595
1233
|
next: "16.2.1",
|
|
@@ -1616,7 +2254,7 @@ const resolvePushConfig = async (config, options) => {
|
|
|
1616
2254
|
envProject: process.env[BLODE_PROJECT_ENV]
|
|
1617
2255
|
});
|
|
1618
2256
|
const apiUrl = options.apiUrl ?? process.env["BLODEMD_API_URL"] ?? "https://api.blode.md";
|
|
1619
|
-
const authToken = (await resolveAuthToken(
|
|
2257
|
+
const authToken = (await resolveAuthToken())?.token;
|
|
1620
2258
|
const branch = options.branch ?? process.env["BLODEMD_BRANCH"] ?? process.env.GITHUB_REF_NAME ?? readGitValue([
|
|
1621
2259
|
"rev-parse",
|
|
1622
2260
|
"--abbrev-ref",
|
|
@@ -1633,7 +2271,7 @@ const resolvePushConfig = async (config, options) => {
|
|
|
1633
2271
|
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
2272
|
throw new Error(`Invalid project slug "${project}". ${projectSlugError}`);
|
|
1635
2273
|
}
|
|
1636
|
-
if (!authToken) throw new Error("
|
|
2274
|
+
if (!authToken) throw new Error("Not logged in. Run \"blodemd login\" to authenticate.");
|
|
1637
2275
|
return {
|
|
1638
2276
|
apiUrl,
|
|
1639
2277
|
authToken,
|
|
@@ -1656,8 +2294,7 @@ const autoCreateProject = async (project, projectDisplayName, apiUrl, headers) =
|
|
|
1656
2294
|
headers,
|
|
1657
2295
|
method: "POST"
|
|
1658
2296
|
}, "Failed to create project");
|
|
1659
|
-
log.success(`Project ${chalk.cyan(createResult.
|
|
1660
|
-
log.info(`API key for CI: ${chalk.dim(createResult.token)}`);
|
|
2297
|
+
log.success(`Project ${chalk.cyan(createResult.slug)} created`);
|
|
1661
2298
|
return true;
|
|
1662
2299
|
};
|
|
1663
2300
|
const scaffoldDocsSite = async (directory, options) => {
|
|
@@ -1727,29 +2364,9 @@ program.name("blodemd").description("Blode.md CLI").version(cliVersion);
|
|
|
1727
2364
|
program.hook("preAction", () => {
|
|
1728
2365
|
assertSupportedNodeVersion();
|
|
1729
2366
|
});
|
|
1730
|
-
program.command("login").description("Authenticate with Blode.md
|
|
2367
|
+
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
2368
|
intro(chalk.bold("blodemd login"));
|
|
1732
2369
|
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
2370
|
const { authorizeUrl, tokenUrl } = buildOAuthUrls(resolveSupabaseConfig());
|
|
1754
2371
|
const clientId = OAUTH_CLIENT_ID;
|
|
1755
2372
|
const port = parsePort(options.port);
|
|
@@ -1766,6 +2383,7 @@ program.command("login").description("Authenticate with Blode.md").option("--tok
|
|
|
1766
2383
|
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
1767
2384
|
authUrl.searchParams.set("state", state);
|
|
1768
2385
|
authUrl.searchParams.set("scope", "openid email profile");
|
|
2386
|
+
authUrl.searchParams.set("provider", "github");
|
|
1769
2387
|
const callbackPromise = waitForOAuthCode({
|
|
1770
2388
|
expectedState: state,
|
|
1771
2389
|
redirectUrl,
|
|
@@ -1818,15 +2436,6 @@ program.command("whoami").description("Show current authentication").action(asyn
|
|
|
1818
2436
|
log.warn("Not logged in. Run \"blodemd login\" to authenticate.");
|
|
1819
2437
|
return;
|
|
1820
2438
|
}
|
|
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
2439
|
const status = resolveTokenStatus(resolved);
|
|
1831
2440
|
const email = resolved.user?.email ?? await fetchUserEmail(process.env["BLODEMD_API_URL"] ?? "https://api.blode.md", resolved.token);
|
|
1832
2441
|
if (email) log.info(`Logged in as ${chalk.cyan(email)}`);
|
|
@@ -1866,7 +2475,7 @@ program.command("validate").description("Validate docs.json").argument("[dir]",
|
|
|
1866
2475
|
reportCommandError("Validation failed", error);
|
|
1867
2476
|
}
|
|
1868
2477
|
});
|
|
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("--
|
|
2478
|
+
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
2479
|
intro(chalk.bold("blodemd push"));
|
|
1871
2480
|
const s = spinner();
|
|
1872
2481
|
try {
|