blodemd 0.0.6 → 0.0.8
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 +2 -2
- package/dev-server/app/favicon.ico +0 -0
- package/dev-server/next-env.d.ts +5 -0
- package/dev-server/package.json +1 -1
- package/dev-server/tsconfig.json +3 -2
- package/dist/cli.mjs +209 -53
- package/dist/cli.mjs.map +1 -1
- package/docs/app/globals.css +4 -2
- package/docs/components/docs/doc-header.tsx +35 -17
- package/docs/components/docs/doc-shell.tsx +46 -22
- package/docs/components/docs/doc-sidebar.tsx +13 -8
- package/docs/components/docs/mobile-nav.tsx +150 -152
- package/docs/components/icons/doc-icon.tsx +96 -0
- package/docs/components/mdx/card.tsx +60 -54
- package/docs/components/mdx/icon.tsx +2 -46
- package/docs/components/mdx/index.tsx +12 -1
- package/docs/components/mdx/tree.tsx +7 -7
- package/docs/components/ui/search.tsx +11 -7
- package/docs/lib/mdx.ts +2 -5
- package/docs/lib/navigation.ts +2 -2
- package/docs/lib/routes.ts +34 -0
- package/docs/lib/shiki.ts +6 -1
- package/package.json +13 -5
- package/packages/@repo/contracts/dist/tenant.d.ts +12 -0
- package/packages/@repo/contracts/dist/tenant.d.ts.map +1 -1
- package/packages/@repo/contracts/dist/tenant.js +20 -0
- package/packages/@repo/contracts/src/tenant.ts +38 -0
- package/packages/@repo/previewing/dist/fs-source.d.ts.map +1 -1
- package/packages/@repo/previewing/dist/fs-source.js +1 -8
- package/packages/@repo/previewing/src/fs-source.ts +1 -8
- package/packages/@repo/validation/src/mintlify-docs-schema.json +1 -1
- package/scripts/prepare-package.mjs +39 -0
- package/packages/@repo/common/src/common.unit.test.ts +0 -55
- package/packages/@repo/previewing/src/index.unit.test.ts +0 -290
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Or run without installing:
|
|
|
23
23
|
npx blodemd
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Requires Node.js
|
|
26
|
+
Requires Node.js 20.17+ and <25.
|
|
27
27
|
|
|
28
28
|
## Quick Start
|
|
29
29
|
|
|
@@ -90,7 +90,7 @@ The CLI looks for a `docs.json` file in the docs directory. Minimal example:
|
|
|
90
90
|
|
|
91
91
|
```json
|
|
92
92
|
{
|
|
93
|
-
"$schema": "https://
|
|
93
|
+
"$schema": "https://docs.blode.md/docs.json",
|
|
94
94
|
"name": "my-project",
|
|
95
95
|
"theme": "mint",
|
|
96
96
|
"colors": { "primary": "#0D9373" },
|
|
Binary file
|
package/dev-server/package.json
CHANGED
package/dev-server/tsconfig.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"esModuleInterop": true,
|
|
7
7
|
"incremental": false,
|
|
8
8
|
"isolatedModules": true,
|
|
9
|
-
"jsx": "
|
|
9
|
+
"jsx": "react-jsx",
|
|
10
10
|
"lib": [
|
|
11
11
|
"es2022",
|
|
12
12
|
"DOM",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"**/*.tsx",
|
|
45
45
|
"next-env.d.ts",
|
|
46
46
|
"next.config.js",
|
|
47
|
-
".next/types/**/*.ts"
|
|
47
|
+
".next/types/**/*.ts",
|
|
48
|
+
".next/dev/types/**/*.ts"
|
|
48
49
|
]
|
|
49
50
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -9,12 +9,14 @@ import { Command } from "commander";
|
|
|
9
9
|
import open from "open";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { once } from "node:events";
|
|
12
|
+
import { createServer } from "node:net";
|
|
12
13
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
13
14
|
import { fileURLToPath } from "node:url";
|
|
14
15
|
import { createFsSource, loadSiteConfig } from "@repo/previewing";
|
|
15
16
|
import { watch } from "chokidar";
|
|
16
|
-
import { createServer } from "node:http";
|
|
17
|
+
import { createServer as createServer$1 } from "node:http";
|
|
17
18
|
import { createHash, randomBytes } from "node:crypto";
|
|
19
|
+
import { readFileSync } from "node:fs";
|
|
18
20
|
//#region src/constants.ts
|
|
19
21
|
const CLI_NAME = "blodemd";
|
|
20
22
|
const OAUTH_CLIENT_ID = "6b5f9860-fe96-4a83-b1ad-266260523c91";
|
|
@@ -289,9 +291,20 @@ const resolveTokenStatus = (token) => {
|
|
|
289
291
|
};
|
|
290
292
|
};
|
|
291
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
|
|
292
305
|
//#region src/dev/resolve-root.ts
|
|
293
306
|
const CONFIG_FILE$1 = "docs.json";
|
|
294
|
-
const fileExists$
|
|
307
|
+
const fileExists$1 = async (filePath) => {
|
|
295
308
|
try {
|
|
296
309
|
await fs.access(filePath);
|
|
297
310
|
return true;
|
|
@@ -299,21 +312,17 @@ const fileExists$2 = async (filePath) => {
|
|
|
299
312
|
return false;
|
|
300
313
|
}
|
|
301
314
|
};
|
|
302
|
-
const resolveDocsRoot
|
|
315
|
+
const resolveDocsRoot = async (dir) => {
|
|
303
316
|
if (dir) return path.resolve(process.cwd(), dir);
|
|
304
317
|
const candidates = [
|
|
305
318
|
process.cwd(),
|
|
306
319
|
path.join(process.cwd(), "docs"),
|
|
307
320
|
path.join(process.cwd(), "apps/docs")
|
|
308
321
|
];
|
|
309
|
-
for (const candidate of candidates) if (await fileExists$
|
|
322
|
+
for (const candidate of candidates) if (await fileExists$1(path.join(candidate, CONFIG_FILE$1))) return candidate;
|
|
310
323
|
return process.cwd();
|
|
311
324
|
};
|
|
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
|
-
};
|
|
325
|
+
const validateDocsRoot = async (root) => await loadValidatedSiteConfig(root);
|
|
317
326
|
//#endregion
|
|
318
327
|
//#region src/dev/watcher.ts
|
|
319
328
|
const INVALIDATE_ENDPOINT = "/blodemd-dev/invalidate";
|
|
@@ -376,12 +385,20 @@ const createDevWatcher = ({ port, root }) => {
|
|
|
376
385
|
//#region src/dev/command.ts
|
|
377
386
|
const DEV_READY_ENDPOINT = "/blodemd-dev/version";
|
|
378
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
|
+
]);
|
|
379
396
|
const parsePositiveInteger$1 = (value, label) => {
|
|
380
397
|
const parsed = Number.parseInt(value, 10);
|
|
381
398
|
if (!Number.isInteger(parsed) || parsed <= 0) throw new CliError(`${label} must be a positive integer.`, EXIT_CODES.VALIDATION);
|
|
382
399
|
return parsed;
|
|
383
400
|
};
|
|
384
|
-
const fileExists
|
|
401
|
+
const fileExists = async (filePath) => {
|
|
385
402
|
try {
|
|
386
403
|
await fs.access(filePath);
|
|
387
404
|
return true;
|
|
@@ -389,11 +406,115 @@ const fileExists$1 = async (filePath) => {
|
|
|
389
406
|
return false;
|
|
390
407
|
}
|
|
391
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
|
+
};
|
|
392
461
|
/**
|
|
393
462
|
* Derive the CLI npm package root from the running script path.
|
|
394
463
|
* The CLI entry point is at `<pkg-root>/dist/cli.mjs`.
|
|
395
464
|
*/
|
|
396
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
|
+
};
|
|
397
518
|
/**
|
|
398
519
|
* Check if a shipped dev-server exists alongside the CLI (npm-installed mode).
|
|
399
520
|
* Verifies both the dev-server directory AND that `next` is resolvable
|
|
@@ -401,16 +522,19 @@ const resolveCliPackageRoot = (cliFilePath) => path.dirname(path.dirname(cliFile
|
|
|
401
522
|
*/
|
|
402
523
|
const findStandaloneDevServer = async (cliPackageRoot) => {
|
|
403
524
|
const devServerDir = path.join(cliPackageRoot, "dev-server");
|
|
404
|
-
if (!await fileExists
|
|
525
|
+
if (!await fileExists(path.join(devServerDir, "next.config.js"))) return null;
|
|
526
|
+
if (!await isStandaloneCliInstall(cliPackageRoot)) return null;
|
|
405
527
|
try {
|
|
406
528
|
createRequire(path.join(cliPackageRoot, "package.json")).resolve("next/package.json");
|
|
407
529
|
} catch {
|
|
408
530
|
return null;
|
|
409
531
|
}
|
|
532
|
+
const runtime = await materializeStandaloneRuntime(cliPackageRoot);
|
|
410
533
|
return {
|
|
411
|
-
devServerDir,
|
|
534
|
+
devServerDir: runtime.devServerDir,
|
|
412
535
|
mode: "standalone",
|
|
413
|
-
|
|
536
|
+
nextPackageRoot: cliPackageRoot,
|
|
537
|
+
packagesDir: runtime.packagesDir
|
|
414
538
|
};
|
|
415
539
|
};
|
|
416
540
|
/**
|
|
@@ -424,7 +548,7 @@ const findMonorepoRoot = async (start) => {
|
|
|
424
548
|
let current = start;
|
|
425
549
|
while (true) {
|
|
426
550
|
const packageJsonPath = path.join(current, "package.json");
|
|
427
|
-
if (await fileExists
|
|
551
|
+
if (await fileExists(packageJsonPath)) {
|
|
428
552
|
const raw = await fs.readFile(packageJsonPath, "utf8");
|
|
429
553
|
const workspaces = JSON.parse(raw).workspaces ?? [];
|
|
430
554
|
if (workspaces.includes("apps/*") && workspaces.includes("packages/*")) return current;
|
|
@@ -445,8 +569,12 @@ const resolveDevServer = async (cliFilePath) => {
|
|
|
445
569
|
};
|
|
446
570
|
const spawnDevServer = (server, { root, port }) => {
|
|
447
571
|
if (server.mode === "standalone") {
|
|
448
|
-
const nextBin = resolveNextBin(
|
|
449
|
-
return spawn(process.execPath, [
|
|
572
|
+
const nextBin = resolveNextBin(server.nextPackageRoot);
|
|
573
|
+
return spawn(process.execPath, [
|
|
574
|
+
nextBin,
|
|
575
|
+
"dev",
|
|
576
|
+
"--webpack"
|
|
577
|
+
], {
|
|
450
578
|
cwd: server.devServerDir,
|
|
451
579
|
env: {
|
|
452
580
|
...process.env,
|
|
@@ -490,14 +618,14 @@ const waitForServer = async ({ child, port }) => {
|
|
|
490
618
|
const devCommand = async ({ dir, openBrowser, port: portValue }) => {
|
|
491
619
|
intro(chalk.bold("blodemd dev"));
|
|
492
620
|
try {
|
|
493
|
-
const
|
|
494
|
-
const root = await resolveDocsRoot
|
|
621
|
+
const resolvedPort = await resolveDevPort(parsePositiveInteger$1(portValue, "Port"));
|
|
622
|
+
const root = await resolveDocsRoot(dir);
|
|
495
623
|
await validateDocsRoot(root);
|
|
496
624
|
const server = await resolveDevServer(fileURLToPath(import.meta.url));
|
|
497
|
-
const localUrl = `http://localhost:${
|
|
625
|
+
const localUrl = `http://localhost:${resolvedPort}`;
|
|
498
626
|
log.info(`Docs root: ${chalk.cyan(root)}`);
|
|
499
627
|
const child = spawnDevServer(server, {
|
|
500
|
-
port,
|
|
628
|
+
port: resolvedPort,
|
|
501
629
|
root
|
|
502
630
|
});
|
|
503
631
|
let watcher = null;
|
|
@@ -509,17 +637,17 @@ const devCommand = async ({ dir, openBrowser, port: portValue }) => {
|
|
|
509
637
|
await watcher.close();
|
|
510
638
|
watcher = null;
|
|
511
639
|
}
|
|
512
|
-
|
|
640
|
+
await shutdownChildProcess(child);
|
|
513
641
|
};
|
|
514
642
|
process.once("SIGINT", closeAll);
|
|
515
643
|
process.once("SIGTERM", closeAll);
|
|
516
644
|
try {
|
|
517
645
|
await waitForServer({
|
|
518
646
|
child,
|
|
519
|
-
port
|
|
647
|
+
port: resolvedPort
|
|
520
648
|
});
|
|
521
649
|
watcher = await createDevWatcher({
|
|
522
|
-
port,
|
|
650
|
+
port: resolvedPort,
|
|
523
651
|
root
|
|
524
652
|
});
|
|
525
653
|
log.success(`Dev server running at ${chalk.cyan(localUrl)}`);
|
|
@@ -562,7 +690,7 @@ const waitForOAuthCode = (options) => {
|
|
|
562
690
|
});
|
|
563
691
|
for (const socket of sockets) socket.destroy();
|
|
564
692
|
};
|
|
565
|
-
const httpServer = createServer((request, response) => {
|
|
693
|
+
const httpServer = createServer$1((request, response) => {
|
|
566
694
|
if (!request.url) {
|
|
567
695
|
response.writeHead(400, { "content-type": "text/html; charset=utf-8" });
|
|
568
696
|
response.end(errorHtml("Missing request URL"));
|
|
@@ -619,6 +747,53 @@ const createOAuthState = () => randomBytes(24).toString("hex");
|
|
|
619
747
|
const createCodeVerifier = () => randomBytes(64).toString("base64url");
|
|
620
748
|
const createCodeChallenge = (verifier) => createHash("sha256").update(verifier).digest().toString("base64url");
|
|
621
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
|
|
622
797
|
//#region src/cli.ts
|
|
623
798
|
const CONFIG_FILE = "docs.json";
|
|
624
799
|
const TEXT_CONTENT_TYPES = {
|
|
@@ -638,31 +813,6 @@ const ensureFile = async (filePath, content) => {
|
|
|
638
813
|
await fs.writeFile(filePath, content, { flag: "wx" });
|
|
639
814
|
} catch {}
|
|
640
815
|
};
|
|
641
|
-
const fileExists = async (filePath) => {
|
|
642
|
-
try {
|
|
643
|
-
await fs.access(filePath);
|
|
644
|
-
return true;
|
|
645
|
-
} catch {
|
|
646
|
-
return false;
|
|
647
|
-
}
|
|
648
|
-
};
|
|
649
|
-
const readConfig = async (root) => {
|
|
650
|
-
const raw = await fs.readFile(path.join(root, CONFIG_FILE), "utf8");
|
|
651
|
-
return {
|
|
652
|
-
name: JSON.parse(raw).name,
|
|
653
|
-
raw
|
|
654
|
-
};
|
|
655
|
-
};
|
|
656
|
-
const resolveDocsRoot = async (dir) => {
|
|
657
|
-
if (dir) return path.resolve(process.cwd(), dir);
|
|
658
|
-
const candidates = [
|
|
659
|
-
process.cwd(),
|
|
660
|
-
path.join(process.cwd(), "docs"),
|
|
661
|
-
path.join(process.cwd(), "apps/docs")
|
|
662
|
-
];
|
|
663
|
-
for (const candidate of candidates) if (await fileExists(path.join(candidate, CONFIG_FILE))) return candidate;
|
|
664
|
-
return process.cwd();
|
|
665
|
-
};
|
|
666
816
|
const readGitValue = (gitArgs) => {
|
|
667
817
|
const result = spawnSync("git", gitArgs, {
|
|
668
818
|
encoding: "utf8",
|
|
@@ -806,7 +956,11 @@ const uploadFiles = async (files, root, apiPath, deploymentId, headers, s) => {
|
|
|
806
956
|
s.stop(`Uploaded ${chalk.cyan(String(files.length))} files`);
|
|
807
957
|
};
|
|
808
958
|
const program = new Command();
|
|
809
|
-
|
|
959
|
+
const cliVersion = readCliVersion(import.meta.url);
|
|
960
|
+
program.name("blodemd").description("Blode.md CLI").version(cliVersion);
|
|
961
|
+
program.hook("preAction", () => {
|
|
962
|
+
assertSupportedNodeVersion();
|
|
963
|
+
});
|
|
810
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) => {
|
|
811
965
|
intro(chalk.bold("blodemd login"));
|
|
812
966
|
try {
|
|
@@ -916,7 +1070,7 @@ program.command("init").description("Scaffold a docs folder").argument("[dir]",
|
|
|
916
1070
|
const root = path.resolve(process.cwd(), dir);
|
|
917
1071
|
await fs.mkdir(root, { recursive: true });
|
|
918
1072
|
await ensureFile(path.join(root, CONFIG_FILE), `${JSON.stringify({
|
|
919
|
-
$schema: "https://
|
|
1073
|
+
$schema: "https://docs.blode.md/docs.json",
|
|
920
1074
|
colors: { primary: "#0D9373" },
|
|
921
1075
|
name: "my-project",
|
|
922
1076
|
navigation: { groups: [{
|
|
@@ -936,7 +1090,8 @@ program.command("init").description("Scaffold a docs folder").argument("[dir]",
|
|
|
936
1090
|
program.command("validate").description("Validate docs.json").argument("[dir]", "docs directory").action(async (dir) => {
|
|
937
1091
|
intro(chalk.bold("blodemd validate"));
|
|
938
1092
|
try {
|
|
939
|
-
await
|
|
1093
|
+
const { warnings } = await loadValidatedSiteConfig(await resolveDocsRoot(dir));
|
|
1094
|
+
for (const warning of warnings) log.warn(warning);
|
|
940
1095
|
log.success(`${chalk.cyan(CONFIG_FILE)} is valid.`);
|
|
941
1096
|
log.info("Done");
|
|
942
1097
|
} catch (error) {
|
|
@@ -949,8 +1104,9 @@ program.command("push").description("Deploy docs").argument("[dir]", "docs direc
|
|
|
949
1104
|
try {
|
|
950
1105
|
const root = await resolveDocsRoot(dir);
|
|
951
1106
|
s.start("Validating configuration");
|
|
952
|
-
const config = await
|
|
1107
|
+
const { config, warnings } = await loadValidatedSiteConfig(root);
|
|
953
1108
|
s.stop("Configuration valid");
|
|
1109
|
+
for (const warning of warnings) log.warn(warning);
|
|
954
1110
|
const { project, apiUrl, authToken, branch, commitMessage } = await resolvePushConfig(config, options);
|
|
955
1111
|
s.start("Collecting files");
|
|
956
1112
|
const files = await collectFiles(root);
|