blodemd 0.0.4 → 0.0.6

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 (185) hide show
  1. package/README.md +12 -1
  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/globals.css +4 -0
  9. package/dev-server/app/layout.tsx +38 -0
  10. package/dev-server/app/not-found.tsx +18 -0
  11. package/dev-server/app/search/route.ts +17 -0
  12. package/dev-server/components/dev-reload-script.tsx +86 -0
  13. package/dev-server/components/providers.tsx +15 -0
  14. package/dev-server/lib/dev-state.ts +8 -0
  15. package/dev-server/lib/local-content-source.ts +103 -0
  16. package/dev-server/lib/local-runtime.tsx +558 -0
  17. package/dev-server/next.config.js +46 -0
  18. package/dev-server/package.json +57 -0
  19. package/dev-server/postcss.config.mjs +7 -0
  20. package/dev-server/public/glide-variable.woff2 +0 -0
  21. package/dev-server/tsconfig.json +49 -0
  22. package/dist/cli.mjs +299 -26
  23. package/dist/cli.mjs.map +1 -1
  24. package/docs/app/globals.css +455 -0
  25. package/docs/components/api/api-playground.tsx +295 -0
  26. package/docs/components/api/api-reference.tsx +121 -0
  27. package/docs/components/content/collection-index.tsx +114 -0
  28. package/docs/components/docs/contextual-menu.tsx +406 -0
  29. package/docs/components/docs/copy-page-menu.tsx +255 -0
  30. package/docs/components/docs/doc-header.tsx +192 -0
  31. package/docs/components/docs/doc-shell.tsx +289 -0
  32. package/docs/components/docs/doc-sidebar.tsx +206 -0
  33. package/docs/components/docs/doc-toc.tsx +45 -0
  34. package/docs/components/docs/mobile-nav.tsx +207 -0
  35. package/docs/components/mdx/accordion.tsx +83 -0
  36. package/docs/components/mdx/badge.tsx +79 -0
  37. package/docs/components/mdx/callout.tsx +88 -0
  38. package/docs/components/mdx/card.tsx +104 -0
  39. package/docs/components/mdx/code-block.tsx +75 -0
  40. package/docs/components/mdx/code-group.tsx +94 -0
  41. package/docs/components/mdx/color.tsx +87 -0
  42. package/docs/components/mdx/columns.tsx +25 -0
  43. package/docs/components/mdx/expandable.tsx +45 -0
  44. package/docs/components/mdx/field-layout.tsx +77 -0
  45. package/docs/components/mdx/frame.tsx +23 -0
  46. package/docs/components/mdx/get-text-content.ts +18 -0
  47. package/docs/components/mdx/icon.tsx +56 -0
  48. package/docs/components/mdx/index.tsx +96 -0
  49. package/docs/components/mdx/installer.tsx +20 -0
  50. package/docs/components/mdx/panel.tsx +11 -0
  51. package/docs/components/mdx/param-field.tsx +56 -0
  52. package/docs/components/mdx/preview.tsx +36 -0
  53. package/docs/components/mdx/prompt.tsx +63 -0
  54. package/docs/components/mdx/request-example.tsx +27 -0
  55. package/docs/components/mdx/response-field.tsx +42 -0
  56. package/docs/components/mdx/steps.tsx +92 -0
  57. package/docs/components/mdx/tabs.tsx +88 -0
  58. package/docs/components/mdx/tile.tsx +43 -0
  59. package/docs/components/mdx/tooltip.tsx +71 -0
  60. package/docs/components/mdx/tree.tsx +120 -0
  61. package/docs/components/mdx/type-table.tsx +71 -0
  62. package/docs/components/mdx/update.tsx +44 -0
  63. package/docs/components/mdx/video.tsx +12 -0
  64. package/docs/components/mdx/view.tsx +66 -0
  65. package/docs/components/providers.tsx +15 -0
  66. package/docs/components/ui/breadcrumb.tsx +92 -0
  67. package/docs/components/ui/button.tsx +90 -0
  68. package/docs/components/ui/card.tsx +92 -0
  69. package/docs/components/ui/command.tsx +139 -0
  70. package/docs/components/ui/dialog.tsx +97 -0
  71. package/docs/components/ui/field.tsx +237 -0
  72. package/docs/components/ui/input.tsx +105 -0
  73. package/docs/components/ui/label.tsx +22 -0
  74. package/docs/components/ui/popover.tsx +72 -0
  75. package/docs/components/ui/search.tsx +380 -0
  76. package/docs/components/ui/separator.tsx +26 -0
  77. package/docs/components/ui/sheet.tsx +104 -0
  78. package/docs/components/ui/sidebar.tsx +433 -0
  79. package/docs/components/ui/theme-toggle.tsx +62 -0
  80. package/docs/components/ui/tooltip.tsx +53 -0
  81. package/docs/lib/contextual-options.ts +193 -0
  82. package/docs/lib/docs-collection.ts +22 -0
  83. package/docs/lib/mdx.ts +90 -0
  84. package/docs/lib/navigation.ts +288 -0
  85. package/docs/lib/openapi.ts +158 -0
  86. package/docs/lib/routes.ts +10 -0
  87. package/docs/lib/server-cache.ts +83 -0
  88. package/docs/lib/shiki.ts +35 -0
  89. package/docs/lib/theme.ts +29 -0
  90. package/docs/lib/toc.ts +2 -0
  91. package/docs/lib/utils.ts +5 -0
  92. package/package.json +34 -3
  93. package/packages/@repo/common/dist/index.d.ts +9 -0
  94. package/packages/@repo/common/dist/index.d.ts.map +1 -0
  95. package/packages/@repo/common/dist/index.js +42 -0
  96. package/packages/@repo/common/package.json +34 -0
  97. package/packages/@repo/common/src/common.unit.test.ts +55 -0
  98. package/packages/@repo/common/src/index.ts +51 -0
  99. package/packages/@repo/contracts/dist/api-key.d.ts +30 -0
  100. package/packages/@repo/contracts/dist/api-key.d.ts.map +1 -0
  101. package/packages/@repo/contracts/dist/api-key.js +20 -0
  102. package/packages/@repo/contracts/dist/dates.d.ts +4 -0
  103. package/packages/@repo/contracts/dist/dates.d.ts.map +1 -0
  104. package/packages/@repo/contracts/dist/dates.js +2 -0
  105. package/packages/@repo/contracts/dist/deployment.d.ts +71 -0
  106. package/packages/@repo/contracts/dist/deployment.d.ts.map +1 -0
  107. package/packages/@repo/contracts/dist/deployment.js +46 -0
  108. package/packages/@repo/contracts/dist/domain.d.ts +94 -0
  109. package/packages/@repo/contracts/dist/domain.d.ts.map +1 -0
  110. package/packages/@repo/contracts/dist/domain.js +36 -0
  111. package/packages/@repo/contracts/dist/ids.d.ts +14 -0
  112. package/packages/@repo/contracts/dist/ids.d.ts.map +1 -0
  113. package/packages/@repo/contracts/dist/ids.js +10 -0
  114. package/packages/@repo/contracts/dist/index.d.ts +10 -0
  115. package/packages/@repo/contracts/dist/index.d.ts.map +1 -0
  116. package/packages/@repo/contracts/dist/index.js +11 -0
  117. package/packages/@repo/contracts/dist/pagination.d.ts +23 -0
  118. package/packages/@repo/contracts/dist/pagination.d.ts.map +1 -0
  119. package/packages/@repo/contracts/dist/pagination.js +15 -0
  120. package/packages/@repo/contracts/dist/project.d.ts +25 -0
  121. package/packages/@repo/contracts/dist/project.d.ts.map +1 -0
  122. package/packages/@repo/contracts/dist/project.js +23 -0
  123. package/packages/@repo/contracts/dist/tenant.d.ts +99 -0
  124. package/packages/@repo/contracts/dist/tenant.d.ts.map +1 -0
  125. package/packages/@repo/contracts/dist/tenant.js +36 -0
  126. package/packages/@repo/contracts/dist/user.d.ts +9 -0
  127. package/packages/@repo/contracts/dist/user.d.ts.map +1 -0
  128. package/packages/@repo/contracts/dist/user.js +9 -0
  129. package/packages/@repo/contracts/package.json +37 -0
  130. package/packages/@repo/contracts/src/api-key.ts +27 -0
  131. package/packages/@repo/contracts/src/dates.ts +4 -0
  132. package/packages/@repo/contracts/src/deployment.ts +73 -0
  133. package/packages/@repo/contracts/src/domain.ts +51 -0
  134. package/packages/@repo/contracts/src/ids.ts +22 -0
  135. package/packages/@repo/contracts/src/index.ts +11 -0
  136. package/packages/@repo/contracts/src/pagination.ts +21 -0
  137. package/packages/@repo/contracts/src/project.ts +30 -0
  138. package/packages/@repo/contracts/src/tenant.ts +54 -0
  139. package/packages/@repo/contracts/src/user.ts +12 -0
  140. package/packages/@repo/models/dist/docs-config.d.ts +985 -0
  141. package/packages/@repo/models/dist/docs-config.d.ts.map +1 -0
  142. package/packages/@repo/models/dist/docs-config.js +548 -0
  143. package/packages/@repo/models/dist/index.d.ts +3 -0
  144. package/packages/@repo/models/dist/index.d.ts.map +1 -0
  145. package/packages/@repo/models/dist/index.js +3 -0
  146. package/packages/@repo/models/dist/tenant.d.ts +25 -0
  147. package/packages/@repo/models/dist/tenant.d.ts.map +1 -0
  148. package/packages/@repo/models/dist/tenant.js +1 -0
  149. package/packages/@repo/models/package.json +37 -0
  150. package/packages/@repo/models/src/docs-config.ts +648 -0
  151. package/packages/@repo/models/src/index.ts +3 -0
  152. package/packages/@repo/models/src/tenant.ts +29 -0
  153. package/packages/@repo/prebuild/dist/index.d.ts +2 -0
  154. package/packages/@repo/prebuild/dist/index.d.ts.map +1 -0
  155. package/packages/@repo/prebuild/dist/index.js +2 -0
  156. package/packages/@repo/prebuild/dist/openapi.d.ts +43 -0
  157. package/packages/@repo/prebuild/dist/openapi.d.ts.map +1 -0
  158. package/packages/@repo/prebuild/dist/openapi.js +58 -0
  159. package/packages/@repo/prebuild/package.json +39 -0
  160. package/packages/@repo/prebuild/src/index.ts +2 -0
  161. package/packages/@repo/prebuild/src/openapi.ts +116 -0
  162. package/packages/@repo/previewing/dist/blob-source.d.ts +16 -0
  163. package/packages/@repo/previewing/dist/blob-source.d.ts.map +1 -0
  164. package/packages/@repo/previewing/dist/blob-source.js +110 -0
  165. package/packages/@repo/previewing/dist/content-source.d.ts +12 -0
  166. package/packages/@repo/previewing/dist/content-source.d.ts.map +1 -0
  167. package/packages/@repo/previewing/dist/content-source.js +1 -0
  168. package/packages/@repo/previewing/dist/fs-source.d.ts +11 -0
  169. package/packages/@repo/previewing/dist/fs-source.d.ts.map +1 -0
  170. package/packages/@repo/previewing/dist/fs-source.js +79 -0
  171. package/packages/@repo/previewing/dist/index.d.ts +120 -0
  172. package/packages/@repo/previewing/dist/index.d.ts.map +1 -0
  173. package/packages/@repo/previewing/dist/index.js +984 -0
  174. package/packages/@repo/previewing/package.json +41 -0
  175. package/packages/@repo/previewing/src/blob-source.ts +167 -0
  176. package/packages/@repo/previewing/src/content-source.ts +12 -0
  177. package/packages/@repo/previewing/src/fs-source.ts +111 -0
  178. package/packages/@repo/previewing/src/index.ts +1490 -0
  179. package/packages/@repo/previewing/src/index.unit.test.ts +290 -0
  180. package/packages/@repo/validation/dist/index.d.ts +12 -0
  181. package/packages/@repo/validation/dist/index.d.ts.map +1 -0
  182. package/packages/@repo/validation/dist/index.js +30 -0
  183. package/packages/@repo/validation/package.json +37 -0
  184. package/packages/@repo/validation/src/index.ts +59 -0
  185. package/packages/@repo/validation/src/mintlify-docs-schema.json +5016 -0
@@ -0,0 +1,49 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "declaration": true,
5
+ "declarationMap": true,
6
+ "esModuleInterop": true,
7
+ "incremental": false,
8
+ "isolatedModules": true,
9
+ "jsx": "preserve",
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
+ ]
49
+ }
package/dist/cli.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { spawnSync } from "node:child_process";
2
+ import { createRequire } from "node:module";
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";
5
6
  import { confirm, intro, isCancel, log, password, spinner } from "@clack/prompts";
@@ -7,6 +8,11 @@ import chalk from "chalk";
7
8
  import { Command } from "commander";
8
9
  import open from "open";
9
10
  import { homedir } from "node:os";
11
+ import { once } from "node:events";
12
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
13
+ import { fileURLToPath } from "node:url";
14
+ import { createFsSource, loadSiteConfig } from "@repo/previewing";
15
+ import { watch } from "chokidar";
10
16
  import { createServer } from "node:http";
11
17
  import { createHash, randomBytes } from "node:crypto";
12
18
  //#region src/constants.ts
@@ -283,6 +289,257 @@ const resolveTokenStatus = (token) => {
283
289
  };
284
290
  };
285
291
  //#endregion
292
+ //#region src/dev/resolve-root.ts
293
+ const CONFIG_FILE$1 = "docs.json";
294
+ const fileExists$2 = async (filePath) => {
295
+ try {
296
+ await fs.access(filePath);
297
+ return true;
298
+ } catch {
299
+ return false;
300
+ }
301
+ };
302
+ const resolveDocsRoot$1 = async (dir) => {
303
+ if (dir) return path.resolve(process.cwd(), dir);
304
+ const candidates = [
305
+ process.cwd(),
306
+ path.join(process.cwd(), "docs"),
307
+ path.join(process.cwd(), "apps/docs")
308
+ ];
309
+ for (const candidate of candidates) if (await fileExists$2(path.join(candidate, CONFIG_FILE$1))) return candidate;
310
+ return process.cwd();
311
+ };
312
+ const validateDocsRoot = async (root) => {
313
+ const result = await loadSiteConfig(createFsSource(root));
314
+ if (!result.ok) throw new CliError(result.errors.join("\n"), EXIT_CODES.VALIDATION, `Make sure ${CONFIG_FILE$1} exists and is valid JSON.`);
315
+ return result;
316
+ };
317
+ //#endregion
318
+ //#region src/dev/watcher.ts
319
+ const INVALIDATE_ENDPOINT = "/blodemd-dev/invalidate";
320
+ const WATCH_DEBOUNCE_MS = 100;
321
+ const normalizeRelativePath$1 = (root, filePath) => path.relative(root, filePath).split(path.sep).join("/");
322
+ const isDirectoryEvent = (event) => event === "addDir" || event === "unlinkDir";
323
+ const createDevWatcher = ({ port, root }) => {
324
+ const watcher = watch(root, {
325
+ ignoreInitial: true,
326
+ ignored: [
327
+ "**/.git/**",
328
+ "**/.next/**",
329
+ "**/dist/**",
330
+ "**/node_modules/**"
331
+ ]
332
+ });
333
+ let flushTimer = null;
334
+ let pendingKind = "content";
335
+ const pendingPaths = /* @__PURE__ */ new Set();
336
+ const flush = async () => {
337
+ flushTimer = null;
338
+ const paths = [...pendingPaths];
339
+ const kind = pendingKind;
340
+ pendingPaths.clear();
341
+ pendingKind = "content";
342
+ if (!paths.length) return;
343
+ try {
344
+ const response = await fetch(`http://127.0.0.1:${port}${INVALIDATE_ENDPOINT}`, {
345
+ body: JSON.stringify({
346
+ kind,
347
+ paths
348
+ }),
349
+ headers: { "Content-Type": "application/json" },
350
+ method: "POST"
351
+ });
352
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
353
+ } catch (error) {
354
+ log.error(`Failed to invalidate preview cache: ${error instanceof Error ? error.message : "unknown error"}`);
355
+ }
356
+ };
357
+ watcher.on("all", (event, changedPath) => {
358
+ if (isDirectoryEvent(event)) return;
359
+ const relativePath = normalizeRelativePath$1(root, changedPath);
360
+ pendingPaths.add(relativePath);
361
+ if (path.basename(changedPath) === "docs.json") pendingKind = "config";
362
+ if (flushTimer) clearTimeout(flushTimer);
363
+ flushTimer = setTimeout(() => {
364
+ flush();
365
+ }, WATCH_DEBOUNCE_MS);
366
+ });
367
+ return { async close() {
368
+ if (flushTimer) {
369
+ clearTimeout(flushTimer);
370
+ await flush();
371
+ }
372
+ await watcher.close();
373
+ } };
374
+ };
375
+ //#endregion
376
+ //#region src/dev/command.ts
377
+ const DEV_READY_ENDPOINT = "/blodemd-dev/version";
378
+ const DEV_READY_TIMEOUT_MS = 45e3;
379
+ const parsePositiveInteger$1 = (value, label) => {
380
+ const parsed = Number.parseInt(value, 10);
381
+ if (!Number.isInteger(parsed) || parsed <= 0) throw new CliError(`${label} must be a positive integer.`, EXIT_CODES.VALIDATION);
382
+ return parsed;
383
+ };
384
+ const fileExists$1 = async (filePath) => {
385
+ try {
386
+ await fs.access(filePath);
387
+ return true;
388
+ } catch {
389
+ return false;
390
+ }
391
+ };
392
+ /**
393
+ * Derive the CLI npm package root from the running script path.
394
+ * The CLI entry point is at `<pkg-root>/dist/cli.mjs`.
395
+ */
396
+ const resolveCliPackageRoot = (cliFilePath) => path.dirname(path.dirname(cliFilePath));
397
+ /**
398
+ * Check if a shipped dev-server exists alongside the CLI (npm-installed mode).
399
+ * Verifies both the dev-server directory AND that `next` is resolvable
400
+ * (it's a dependency when npm-installed, but not in the monorepo).
401
+ */
402
+ const findStandaloneDevServer = async (cliPackageRoot) => {
403
+ const devServerDir = path.join(cliPackageRoot, "dev-server");
404
+ if (!await fileExists$1(path.join(devServerDir, "next.config.js"))) return null;
405
+ try {
406
+ createRequire(path.join(cliPackageRoot, "package.json")).resolve("next/package.json");
407
+ } catch {
408
+ return null;
409
+ }
410
+ return {
411
+ devServerDir,
412
+ mode: "standalone",
413
+ packagesDir: path.join(cliPackageRoot, "packages")
414
+ };
415
+ };
416
+ /**
417
+ * Resolve the `next` CLI binary from the blodemd package's own dependencies.
418
+ */
419
+ const resolveNextBin = (cliPackageRoot) => {
420
+ const nextPkgPath = createRequire(path.join(cliPackageRoot, "package.json")).resolve("next/package.json");
421
+ return path.join(path.dirname(nextPkgPath), "dist", "bin", "next");
422
+ };
423
+ const findMonorepoRoot = async (start) => {
424
+ let current = start;
425
+ while (true) {
426
+ const packageJsonPath = path.join(current, "package.json");
427
+ if (await fileExists$1(packageJsonPath)) {
428
+ const raw = await fs.readFile(packageJsonPath, "utf8");
429
+ const workspaces = JSON.parse(raw).workspaces ?? [];
430
+ if (workspaces.includes("apps/*") && workspaces.includes("packages/*")) return current;
431
+ }
432
+ const parent = path.dirname(current);
433
+ if (parent === current) break;
434
+ current = parent;
435
+ }
436
+ throw new CliError("Could not locate the blodemd dev server.", EXIT_CODES.ERROR, "Make sure blodemd is installed correctly (npm i blodemd).");
437
+ };
438
+ const resolveDevServer = async (cliFilePath) => {
439
+ const standalone = await findStandaloneDevServer(resolveCliPackageRoot(cliFilePath));
440
+ if (standalone) return standalone;
441
+ return {
442
+ mode: "monorepo",
443
+ repoRoot: await findMonorepoRoot(path.dirname(cliFilePath))
444
+ };
445
+ };
446
+ const spawnDevServer = (server, { root, port }) => {
447
+ if (server.mode === "standalone") {
448
+ const nextBin = resolveNextBin(path.dirname(server.devServerDir));
449
+ return spawn(process.execPath, [nextBin, "dev"], {
450
+ cwd: server.devServerDir,
451
+ env: {
452
+ ...process.env,
453
+ BLODEMD_PACKAGES_DIR: server.packagesDir,
454
+ DOCS_ROOT: root,
455
+ NODE_PATH: [server.packagesDir, process.env.NODE_PATH].filter(Boolean).join(path.delimiter),
456
+ PORT: String(port)
457
+ },
458
+ stdio: "inherit"
459
+ });
460
+ }
461
+ return spawn(process.platform === "win32" ? "npm.cmd" : "npm", [
462
+ "run",
463
+ "dev",
464
+ "--workspace=dev-server"
465
+ ], {
466
+ cwd: server.repoRoot,
467
+ env: {
468
+ ...process.env,
469
+ DOCS_ROOT: root,
470
+ PORT: String(port)
471
+ },
472
+ stdio: "inherit"
473
+ });
474
+ };
475
+ const waitForServer = async ({ child, port }) => {
476
+ const url = `http://localhost:${port}${DEV_READY_ENDPOINT}`;
477
+ const startedAt = Date.now();
478
+ while (Date.now() - startedAt < DEV_READY_TIMEOUT_MS) {
479
+ if (child.exitCode !== null) throw new CliError("The local dev server exited before it became ready.", EXIT_CODES.ERROR);
480
+ try {
481
+ if ((await fetch(url, {
482
+ cache: "no-store",
483
+ headers: { accept: "application/json" }
484
+ })).ok) return;
485
+ } catch {}
486
+ await setTimeout$1(500);
487
+ }
488
+ throw new CliError("Timed out waiting for the local dev server to start.", EXIT_CODES.ERROR);
489
+ };
490
+ const devCommand = async ({ dir, openBrowser, port: portValue }) => {
491
+ intro(chalk.bold("blodemd dev"));
492
+ try {
493
+ const port = parsePositiveInteger$1(portValue, "Port");
494
+ const root = await resolveDocsRoot$1(dir);
495
+ await validateDocsRoot(root);
496
+ const server = await resolveDevServer(fileURLToPath(import.meta.url));
497
+ const localUrl = `http://localhost:${port}`;
498
+ log.info(`Docs root: ${chalk.cyan(root)}`);
499
+ const child = spawnDevServer(server, {
500
+ port,
501
+ root
502
+ });
503
+ let watcher = null;
504
+ let shuttingDown = false;
505
+ const closeAll = async () => {
506
+ if (shuttingDown) return;
507
+ shuttingDown = true;
508
+ if (watcher) {
509
+ await watcher.close();
510
+ watcher = null;
511
+ }
512
+ if (child.exitCode === null && !child.killed) child.kill("SIGTERM");
513
+ };
514
+ process.once("SIGINT", closeAll);
515
+ process.once("SIGTERM", closeAll);
516
+ try {
517
+ await waitForServer({
518
+ child,
519
+ port
520
+ });
521
+ watcher = await createDevWatcher({
522
+ port,
523
+ root
524
+ });
525
+ log.success(`Dev server running at ${chalk.cyan(localUrl)}`);
526
+ if (openBrowser) await open(localUrl);
527
+ const [code, signal] = await once(child, "exit");
528
+ if (shuttingDown || signal === "SIGINT" || signal === "SIGTERM") return;
529
+ if (code !== 0) throw new CliError(`The local dev server exited with code ${code ?? "unknown"}.`, EXIT_CODES.ERROR);
530
+ } finally {
531
+ await closeAll();
532
+ process.removeListener("SIGINT", closeAll);
533
+ process.removeListener("SIGTERM", closeAll);
534
+ }
535
+ } catch (error) {
536
+ const cliError = toCliError(error);
537
+ log.error(cliError.message);
538
+ if (cliError.hint) log.info(cliError.hint);
539
+ process.exitCode = cliError.exitCode;
540
+ }
541
+ };
542
+ //#endregion
286
543
  //#region src/oauth-callback.ts
287
544
  const SUCCESS_HTML = "<!doctype html><html><head><meta charset=\"utf-8\"/><title>Blode.md CLI</title></head><body><h2>Logged in! You can close this tab.</h2></body></html>";
288
545
  const escapeHtml = (text) => text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
@@ -294,6 +551,7 @@ const waitForOAuthCode = (options) => {
294
551
  if (!Number.isInteger(port) || port <= 0) return Promise.reject(new CliError("OAuth redirect URL requires an explicit port", EXIT_CODES.ERROR));
295
552
  return new Promise((resolve, reject) => {
296
553
  let settled = false;
554
+ const sockets = /* @__PURE__ */ new Set();
297
555
  const settle = (ok, value) => {
298
556
  if (settled) return;
299
557
  settled = true;
@@ -302,6 +560,7 @@ const waitForOAuthCode = (options) => {
302
560
  if (ok) resolve(value);
303
561
  else reject(value);
304
562
  });
563
+ for (const socket of sockets) socket.destroy();
305
564
  };
306
565
  const httpServer = createServer((request, response) => {
307
566
  if (!request.url) {
@@ -341,6 +600,10 @@ const waitForOAuthCode = (options) => {
341
600
  response.end(SUCCESS_HTML);
342
601
  settle(true, code);
343
602
  });
603
+ httpServer.on("connection", (socket) => {
604
+ sockets.add(socket);
605
+ socket.once("close", () => sockets.delete(socket));
606
+ });
344
607
  httpServer.on("error", (error) => {
345
608
  settle(false, new CliError(`Failed to start callback server on ${host}:${port}: ${error.message}`, EXIT_CODES.ERROR));
346
609
  });
@@ -506,21 +769,39 @@ const autoCreateProject = async (project, apiUrl, headers) => {
506
769
  log.info(`API key for CI: ${chalk.dim(createResult.token)}`);
507
770
  return true;
508
771
  };
772
+ const MAX_BATCH_BYTES = 4 * 1024 * 1024;
509
773
  const uploadFiles = async (files, root, apiPath, deploymentId, headers, s) => {
510
774
  s.start(`Uploading ${files.length} files`);
511
- for (const [index, filePath] of files.entries()) {
512
- const relativePath = normalizeRelativePath(root, filePath);
513
- const content = await fs.readFile(filePath);
514
- await requestJson(apiPath(`/${deploymentId}/files`), {
515
- body: JSON.stringify({
516
- contentBase64: content.toString("base64"),
517
- contentType: getContentType(filePath),
518
- path: relativePath
519
- }),
775
+ const items = await Promise.all(files.map(async (filePath) => {
776
+ return {
777
+ contentBase64: (await fs.readFile(filePath)).toString("base64"),
778
+ contentType: getContentType(filePath),
779
+ path: normalizeRelativePath(root, filePath)
780
+ };
781
+ }));
782
+ const batches = [];
783
+ let current = [];
784
+ let currentBytes = 0;
785
+ for (const item of items) {
786
+ const itemBytes = item.contentBase64.length + item.path.length + 64;
787
+ if (current.length > 0 && currentBytes + itemBytes > MAX_BATCH_BYTES) {
788
+ batches.push(current);
789
+ current = [];
790
+ currentBytes = 0;
791
+ }
792
+ current.push(item);
793
+ currentBytes += itemBytes;
794
+ }
795
+ if (current.length > 0) batches.push(current);
796
+ let uploaded = 0;
797
+ for (const batch of batches) {
798
+ await requestJson(apiPath(`/${deploymentId}/files/batch`), {
799
+ body: JSON.stringify({ files: batch }),
520
800
  headers,
521
801
  method: "POST"
522
- }, `Failed to upload ${relativePath}`);
523
- s.message(`Uploading files (${index + 1}/${files.length})`);
802
+ }, "Failed to upload files");
803
+ uploaded += batch.length;
804
+ s.message(`Uploading files (${uploaded}/${files.length})`);
524
805
  }
525
806
  s.stop(`Uploaded ${chalk.cyan(String(files.length))} files`);
526
807
  };
@@ -624,14 +905,7 @@ program.command("whoami").description("Show current authentication").action(asyn
624
905
  const email = resolved.user?.email ?? await fetchUserEmail(process.env["BLODEMD_API_URL"] ?? "https://api.blode.md", resolved.token);
625
906
  if (email) log.info(`Logged in as ${chalk.cyan(email)}`);
626
907
  else log.info("Logged in (could not fetch user details).");
627
- if (resolved.expiresAt) {
628
- if (status.expired) log.warn("Session has expired. Run \"blodemd login\" to re-authenticate.");
629
- else if (status.expiresInSeconds !== null) {
630
- const hours = Math.floor(status.expiresInSeconds / 3600);
631
- const minutes = Math.floor(status.expiresInSeconds % 3600 / 60);
632
- log.info(`Session expires in ${hours}h ${minutes}m`);
633
- }
634
- }
908
+ if (resolved.expiresAt && status.expired) log.warn("Session has expired. Run \"blodemd login\" to re-authenticate.");
635
909
  } catch (error) {
636
910
  reportCommandError("Whoami failed", error);
637
911
  }
@@ -731,12 +1005,11 @@ program.command("push").description("Deploy docs").argument("[dir]", "docs direc
731
1005
  reportCommandError("Push failed", error);
732
1006
  }
733
1007
  });
734
- program.command("dev").description("Start the docs dev server").action(() => {
735
- intro(chalk.bold("blodemd dev"));
736
- log.info(`Run ${chalk.cyan("npm run dev --filter=docs")} from the repo root.`);
737
- log.info(`Then open ${chalk.cyan("http://localhost:3001")} to view the docs site.`);
738
- log.info("Done");
739
- });
1008
+ program.command("dev").description("Start the local docs dev server").option("-p, --port <port>", "Port number", "3030").option("-d, --dir <dir>", "Docs directory").option("--no-open", "Don't open browser").action(async (options) => await devCommand({
1009
+ dir: options.dir,
1010
+ openBrowser: options.open ?? true,
1011
+ port: options.port
1012
+ }));
740
1013
  program.parse();
741
1014
  //#endregion
742
1015
  export {};