blodemd 0.0.5 → 0.0.7

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.
Files changed (187) hide show
  1. package/README.md +2 -2
  2. package/dev-server/app/[[...slug]]/page.tsx +139 -0
  3. package/dev-server/app/blodemd-dev/invalidate/route.ts +12 -0
  4. package/dev-server/app/blodemd-dev/static/[...path]/route.ts +32 -0
  5. package/dev-server/app/blodemd-dev/version/route.ts +14 -0
  6. package/dev-server/app/blodemd-internal/proxy/route.ts +86 -0
  7. package/dev-server/app/error.tsx +24 -0
  8. package/dev-server/app/favicon.ico +0 -0
  9. package/dev-server/app/globals.css +4 -0
  10. package/dev-server/app/layout.tsx +38 -0
  11. package/dev-server/app/not-found.tsx +18 -0
  12. package/dev-server/app/search/route.ts +17 -0
  13. package/dev-server/components/dev-reload-script.tsx +86 -0
  14. package/dev-server/components/providers.tsx +15 -0
  15. package/dev-server/lib/dev-state.ts +8 -0
  16. package/dev-server/lib/local-content-source.ts +103 -0
  17. package/dev-server/lib/local-runtime.tsx +558 -0
  18. package/dev-server/next-env.d.ts +5 -0
  19. package/dev-server/next.config.js +46 -0
  20. package/dev-server/package.json +57 -0
  21. package/dev-server/postcss.config.mjs +7 -0
  22. package/dev-server/public/glide-variable.woff2 +0 -0
  23. package/dev-server/tsconfig.json +50 -0
  24. package/dist/cli.mjs +311 -86
  25. package/dist/cli.mjs.map +1 -1
  26. package/docs/app/globals.css +457 -0
  27. package/docs/components/api/api-playground.tsx +295 -0
  28. package/docs/components/api/api-reference.tsx +121 -0
  29. package/docs/components/content/collection-index.tsx +114 -0
  30. package/docs/components/docs/contextual-menu.tsx +406 -0
  31. package/docs/components/docs/copy-page-menu.tsx +255 -0
  32. package/docs/components/docs/doc-header.tsx +210 -0
  33. package/docs/components/docs/doc-shell.tsx +313 -0
  34. package/docs/components/docs/doc-sidebar.tsx +211 -0
  35. package/docs/components/docs/doc-toc.tsx +45 -0
  36. package/docs/components/docs/mobile-nav.tsx +205 -0
  37. package/docs/components/icons/doc-icon.tsx +96 -0
  38. package/docs/components/mdx/accordion.tsx +83 -0
  39. package/docs/components/mdx/badge.tsx +79 -0
  40. package/docs/components/mdx/callout.tsx +88 -0
  41. package/docs/components/mdx/card.tsx +110 -0
  42. package/docs/components/mdx/code-block.tsx +75 -0
  43. package/docs/components/mdx/code-group.tsx +94 -0
  44. package/docs/components/mdx/color.tsx +87 -0
  45. package/docs/components/mdx/columns.tsx +25 -0
  46. package/docs/components/mdx/expandable.tsx +45 -0
  47. package/docs/components/mdx/field-layout.tsx +77 -0
  48. package/docs/components/mdx/frame.tsx +23 -0
  49. package/docs/components/mdx/get-text-content.ts +18 -0
  50. package/docs/components/mdx/icon.tsx +12 -0
  51. package/docs/components/mdx/index.tsx +107 -0
  52. package/docs/components/mdx/installer.tsx +20 -0
  53. package/docs/components/mdx/panel.tsx +11 -0
  54. package/docs/components/mdx/param-field.tsx +56 -0
  55. package/docs/components/mdx/preview.tsx +36 -0
  56. package/docs/components/mdx/prompt.tsx +63 -0
  57. package/docs/components/mdx/request-example.tsx +27 -0
  58. package/docs/components/mdx/response-field.tsx +42 -0
  59. package/docs/components/mdx/steps.tsx +92 -0
  60. package/docs/components/mdx/tabs.tsx +88 -0
  61. package/docs/components/mdx/tile.tsx +43 -0
  62. package/docs/components/mdx/tooltip.tsx +71 -0
  63. package/docs/components/mdx/tree.tsx +120 -0
  64. package/docs/components/mdx/type-table.tsx +71 -0
  65. package/docs/components/mdx/update.tsx +44 -0
  66. package/docs/components/mdx/video.tsx +12 -0
  67. package/docs/components/mdx/view.tsx +66 -0
  68. package/docs/components/providers.tsx +15 -0
  69. package/docs/components/ui/breadcrumb.tsx +92 -0
  70. package/docs/components/ui/button.tsx +90 -0
  71. package/docs/components/ui/card.tsx +92 -0
  72. package/docs/components/ui/command.tsx +139 -0
  73. package/docs/components/ui/dialog.tsx +97 -0
  74. package/docs/components/ui/field.tsx +237 -0
  75. package/docs/components/ui/input.tsx +105 -0
  76. package/docs/components/ui/label.tsx +22 -0
  77. package/docs/components/ui/popover.tsx +72 -0
  78. package/docs/components/ui/search.tsx +384 -0
  79. package/docs/components/ui/separator.tsx +26 -0
  80. package/docs/components/ui/sheet.tsx +104 -0
  81. package/docs/components/ui/sidebar.tsx +433 -0
  82. package/docs/components/ui/theme-toggle.tsx +62 -0
  83. package/docs/components/ui/tooltip.tsx +53 -0
  84. package/docs/lib/contextual-options.ts +193 -0
  85. package/docs/lib/docs-collection.ts +22 -0
  86. package/docs/lib/mdx.ts +87 -0
  87. package/docs/lib/navigation.ts +288 -0
  88. package/docs/lib/openapi.ts +158 -0
  89. package/docs/lib/routes.ts +44 -0
  90. package/docs/lib/server-cache.ts +83 -0
  91. package/docs/lib/shiki.ts +40 -0
  92. package/docs/lib/theme.ts +29 -0
  93. package/docs/lib/toc.ts +2 -0
  94. package/docs/lib/utils.ts +5 -0
  95. package/package.json +43 -6
  96. package/packages/@repo/common/dist/index.d.ts +9 -0
  97. package/packages/@repo/common/dist/index.d.ts.map +1 -0
  98. package/packages/@repo/common/dist/index.js +42 -0
  99. package/packages/@repo/common/package.json +34 -0
  100. package/packages/@repo/common/src/index.ts +51 -0
  101. package/packages/@repo/contracts/dist/api-key.d.ts +30 -0
  102. package/packages/@repo/contracts/dist/api-key.d.ts.map +1 -0
  103. package/packages/@repo/contracts/dist/api-key.js +20 -0
  104. package/packages/@repo/contracts/dist/dates.d.ts +4 -0
  105. package/packages/@repo/contracts/dist/dates.d.ts.map +1 -0
  106. package/packages/@repo/contracts/dist/dates.js +2 -0
  107. package/packages/@repo/contracts/dist/deployment.d.ts +71 -0
  108. package/packages/@repo/contracts/dist/deployment.d.ts.map +1 -0
  109. package/packages/@repo/contracts/dist/deployment.js +46 -0
  110. package/packages/@repo/contracts/dist/domain.d.ts +94 -0
  111. package/packages/@repo/contracts/dist/domain.d.ts.map +1 -0
  112. package/packages/@repo/contracts/dist/domain.js +36 -0
  113. package/packages/@repo/contracts/dist/ids.d.ts +14 -0
  114. package/packages/@repo/contracts/dist/ids.d.ts.map +1 -0
  115. package/packages/@repo/contracts/dist/ids.js +10 -0
  116. package/packages/@repo/contracts/dist/index.d.ts +10 -0
  117. package/packages/@repo/contracts/dist/index.d.ts.map +1 -0
  118. package/packages/@repo/contracts/dist/index.js +11 -0
  119. package/packages/@repo/contracts/dist/pagination.d.ts +23 -0
  120. package/packages/@repo/contracts/dist/pagination.d.ts.map +1 -0
  121. package/packages/@repo/contracts/dist/pagination.js +15 -0
  122. package/packages/@repo/contracts/dist/project.d.ts +25 -0
  123. package/packages/@repo/contracts/dist/project.d.ts.map +1 -0
  124. package/packages/@repo/contracts/dist/project.js +23 -0
  125. package/packages/@repo/contracts/dist/tenant.d.ts +111 -0
  126. package/packages/@repo/contracts/dist/tenant.d.ts.map +1 -0
  127. package/packages/@repo/contracts/dist/tenant.js +56 -0
  128. package/packages/@repo/contracts/dist/user.d.ts +9 -0
  129. package/packages/@repo/contracts/dist/user.d.ts.map +1 -0
  130. package/packages/@repo/contracts/dist/user.js +9 -0
  131. package/packages/@repo/contracts/package.json +37 -0
  132. package/packages/@repo/contracts/src/api-key.ts +27 -0
  133. package/packages/@repo/contracts/src/dates.ts +4 -0
  134. package/packages/@repo/contracts/src/deployment.ts +73 -0
  135. package/packages/@repo/contracts/src/domain.ts +51 -0
  136. package/packages/@repo/contracts/src/ids.ts +22 -0
  137. package/packages/@repo/contracts/src/index.ts +11 -0
  138. package/packages/@repo/contracts/src/pagination.ts +21 -0
  139. package/packages/@repo/contracts/src/project.ts +30 -0
  140. package/packages/@repo/contracts/src/tenant.ts +92 -0
  141. package/packages/@repo/contracts/src/user.ts +12 -0
  142. package/packages/@repo/models/dist/docs-config.d.ts +985 -0
  143. package/packages/@repo/models/dist/docs-config.d.ts.map +1 -0
  144. package/packages/@repo/models/dist/docs-config.js +548 -0
  145. package/packages/@repo/models/dist/index.d.ts +3 -0
  146. package/packages/@repo/models/dist/index.d.ts.map +1 -0
  147. package/packages/@repo/models/dist/index.js +3 -0
  148. package/packages/@repo/models/dist/tenant.d.ts +25 -0
  149. package/packages/@repo/models/dist/tenant.d.ts.map +1 -0
  150. package/packages/@repo/models/dist/tenant.js +1 -0
  151. package/packages/@repo/models/package.json +37 -0
  152. package/packages/@repo/models/src/docs-config.ts +648 -0
  153. package/packages/@repo/models/src/index.ts +3 -0
  154. package/packages/@repo/models/src/tenant.ts +29 -0
  155. package/packages/@repo/prebuild/dist/index.d.ts +2 -0
  156. package/packages/@repo/prebuild/dist/index.d.ts.map +1 -0
  157. package/packages/@repo/prebuild/dist/index.js +2 -0
  158. package/packages/@repo/prebuild/dist/openapi.d.ts +43 -0
  159. package/packages/@repo/prebuild/dist/openapi.d.ts.map +1 -0
  160. package/packages/@repo/prebuild/dist/openapi.js +58 -0
  161. package/packages/@repo/prebuild/package.json +39 -0
  162. package/packages/@repo/prebuild/src/index.ts +2 -0
  163. package/packages/@repo/prebuild/src/openapi.ts +116 -0
  164. package/packages/@repo/previewing/dist/blob-source.d.ts +16 -0
  165. package/packages/@repo/previewing/dist/blob-source.d.ts.map +1 -0
  166. package/packages/@repo/previewing/dist/blob-source.js +110 -0
  167. package/packages/@repo/previewing/dist/content-source.d.ts +12 -0
  168. package/packages/@repo/previewing/dist/content-source.d.ts.map +1 -0
  169. package/packages/@repo/previewing/dist/content-source.js +1 -0
  170. package/packages/@repo/previewing/dist/fs-source.d.ts +11 -0
  171. package/packages/@repo/previewing/dist/fs-source.d.ts.map +1 -0
  172. package/packages/@repo/previewing/dist/fs-source.js +72 -0
  173. package/packages/@repo/previewing/dist/index.d.ts +120 -0
  174. package/packages/@repo/previewing/dist/index.d.ts.map +1 -0
  175. package/packages/@repo/previewing/dist/index.js +984 -0
  176. package/packages/@repo/previewing/package.json +41 -0
  177. package/packages/@repo/previewing/src/blob-source.ts +167 -0
  178. package/packages/@repo/previewing/src/content-source.ts +12 -0
  179. package/packages/@repo/previewing/src/fs-source.ts +104 -0
  180. package/packages/@repo/previewing/src/index.ts +1490 -0
  181. package/packages/@repo/validation/dist/index.d.ts +12 -0
  182. package/packages/@repo/validation/dist/index.d.ts.map +1 -0
  183. package/packages/@repo/validation/dist/index.js +30 -0
  184. package/packages/@repo/validation/package.json +37 -0
  185. package/packages/@repo/validation/src/index.ts +59 -0
  186. package/packages/@repo/validation/src/mintlify-docs-schema.json +5016 -0
  187. package/scripts/prepare-package.mjs +39 -0
@@ -0,0 +1,50 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "declaration": true,
5
+ "declarationMap": true,
6
+ "esModuleInterop": true,
7
+ "incremental": false,
8
+ "isolatedModules": true,
9
+ "jsx": "react-jsx",
10
+ "lib": [
11
+ "es2022",
12
+ "DOM",
13
+ "DOM.Iterable"
14
+ ],
15
+ "module": "ESNext",
16
+ "moduleDetection": "force",
17
+ "moduleResolution": "Bundler",
18
+ "noEmit": true,
19
+ "noUncheckedIndexedAccess": true,
20
+ "paths": {
21
+ "@/*": [
22
+ "../docs/*"
23
+ ],
24
+ "@dev/*": [
25
+ "./*"
26
+ ]
27
+ },
28
+ "plugins": [
29
+ {
30
+ "name": "next"
31
+ }
32
+ ],
33
+ "resolveJsonModule": true,
34
+ "skipLibCheck": true,
35
+ "strict": true,
36
+ "strictNullChecks": true,
37
+ "target": "ES2022"
38
+ },
39
+ "exclude": [
40
+ "node_modules"
41
+ ],
42
+ "include": [
43
+ "**/*.ts",
44
+ "**/*.tsx",
45
+ "next-env.d.ts",
46
+ "next.config.js",
47
+ ".next/types/**/*.ts",
48
+ ".next/dev/types/**/*.ts"
49
+ ]
50
+ }
package/dist/cli.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
2
3
  import { spawn, spawnSync } from "node:child_process";
3
4
  import fs, { mkdir, readFile, rm, writeFile } from "node:fs/promises";
4
5
  import path, { join } from "node:path";
@@ -8,12 +9,14 @@ import { Command } from "commander";
8
9
  import open from "open";
9
10
  import { homedir } from "node:os";
10
11
  import { once } from "node:events";
12
+ import { createServer } from "node:net";
11
13
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
12
14
  import { fileURLToPath } from "node:url";
13
15
  import { createFsSource, loadSiteConfig } from "@repo/previewing";
14
16
  import { watch } from "chokidar";
15
- import { createServer } from "node:http";
17
+ import { createServer as createServer$1 } from "node:http";
16
18
  import { createHash, randomBytes } from "node:crypto";
19
+ import { readFileSync } from "node:fs";
17
20
  //#region src/constants.ts
18
21
  const CLI_NAME = "blodemd";
19
22
  const OAUTH_CLIENT_ID = "6b5f9860-fe96-4a83-b1ad-266260523c91";
@@ -288,9 +291,20 @@ const resolveTokenStatus = (token) => {
288
291
  };
289
292
  };
290
293
  //#endregion
294
+ //#region src/site-config.ts
295
+ const CONFIG_FILE$2 = "docs.json";
296
+ const loadValidatedSiteConfig = async (root) => {
297
+ const result = await loadSiteConfig(createFsSource(root));
298
+ if (!result.ok) throw new CliError(result.errors.join("\n"), EXIT_CODES.VALIDATION, `Make sure ${CONFIG_FILE$2} exists and is valid JSON.`);
299
+ return {
300
+ config: result.config,
301
+ warnings: result.warnings
302
+ };
303
+ };
304
+ //#endregion
291
305
  //#region src/dev/resolve-root.ts
292
306
  const CONFIG_FILE$1 = "docs.json";
293
- const fileExists$2 = async (filePath) => {
307
+ const fileExists$1 = async (filePath) => {
294
308
  try {
295
309
  await fs.access(filePath);
296
310
  return true;
@@ -298,21 +312,17 @@ const fileExists$2 = async (filePath) => {
298
312
  return false;
299
313
  }
300
314
  };
301
- const resolveDocsRoot$1 = async (dir) => {
315
+ const resolveDocsRoot = async (dir) => {
302
316
  if (dir) return path.resolve(process.cwd(), dir);
303
317
  const candidates = [
304
318
  process.cwd(),
305
319
  path.join(process.cwd(), "docs"),
306
320
  path.join(process.cwd(), "apps/docs")
307
321
  ];
308
- for (const candidate of candidates) if (await fileExists$2(path.join(candidate, CONFIG_FILE$1))) return candidate;
322
+ for (const candidate of candidates) if (await fileExists$1(path.join(candidate, CONFIG_FILE$1))) return candidate;
309
323
  return process.cwd();
310
324
  };
311
- const validateDocsRoot = async (root) => {
312
- const result = await loadSiteConfig(createFsSource(root));
313
- if (!result.ok) throw new CliError(result.errors.join("\n"), EXIT_CODES.VALIDATION, `Make sure ${CONFIG_FILE$1} exists and is valid JSON.`);
314
- return result;
315
- };
325
+ const validateDocsRoot = async (root) => await loadValidatedSiteConfig(root);
316
326
  //#endregion
317
327
  //#region src/dev/watcher.ts
318
328
  const INVALIDATE_ENDPOINT = "/blodemd-dev/invalidate";
@@ -375,12 +385,20 @@ const createDevWatcher = ({ port, root }) => {
375
385
  //#region src/dev/command.ts
376
386
  const DEV_READY_ENDPOINT = "/blodemd-dev/version";
377
387
  const DEV_READY_TIMEOUT_MS = 45e3;
388
+ const DEV_PORT_SCAN_LIMIT = 10;
389
+ const DEV_SHUTDOWN_TIMEOUT_MS = 5e3;
390
+ const LOCALHOST = "127.0.0.1";
391
+ const RUNTIME_EXCLUDE_DIRS = new Set([
392
+ ".next",
393
+ ".turbo",
394
+ "node_modules"
395
+ ]);
378
396
  const parsePositiveInteger$1 = (value, label) => {
379
397
  const parsed = Number.parseInt(value, 10);
380
398
  if (!Number.isInteger(parsed) || parsed <= 0) throw new CliError(`${label} must be a positive integer.`, EXIT_CODES.VALIDATION);
381
399
  return parsed;
382
400
  };
383
- const fileExists$1 = async (filePath) => {
401
+ const fileExists = async (filePath) => {
384
402
  try {
385
403
  await fs.access(filePath);
386
404
  return true;
@@ -388,11 +406,149 @@ const fileExists$1 = async (filePath) => {
388
406
  return false;
389
407
  }
390
408
  };
409
+ const probePortAvailability = async (port) => {
410
+ const server = createServer();
411
+ const listening = (async () => {
412
+ await once(server, "listening");
413
+ return { kind: "listening" };
414
+ })();
415
+ const errored = (async () => {
416
+ const [error] = await once(server, "error");
417
+ return {
418
+ error,
419
+ kind: "error"
420
+ };
421
+ })();
422
+ server.listen({
423
+ exclusive: true,
424
+ host: LOCALHOST,
425
+ port
426
+ });
427
+ const outcome = await Promise.race([listening, errored]);
428
+ if (outcome.kind === "error") {
429
+ if (outcome.error.code === "EADDRINUSE" || outcome.error.code === "EACCES") return false;
430
+ throw outcome.error;
431
+ }
432
+ server.close();
433
+ await once(server, "close");
434
+ return true;
435
+ };
436
+ const resolveDevPort = async (requestedPort, probePort = probePortAvailability) => {
437
+ for (let offset = 0; offset < DEV_PORT_SCAN_LIMIT; offset += 1) {
438
+ const candidate = requestedPort + offset;
439
+ if (candidate > 65535) break;
440
+ if (await probePort(candidate)) return candidate;
441
+ }
442
+ throw new CliError(`No available port found within ${DEV_PORT_SCAN_LIMIT} attempts starting at ${requestedPort}.`, EXIT_CODES.ERROR, "Close the process using the port or pass a different --port value.");
443
+ };
444
+ const shutdownChildProcess = async (child, timeoutMs = DEV_SHUTDOWN_TIMEOUT_MS) => {
445
+ if (child.exitCode !== null) return;
446
+ const timer = setTimeout(() => {
447
+ if (child.exitCode === null) child.kill("SIGKILL");
448
+ }, timeoutMs);
449
+ const exitPromise = once(child, "exit");
450
+ try {
451
+ child.kill("SIGTERM");
452
+ } catch (error) {
453
+ clearTimeout(timer);
454
+ if (error.code === "ESRCH") return;
455
+ throw error;
456
+ }
457
+ await exitPromise.finally(() => {
458
+ clearTimeout(timer);
459
+ });
460
+ };
461
+ /**
462
+ * Derive the CLI npm package root from the running script path.
463
+ * The CLI entry point is at `<pkg-root>/dist/cli.mjs`.
464
+ */
465
+ const resolveCliPackageRoot = (cliFilePath) => path.dirname(path.dirname(cliFilePath));
466
+ const copyStandaloneTree = async (sourceDir, targetDir) => {
467
+ await fs.cp(sourceDir, targetDir, {
468
+ filter: (source) => {
469
+ const relative = path.relative(sourceDir, source);
470
+ if (!relative) return true;
471
+ const topSegment = relative.split(path.sep)[0] ?? "";
472
+ return !RUNTIME_EXCLUDE_DIRS.has(topSegment);
473
+ },
474
+ recursive: true
475
+ });
476
+ };
477
+ const isStandaloneCliInstall = async (cliPackageRoot) => {
478
+ try {
479
+ return (await fs.realpath(cliPackageRoot)).split(path.sep).includes("node_modules");
480
+ } catch {
481
+ return cliPackageRoot.split(path.sep).includes("node_modules");
482
+ }
483
+ };
484
+ const materializeStandaloneRuntime = async (cliPackageRoot) => {
485
+ const runtimeRoot = path.join(CONFIG_DIR, "standalone-runtime");
486
+ await fs.rm(runtimeRoot, {
487
+ force: true,
488
+ recursive: true
489
+ });
490
+ await fs.mkdir(runtimeRoot, { recursive: true });
491
+ for (const dir of [
492
+ "dev-server",
493
+ "docs",
494
+ "packages"
495
+ ]) await copyStandaloneTree(path.join(cliPackageRoot, dir), path.join(runtimeRoot, dir));
496
+ await fs.symlink(path.join(cliPackageRoot, "node_modules"), path.join(runtimeRoot, "node_modules"), process.platform === "win32" ? "junction" : "dir");
497
+ await fs.writeFile(path.join(runtimeRoot, "dev-server", "package.json"), `${JSON.stringify({
498
+ dependencies: {
499
+ next: "16.2.1",
500
+ react: "^19.2.0",
501
+ "react-dom": "^19.2.0"
502
+ },
503
+ devDependencies: {
504
+ "@types/node": "^22.19.15",
505
+ "@types/react": "19.2.14",
506
+ "@types/react-dom": "19.2.3",
507
+ typescript: "6.0.2"
508
+ },
509
+ name: "blodemd-dev-server",
510
+ private: true,
511
+ type: "module"
512
+ }, null, 2)}\n`);
513
+ return {
514
+ devServerDir: path.join(runtimeRoot, "dev-server"),
515
+ packagesDir: path.join(runtimeRoot, "packages")
516
+ };
517
+ };
518
+ /**
519
+ * Check if a shipped dev-server exists alongside the CLI (npm-installed mode).
520
+ * Verifies both the dev-server directory AND that `next` is resolvable
521
+ * (it's a dependency when npm-installed, but not in the monorepo).
522
+ */
523
+ const findStandaloneDevServer = async (cliPackageRoot) => {
524
+ const devServerDir = path.join(cliPackageRoot, "dev-server");
525
+ if (!await fileExists(path.join(devServerDir, "next.config.js"))) return null;
526
+ if (!await isStandaloneCliInstall(cliPackageRoot)) return null;
527
+ try {
528
+ createRequire(path.join(cliPackageRoot, "package.json")).resolve("next/package.json");
529
+ } catch {
530
+ return null;
531
+ }
532
+ const runtime = await materializeStandaloneRuntime(cliPackageRoot);
533
+ return {
534
+ devServerDir: runtime.devServerDir,
535
+ mode: "standalone",
536
+ nextPackageRoot: cliPackageRoot,
537
+ packagesDir: runtime.packagesDir
538
+ };
539
+ };
540
+ /**
541
+ * Resolve the `next` CLI binary from the blodemd package's own dependencies.
542
+ */
543
+ const resolveNextBin = (cliPackageRoot) => {
544
+ const nextPkgPath = createRequire(path.join(cliPackageRoot, "package.json")).resolve("next/package.json");
545
+ return path.join(path.dirname(nextPkgPath), "dist", "bin", "next");
546
+ };
391
547
  const findMonorepoRoot = async (start) => {
392
548
  let current = start;
393
549
  while (true) {
394
550
  const packageJsonPath = path.join(current, "package.json");
395
- if (await fileExists$1(packageJsonPath)) {
551
+ if (await fileExists(packageJsonPath)) {
396
552
  const raw = await fs.readFile(packageJsonPath, "utf8");
397
553
  const workspaces = JSON.parse(raw).workspaces ?? [];
398
554
  if (workspaces.includes("apps/*") && workspaces.includes("packages/*")) return current;
@@ -401,7 +557,48 @@ const findMonorepoRoot = async (start) => {
401
557
  if (parent === current) break;
402
558
  current = parent;
403
559
  }
404
- throw new CliError("Could not locate the blodemd monorepo root.", EXIT_CODES.ERROR, "The monorepo-only dev server must be run from this repository checkout.");
560
+ throw new CliError("Could not locate the blodemd dev server.", EXIT_CODES.ERROR, "Make sure blodemd is installed correctly (npm i blodemd).");
561
+ };
562
+ const resolveDevServer = async (cliFilePath) => {
563
+ const standalone = await findStandaloneDevServer(resolveCliPackageRoot(cliFilePath));
564
+ if (standalone) return standalone;
565
+ return {
566
+ mode: "monorepo",
567
+ repoRoot: await findMonorepoRoot(path.dirname(cliFilePath))
568
+ };
569
+ };
570
+ const spawnDevServer = (server, { root, port }) => {
571
+ if (server.mode === "standalone") {
572
+ const nextBin = resolveNextBin(server.nextPackageRoot);
573
+ return spawn(process.execPath, [
574
+ nextBin,
575
+ "dev",
576
+ "--webpack"
577
+ ], {
578
+ cwd: server.devServerDir,
579
+ env: {
580
+ ...process.env,
581
+ BLODEMD_PACKAGES_DIR: server.packagesDir,
582
+ DOCS_ROOT: root,
583
+ NODE_PATH: [server.packagesDir, process.env.NODE_PATH].filter(Boolean).join(path.delimiter),
584
+ PORT: String(port)
585
+ },
586
+ stdio: "inherit"
587
+ });
588
+ }
589
+ return spawn(process.platform === "win32" ? "npm.cmd" : "npm", [
590
+ "run",
591
+ "dev",
592
+ "--workspace=dev-server"
593
+ ], {
594
+ cwd: server.repoRoot,
595
+ env: {
596
+ ...process.env,
597
+ DOCS_ROOT: root,
598
+ PORT: String(port)
599
+ },
600
+ stdio: "inherit"
601
+ });
405
602
  };
406
603
  const waitForServer = async ({ child, port }) => {
407
604
  const url = `http://localhost:${port}${DEV_READY_ENDPOINT}`;
@@ -418,29 +615,18 @@ const waitForServer = async ({ child, port }) => {
418
615
  }
419
616
  throw new CliError("Timed out waiting for the local dev server to start.", EXIT_CODES.ERROR);
420
617
  };
421
- const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
422
618
  const devCommand = async ({ dir, openBrowser, port: portValue }) => {
423
619
  intro(chalk.bold("blodemd dev"));
424
620
  try {
425
- const port = parsePositiveInteger$1(portValue, "Port");
426
- const root = await resolveDocsRoot$1(dir);
621
+ const resolvedPort = await resolveDevPort(parsePositiveInteger$1(portValue, "Port"));
622
+ const root = await resolveDocsRoot(dir);
427
623
  await validateDocsRoot(root);
428
- const cliFilePath = fileURLToPath(import.meta.url);
429
- const repoRoot = await findMonorepoRoot(path.dirname(cliFilePath));
430
- const localUrl = `http://localhost:${port}`;
624
+ const server = await resolveDevServer(fileURLToPath(import.meta.url));
625
+ const localUrl = `http://localhost:${resolvedPort}`;
431
626
  log.info(`Docs root: ${chalk.cyan(root)}`);
432
- const child = spawn(npmCommand, [
433
- "run",
434
- "dev",
435
- "--workspace=dev-server"
436
- ], {
437
- cwd: repoRoot,
438
- env: {
439
- ...process.env,
440
- DOCS_ROOT: root,
441
- PORT: String(port)
442
- },
443
- stdio: "inherit"
627
+ const child = spawnDevServer(server, {
628
+ port: resolvedPort,
629
+ root
444
630
  });
445
631
  let watcher = null;
446
632
  let shuttingDown = false;
@@ -451,35 +637,28 @@ const devCommand = async ({ dir, openBrowser, port: portValue }) => {
451
637
  await watcher.close();
452
638
  watcher = null;
453
639
  }
454
- if (child.exitCode === null && !child.killed) child.kill("SIGTERM");
640
+ await shutdownChildProcess(child);
455
641
  };
456
- const onSignal = async () => {
457
- await closeAll();
458
- };
459
- process.once("SIGINT", onSignal);
460
- process.once("SIGTERM", onSignal);
642
+ process.once("SIGINT", closeAll);
643
+ process.once("SIGTERM", closeAll);
461
644
  try {
462
645
  await waitForServer({
463
646
  child,
464
- port
647
+ port: resolvedPort
465
648
  });
466
649
  watcher = await createDevWatcher({
467
- port,
650
+ port: resolvedPort,
468
651
  root
469
652
  });
470
653
  log.success(`Dev server running at ${chalk.cyan(localUrl)}`);
471
654
  if (openBrowser) await open(localUrl);
472
655
  const [code, signal] = await once(child, "exit");
473
- await closeAll();
474
- process.removeListener("SIGINT", onSignal);
475
- process.removeListener("SIGTERM", onSignal);
476
656
  if (shuttingDown || signal === "SIGINT" || signal === "SIGTERM") return;
477
657
  if (code !== 0) throw new CliError(`The local dev server exited with code ${code ?? "unknown"}.`, EXIT_CODES.ERROR);
478
- } catch (error) {
658
+ } finally {
479
659
  await closeAll();
480
- process.removeListener("SIGINT", onSignal);
481
- process.removeListener("SIGTERM", onSignal);
482
- throw error;
660
+ process.removeListener("SIGINT", closeAll);
661
+ process.removeListener("SIGTERM", closeAll);
483
662
  }
484
663
  } catch (error) {
485
664
  const cliError = toCliError(error);
@@ -511,7 +690,7 @@ const waitForOAuthCode = (options) => {
511
690
  });
512
691
  for (const socket of sockets) socket.destroy();
513
692
  };
514
- const httpServer = createServer((request, response) => {
693
+ const httpServer = createServer$1((request, response) => {
515
694
  if (!request.url) {
516
695
  response.writeHead(400, { "content-type": "text/html; charset=utf-8" });
517
696
  response.end(errorHtml("Missing request URL"));
@@ -568,6 +747,53 @@ const createOAuthState = () => randomBytes(24).toString("hex");
568
747
  const createCodeVerifier = () => randomBytes(64).toString("base64url");
569
748
  const createCodeChallenge = (verifier) => createHash("sha256").update(verifier).digest().toString("base64url");
570
749
  //#endregion
750
+ //#region src/runtime.ts
751
+ const MIN_SUPPORTED_NODE_VERSION = [
752
+ 20,
753
+ 17,
754
+ 0
755
+ ];
756
+ const MAX_SUPPORTED_NODE_MAJOR = 25;
757
+ const SUPPORTED_NODE_RANGE = ">=20.17.0 <25";
758
+ const parseVersion = (input) => {
759
+ const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(input.trim());
760
+ if (!match) return null;
761
+ const [, majorText = "", minorText = "", patchText = ""] = match;
762
+ if (!majorText || !minorText || !patchText) return null;
763
+ const major = Number.parseInt(majorText, 10);
764
+ const minor = Number.parseInt(minorText, 10);
765
+ const patch = Number.parseInt(patchText, 10);
766
+ if ([
767
+ major,
768
+ minor,
769
+ patch
770
+ ].some((value) => Number.isNaN(value))) return null;
771
+ return [
772
+ major,
773
+ minor,
774
+ patch
775
+ ];
776
+ };
777
+ const isSupportedNodeVersion = (version) => {
778
+ const parsed = parseVersion(version);
779
+ if (!parsed) return false;
780
+ const [major, minor, patch] = parsed;
781
+ const [minMajor, minMinor, minPatch] = MIN_SUPPORTED_NODE_VERSION;
782
+ if (major >= MAX_SUPPORTED_NODE_MAJOR) return false;
783
+ if (major !== minMajor) return major > minMajor;
784
+ if (minor !== minMinor) return minor > minMinor;
785
+ return patch >= minPatch;
786
+ };
787
+ const assertSupportedNodeVersion = (version = process.versions.node) => {
788
+ if (isSupportedNodeVersion(version)) return;
789
+ throw new CliError(`blodemd requires Node.js ${SUPPORTED_NODE_RANGE}. Current version: ${version}.`, EXIT_CODES.VALIDATION, "Install a supported Node.js version and try again.");
790
+ };
791
+ const readCliVersion = (moduleUrl) => {
792
+ const moduleDir = path.dirname(fileURLToPath(moduleUrl));
793
+ const raw = readFileSync(path.resolve(moduleDir, "..", "package.json"), "utf8");
794
+ return JSON.parse(raw).version ?? "0.0.0";
795
+ };
796
+ //#endregion
571
797
  //#region src/cli.ts
572
798
  const CONFIG_FILE = "docs.json";
573
799
  const TEXT_CONTENT_TYPES = {
@@ -587,31 +813,6 @@ const ensureFile = async (filePath, content) => {
587
813
  await fs.writeFile(filePath, content, { flag: "wx" });
588
814
  } catch {}
589
815
  };
590
- const fileExists = async (filePath) => {
591
- try {
592
- await fs.access(filePath);
593
- return true;
594
- } catch {
595
- return false;
596
- }
597
- };
598
- const readConfig = async (root) => {
599
- const raw = await fs.readFile(path.join(root, CONFIG_FILE), "utf8");
600
- return {
601
- name: JSON.parse(raw).name,
602
- raw
603
- };
604
- };
605
- const resolveDocsRoot = async (dir) => {
606
- if (dir) return path.resolve(process.cwd(), dir);
607
- const candidates = [
608
- process.cwd(),
609
- path.join(process.cwd(), "docs"),
610
- path.join(process.cwd(), "apps/docs")
611
- ];
612
- for (const candidate of candidates) if (await fileExists(path.join(candidate, CONFIG_FILE))) return candidate;
613
- return process.cwd();
614
- };
615
816
  const readGitValue = (gitArgs) => {
616
817
  const result = spawnSync("git", gitArgs, {
617
818
  encoding: "utf8",
@@ -718,26 +919,48 @@ const autoCreateProject = async (project, apiUrl, headers) => {
718
919
  log.info(`API key for CI: ${chalk.dim(createResult.token)}`);
719
920
  return true;
720
921
  };
922
+ const MAX_BATCH_BYTES = 4 * 1024 * 1024;
721
923
  const uploadFiles = async (files, root, apiPath, deploymentId, headers, s) => {
722
924
  s.start(`Uploading ${files.length} files`);
723
- for (const [index, filePath] of files.entries()) {
724
- const relativePath = normalizeRelativePath(root, filePath);
725
- const content = await fs.readFile(filePath);
726
- await requestJson(apiPath(`/${deploymentId}/files`), {
727
- body: JSON.stringify({
728
- contentBase64: content.toString("base64"),
729
- contentType: getContentType(filePath),
730
- path: relativePath
731
- }),
925
+ const items = await Promise.all(files.map(async (filePath) => {
926
+ return {
927
+ contentBase64: (await fs.readFile(filePath)).toString("base64"),
928
+ contentType: getContentType(filePath),
929
+ path: normalizeRelativePath(root, filePath)
930
+ };
931
+ }));
932
+ const batches = [];
933
+ let current = [];
934
+ let currentBytes = 0;
935
+ for (const item of items) {
936
+ const itemBytes = item.contentBase64.length + item.path.length + 64;
937
+ if (current.length > 0 && currentBytes + itemBytes > MAX_BATCH_BYTES) {
938
+ batches.push(current);
939
+ current = [];
940
+ currentBytes = 0;
941
+ }
942
+ current.push(item);
943
+ currentBytes += itemBytes;
944
+ }
945
+ if (current.length > 0) batches.push(current);
946
+ let uploaded = 0;
947
+ for (const batch of batches) {
948
+ await requestJson(apiPath(`/${deploymentId}/files/batch`), {
949
+ body: JSON.stringify({ files: batch }),
732
950
  headers,
733
951
  method: "POST"
734
- }, `Failed to upload ${relativePath}`);
735
- s.message(`Uploading files (${index + 1}/${files.length})`);
952
+ }, "Failed to upload files");
953
+ uploaded += batch.length;
954
+ s.message(`Uploading files (${uploaded}/${files.length})`);
736
955
  }
737
956
  s.stop(`Uploaded ${chalk.cyan(String(files.length))} files`);
738
957
  };
739
958
  const program = new Command();
740
- program.name("blodemd").description("Blode.md CLI").version("0.0.3");
959
+ const cliVersion = readCliVersion(import.meta.url);
960
+ program.name("blodemd").description("Blode.md CLI").version(cliVersion);
961
+ program.hook("preAction", () => {
962
+ assertSupportedNodeVersion();
963
+ });
741
964
  program.command("login").description("Authenticate with Blode.md").option("--token", "Paste an API key instead of using browser login").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) => {
742
965
  intro(chalk.bold("blodemd login"));
743
966
  try {
@@ -847,7 +1070,7 @@ program.command("init").description("Scaffold a docs folder").argument("[dir]",
847
1070
  const root = path.resolve(process.cwd(), dir);
848
1071
  await fs.mkdir(root, { recursive: true });
849
1072
  await ensureFile(path.join(root, CONFIG_FILE), `${JSON.stringify({
850
- $schema: "https://mintlify.com/docs.json",
1073
+ $schema: "https://docs.blode.md/docs.json",
851
1074
  colors: { primary: "#0D9373" },
852
1075
  name: "my-project",
853
1076
  navigation: { groups: [{
@@ -867,7 +1090,8 @@ program.command("init").description("Scaffold a docs folder").argument("[dir]",
867
1090
  program.command("validate").description("Validate docs.json").argument("[dir]", "docs directory").action(async (dir) => {
868
1091
  intro(chalk.bold("blodemd validate"));
869
1092
  try {
870
- await readConfig(await resolveDocsRoot(dir));
1093
+ const { warnings } = await loadValidatedSiteConfig(await resolveDocsRoot(dir));
1094
+ for (const warning of warnings) log.warn(warning);
871
1095
  log.success(`${chalk.cyan(CONFIG_FILE)} is valid.`);
872
1096
  log.info("Done");
873
1097
  } catch (error) {
@@ -880,8 +1104,9 @@ program.command("push").description("Deploy docs").argument("[dir]", "docs direc
880
1104
  try {
881
1105
  const root = await resolveDocsRoot(dir);
882
1106
  s.start("Validating configuration");
883
- const config = await readConfig(root);
1107
+ const { config, warnings } = await loadValidatedSiteConfig(root);
884
1108
  s.stop("Configuration valid");
1109
+ for (const warning of warnings) log.warn(warning);
885
1110
  const { project, apiUrl, authToken, branch, commitMessage } = await resolvePushConfig(config, options);
886
1111
  s.start("Collecting files");
887
1112
  const files = await collectFiles(root);