nrdocs 0.1.8 → 0.2.1
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/dist/bin.mjs +493 -273
- package/package.json +3 -2
package/dist/bin.mjs
CHANGED
|
@@ -599,9 +599,292 @@ function authLogout(opts = {}) {
|
|
|
599
599
|
}
|
|
600
600
|
|
|
601
601
|
// src/commands/init.ts
|
|
602
|
+
import * as fs4 from "node:fs";
|
|
603
|
+
import * as path5 from "node:path";
|
|
604
|
+
import * as readline2 from "node:readline";
|
|
605
|
+
|
|
606
|
+
// src/config/docs-config.ts
|
|
607
|
+
import * as fs3 from "node:fs";
|
|
608
|
+
import * as path4 from "node:path";
|
|
609
|
+
import YAML from "yaml";
|
|
610
|
+
|
|
611
|
+
// src/renderer/navigation.ts
|
|
602
612
|
import * as fs2 from "node:fs";
|
|
603
613
|
import * as path3 from "node:path";
|
|
604
|
-
|
|
614
|
+
function extractTitle(markdownContent, filePath) {
|
|
615
|
+
const match2 = markdownContent.match(/^#\s+(.+)$/m);
|
|
616
|
+
if (match2) {
|
|
617
|
+
return match2[1].trim();
|
|
618
|
+
}
|
|
619
|
+
const basename3 = path3.basename(filePath, ".md");
|
|
620
|
+
if (basename3 === "index") {
|
|
621
|
+
return "Home";
|
|
622
|
+
}
|
|
623
|
+
return basename3.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
624
|
+
}
|
|
625
|
+
function findMarkdownFiles(dir, relativeTo) {
|
|
626
|
+
const results = [];
|
|
627
|
+
if (!fs2.existsSync(dir)) return results;
|
|
628
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
629
|
+
for (const entry of entries) {
|
|
630
|
+
const fullPath = path3.join(dir, entry.name);
|
|
631
|
+
if (entry.isDirectory()) {
|
|
632
|
+
results.push(...findMarkdownFiles(fullPath, relativeTo));
|
|
633
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
634
|
+
results.push(path3.relative(relativeTo, fullPath).replace(/\\/g, "/"));
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return results;
|
|
638
|
+
}
|
|
639
|
+
function sortNavPaths(files, indexPath = "index.md") {
|
|
640
|
+
const normalizedIndex = indexPath.replace(/\\/g, "/");
|
|
641
|
+
const sorted = [...files].sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
|
|
642
|
+
const indexIdx = sorted.indexOf(normalizedIndex);
|
|
643
|
+
if (indexIdx <= 0) return sorted;
|
|
644
|
+
const without = sorted.filter((f) => f !== normalizedIndex);
|
|
645
|
+
return [normalizedIndex, ...without];
|
|
646
|
+
}
|
|
647
|
+
function mdPathToHref(filePath) {
|
|
648
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
649
|
+
const withoutExt = normalized.replace(/\.md$/, "");
|
|
650
|
+
if (withoutExt === "index" || withoutExt.endsWith("/index")) {
|
|
651
|
+
const dir = withoutExt === "index" ? "" : withoutExt.slice(0, -"/index".length);
|
|
652
|
+
return dir ? `${dir}/` : "";
|
|
653
|
+
}
|
|
654
|
+
return `${withoutExt}/`;
|
|
655
|
+
}
|
|
656
|
+
function navEntryFromFile(contentDir, file) {
|
|
657
|
+
const fullPath = path3.join(contentDir, file);
|
|
658
|
+
const content = fs2.readFileSync(fullPath, "utf-8");
|
|
659
|
+
return {
|
|
660
|
+
title: extractTitle(content, file),
|
|
661
|
+
path: file
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
function folderSegmentToTitle(segment) {
|
|
665
|
+
if (segment === "index") return "Home";
|
|
666
|
+
return segment.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
667
|
+
}
|
|
668
|
+
function groupNavEntriesByFolders(files, contentDir, indexPath = "index.md") {
|
|
669
|
+
const sorted = sortNavPaths(files, indexPath);
|
|
670
|
+
const rootLinks = [];
|
|
671
|
+
const byFolder = /* @__PURE__ */ new Map();
|
|
672
|
+
for (const file of sorted) {
|
|
673
|
+
const dir = path3.dirname(file).replace(/\\/g, "/");
|
|
674
|
+
if (dir === ".") {
|
|
675
|
+
rootLinks.push(navEntryFromFile(contentDir, file));
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
const top = dir.split("/")[0];
|
|
679
|
+
const list2 = byFolder.get(top) ?? [];
|
|
680
|
+
list2.push(file);
|
|
681
|
+
byFolder.set(top, list2);
|
|
682
|
+
}
|
|
683
|
+
const sections = [];
|
|
684
|
+
for (const folder of [...byFolder.keys()].sort(
|
|
685
|
+
(a, b) => a.localeCompare(b, void 0, { numeric: true })
|
|
686
|
+
)) {
|
|
687
|
+
const paths = sortNavPaths(byFolder.get(folder), indexPath);
|
|
688
|
+
sections.push({
|
|
689
|
+
title: folderSegmentToTitle(folder),
|
|
690
|
+
children: paths.map((f) => navEntryFromFile(contentDir, f))
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
return [...rootLinks, ...sections];
|
|
694
|
+
}
|
|
695
|
+
function discoverNavEntries(contentDir, options) {
|
|
696
|
+
const indexPath = (options?.indexPath ?? "index.md").replace(/\\/g, "/");
|
|
697
|
+
const files = findMarkdownFiles(contentDir, contentDir);
|
|
698
|
+
return groupNavEntriesByFolders(files, contentDir, indexPath);
|
|
699
|
+
}
|
|
700
|
+
function navConfigToNavItems(entries, contentDir) {
|
|
701
|
+
const items = [];
|
|
702
|
+
const walk = (list2) => {
|
|
703
|
+
for (const entry of list2) {
|
|
704
|
+
if (entry.path) {
|
|
705
|
+
const normalizedPath = entry.path.replace(/\\/g, "/");
|
|
706
|
+
items.push({
|
|
707
|
+
title: entry.title,
|
|
708
|
+
path: normalizedPath,
|
|
709
|
+
href: mdPathToHref(normalizedPath)
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
if (entry.children?.length) {
|
|
713
|
+
walk(entry.children);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
walk(entries);
|
|
718
|
+
if (items.length === 0) {
|
|
719
|
+
throw new Error("Nav has no pages (entries need path or nested children with paths)");
|
|
720
|
+
}
|
|
721
|
+
for (const item of items) {
|
|
722
|
+
const full = path3.join(contentDir, item.path);
|
|
723
|
+
if (!fs2.existsSync(full)) {
|
|
724
|
+
throw new Error(`Nav path not found: ${item.path}`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return items;
|
|
728
|
+
}
|
|
729
|
+
function navConfigToSidebar(entries, activePath) {
|
|
730
|
+
const normalizedActive = activePath?.replace(/\\/g, "/");
|
|
731
|
+
const mapEntry = (entry) => {
|
|
732
|
+
const children = entry.children?.length ? entry.children.map(mapEntry).filter((n) => n !== null) : [];
|
|
733
|
+
if (entry.path) {
|
|
734
|
+
const normalizedPath = entry.path.replace(/\\/g, "/");
|
|
735
|
+
return {
|
|
736
|
+
kind: "link",
|
|
737
|
+
title: entry.title,
|
|
738
|
+
path: normalizedPath,
|
|
739
|
+
href: mdPathToHref(normalizedPath),
|
|
740
|
+
active: normalizedActive === normalizedPath
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
if (children.length === 0) return null;
|
|
744
|
+
const open = normalizedActive ? children.some((c) => sidebarContainsActive(c, normalizedActive)) : true;
|
|
745
|
+
return { kind: "section", title: entry.title, children, open };
|
|
746
|
+
};
|
|
747
|
+
return entries.map(mapEntry).filter((n) => n !== null);
|
|
748
|
+
}
|
|
749
|
+
function sidebarContainsActive(entry, activePath) {
|
|
750
|
+
if (entry.kind === "link") return entry.path === activePath;
|
|
751
|
+
return entry.children.some((c) => sidebarContainsActive(c, activePath));
|
|
752
|
+
}
|
|
753
|
+
function flattenNavPaths(entries) {
|
|
754
|
+
const paths = [];
|
|
755
|
+
const walk = (list2) => {
|
|
756
|
+
for (const e of list2) {
|
|
757
|
+
if (e.path) paths.push(e.path.replace(/\\/g, "/"));
|
|
758
|
+
if (e.children?.length) walk(e.children);
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
walk(entries);
|
|
762
|
+
return paths;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// src/config/docs-config.ts
|
|
766
|
+
function loadDocsConfig(docsDir) {
|
|
767
|
+
const configPath = path4.resolve(docsDir, "nrdocs.yml");
|
|
768
|
+
if (!fs3.existsSync(configPath)) {
|
|
769
|
+
throw new Error(`Config file not found: ${configPath}`);
|
|
770
|
+
}
|
|
771
|
+
const raw = fs3.readFileSync(configPath, "utf-8");
|
|
772
|
+
const config2 = YAML.parse(raw);
|
|
773
|
+
if (!config2 || typeof config2 !== "object") {
|
|
774
|
+
throw new Error(`Invalid config: ${configPath}`);
|
|
775
|
+
}
|
|
776
|
+
const sourceDir = config2.content?.source_dir ?? ".";
|
|
777
|
+
const contentDir = path4.resolve(docsDir, sourceDir);
|
|
778
|
+
return { config: config2, configPath, contentDir };
|
|
779
|
+
}
|
|
780
|
+
function hasExplicitNav(config2) {
|
|
781
|
+
return Array.isArray(config2.content?.nav);
|
|
782
|
+
}
|
|
783
|
+
function parseNavEntries(nav) {
|
|
784
|
+
if (!Array.isArray(nav)) {
|
|
785
|
+
throw new Error('content.nav must be a list or "auto"');
|
|
786
|
+
}
|
|
787
|
+
const entries = [];
|
|
788
|
+
for (const item of nav) {
|
|
789
|
+
if (!item || typeof item !== "object") {
|
|
790
|
+
throw new Error("Each nav entry must be an object with title");
|
|
791
|
+
}
|
|
792
|
+
const rec = item;
|
|
793
|
+
if (typeof rec["title"] !== "string") {
|
|
794
|
+
throw new Error("Each nav entry must have a title string");
|
|
795
|
+
}
|
|
796
|
+
const hasPath = typeof rec["path"] === "string";
|
|
797
|
+
const hasChildren = Array.isArray(rec["children"]) && rec["children"].length > 0;
|
|
798
|
+
if (!hasPath && !hasChildren) {
|
|
799
|
+
throw new Error("Each nav entry needs path and/or children");
|
|
800
|
+
}
|
|
801
|
+
const entry = { title: rec["title"] };
|
|
802
|
+
if (hasPath) {
|
|
803
|
+
entry.path = rec["path"].replace(/\\/g, "/");
|
|
804
|
+
}
|
|
805
|
+
if (Array.isArray(rec["children"])) {
|
|
806
|
+
entry.children = parseNavEntries(rec["children"]);
|
|
807
|
+
}
|
|
808
|
+
entries.push(entry);
|
|
809
|
+
}
|
|
810
|
+
return entries;
|
|
811
|
+
}
|
|
812
|
+
function getExplicitNav(config2) {
|
|
813
|
+
const nav = config2.content?.nav;
|
|
814
|
+
if (nav === void 0 || nav === "auto") return null;
|
|
815
|
+
if (Array.isArray(nav)) return parseNavEntries(nav);
|
|
816
|
+
throw new Error('content.nav must be "auto" or a list of entries');
|
|
817
|
+
}
|
|
818
|
+
function validateNavPaths(entries, contentDir) {
|
|
819
|
+
const errors = [];
|
|
820
|
+
const seen = /* @__PURE__ */ new Set();
|
|
821
|
+
const walk = (list2) => {
|
|
822
|
+
for (const e of list2) {
|
|
823
|
+
if (!e.path && !e.children?.length) {
|
|
824
|
+
errors.push(`Nav entry "${e.title}" has no path or children`);
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
if (e.path) {
|
|
828
|
+
const p = e.path.replace(/\\/g, "/");
|
|
829
|
+
if (seen.has(p)) {
|
|
830
|
+
errors.push(`Duplicate nav path: ${p}`);
|
|
831
|
+
}
|
|
832
|
+
seen.add(p);
|
|
833
|
+
const full = path4.join(contentDir, p);
|
|
834
|
+
if (!fs3.existsSync(full)) {
|
|
835
|
+
errors.push(`Nav path not found: ${p}`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (e.children?.length) walk(e.children);
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
walk(entries);
|
|
842
|
+
return { valid: errors.length === 0, errors };
|
|
843
|
+
}
|
|
844
|
+
function resolveContentIndex(navEntries, indexPath = "index.md") {
|
|
845
|
+
const paths = flattenNavPaths(navEntries);
|
|
846
|
+
const preferred = indexPath.replace(/\\/g, "/");
|
|
847
|
+
if (paths.includes(preferred)) return preferred;
|
|
848
|
+
return paths[0];
|
|
849
|
+
}
|
|
850
|
+
function writeNavToConfig(configPath, navEntries, options) {
|
|
851
|
+
const generatedBy = options?.generatedBy ?? "nrdocs nav generate";
|
|
852
|
+
let raw = fs3.readFileSync(configPath, "utf-8");
|
|
853
|
+
raw = raw.replace(/^# content\.nav generated by: .+\n/gm, "");
|
|
854
|
+
const config2 = YAML.parse(raw);
|
|
855
|
+
if (!config2 || typeof config2 !== "object") {
|
|
856
|
+
throw new Error(`Invalid config: ${configPath}`);
|
|
857
|
+
}
|
|
858
|
+
if (!config2.content) {
|
|
859
|
+
config2.content = {};
|
|
860
|
+
}
|
|
861
|
+
config2.content.nav = navEntries;
|
|
862
|
+
config2.content.index = resolveContentIndex(navEntries, options?.indexPath);
|
|
863
|
+
const doc = new YAML.Document(config2);
|
|
864
|
+
const header = `# content.nav generated by: ${generatedBy}
|
|
865
|
+
`;
|
|
866
|
+
const body = doc.toString();
|
|
867
|
+
fs3.writeFileSync(configPath, header + body, "utf-8");
|
|
868
|
+
}
|
|
869
|
+
function generateNavInConfig(docsDir, options) {
|
|
870
|
+
const loaded = loadDocsConfig(docsDir);
|
|
871
|
+
const indexPath = options?.indexPath ?? loaded.config.content?.index ?? "index.md";
|
|
872
|
+
const entries = discoverNavEntries(loaded.contentDir, { indexPath });
|
|
873
|
+
const pageCount = flattenNavPaths(entries).length;
|
|
874
|
+
if (pageCount === 0) return 0;
|
|
875
|
+
writeNavToConfig(loaded.configPath, entries, { ...options, indexPath });
|
|
876
|
+
return pageCount;
|
|
877
|
+
}
|
|
878
|
+
function formatNavYaml(navEntries) {
|
|
879
|
+
const partial = {
|
|
880
|
+
content: {
|
|
881
|
+
nav: navEntries
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
return YAML.stringify(partial).trimEnd();
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// src/commands/init.ts
|
|
605
888
|
async function prompt2(question, defaultValue) {
|
|
606
889
|
const rl = readline2.createInterface({
|
|
607
890
|
input: process.stdin,
|
|
@@ -622,7 +905,8 @@ site:
|
|
|
622
905
|
api_url: ${apiUrl}
|
|
623
906
|
|
|
624
907
|
content:
|
|
625
|
-
|
|
908
|
+
source_dir: .
|
|
909
|
+
nav: auto
|
|
626
910
|
`;
|
|
627
911
|
if (requestedAccess) {
|
|
628
912
|
yml += `
|
|
@@ -632,16 +916,6 @@ request:
|
|
|
632
916
|
}
|
|
633
917
|
return yml;
|
|
634
918
|
}
|
|
635
|
-
function generateIndexMd(title) {
|
|
636
|
-
return `# ${title}
|
|
637
|
-
|
|
638
|
-
Welcome to your documentation site powered by nrdocs.
|
|
639
|
-
|
|
640
|
-
## Getting Started
|
|
641
|
-
|
|
642
|
-
Edit this file to add your documentation content.
|
|
643
|
-
`;
|
|
644
|
-
}
|
|
645
919
|
function generateWorkflowYml(docsDir, apiUrl) {
|
|
646
920
|
return `name: Publish Docs (nrdocs)
|
|
647
921
|
|
|
@@ -680,6 +954,7 @@ jobs:
|
|
|
680
954
|
run: nrdocs publish --docs-dir ${docsDir}
|
|
681
955
|
env:
|
|
682
956
|
NRDOCS_API_URL: ${apiUrl}
|
|
957
|
+
NRDOCS_DOCS_PASSWORD: \${{ secrets.NRDOCS_DOCS_PASSWORD }}
|
|
683
958
|
`;
|
|
684
959
|
}
|
|
685
960
|
function parseInitArgs(args2) {
|
|
@@ -716,9 +991,9 @@ function normalizeUrl(url) {
|
|
|
716
991
|
return normalized;
|
|
717
992
|
}
|
|
718
993
|
function readExistingConfig(configPath) {
|
|
719
|
-
if (!
|
|
994
|
+
if (!fs4.existsSync(configPath)) return {};
|
|
720
995
|
try {
|
|
721
|
-
const content =
|
|
996
|
+
const content = fs4.readFileSync(configPath, "utf-8");
|
|
722
997
|
const titleMatch = content.match(/(?:title|name):\s*["']?([^"'\n]+)["']?/);
|
|
723
998
|
const apiMatch = content.match(/api_url:\s*["']?([^"'\n]+)["']?/);
|
|
724
999
|
return {
|
|
@@ -736,11 +1011,11 @@ async function handleInit(args2) {
|
|
|
736
1011
|
process.exit(2);
|
|
737
1012
|
}
|
|
738
1013
|
const docsDir = opts.docsDir || "docs";
|
|
739
|
-
const docsPath =
|
|
740
|
-
const configFile =
|
|
1014
|
+
const docsPath = path5.resolve(docsDir);
|
|
1015
|
+
const configFile = path5.join(docsPath, "nrdocs.yml");
|
|
741
1016
|
const existing = readExistingConfig(configFile);
|
|
742
|
-
const configExists =
|
|
743
|
-
const dirName =
|
|
1017
|
+
const configExists = fs4.existsSync(configFile);
|
|
1018
|
+
const dirName = path5.basename(process.cwd());
|
|
744
1019
|
let title = opts.title || existing.title;
|
|
745
1020
|
if (!title) {
|
|
746
1021
|
title = await prompt2("Site title", `${dirName} Docs`);
|
|
@@ -773,43 +1048,46 @@ async function handleInit(args2) {
|
|
|
773
1048
|
process.exit(2);
|
|
774
1049
|
}
|
|
775
1050
|
apiUrl = normalizeUrl(apiUrl);
|
|
776
|
-
const
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
if (!opts.force && fs2.existsSync(workflowFile)) {
|
|
1051
|
+
const workflowDir = path5.resolve(".github", "workflows");
|
|
1052
|
+
const workflowFile = path5.join(workflowDir, "nrdocs.yml");
|
|
1053
|
+
if (!opts.force && fs4.existsSync(workflowFile)) {
|
|
780
1054
|
console.error("Error: Workflow already exists:");
|
|
781
1055
|
console.error(` ${workflowFile}`);
|
|
782
1056
|
console.error("Use --force to overwrite.");
|
|
783
1057
|
process.exit(3);
|
|
784
1058
|
}
|
|
785
|
-
|
|
786
|
-
|
|
1059
|
+
fs4.mkdirSync(docsPath, { recursive: true });
|
|
1060
|
+
fs4.mkdirSync(workflowDir, { recursive: true });
|
|
787
1061
|
const createdConfig = !configExists || opts.force;
|
|
788
1062
|
if (createdConfig) {
|
|
789
|
-
|
|
1063
|
+
fs4.writeFileSync(configFile, generateNrdocsYml(title, apiUrl, opts.requestedAccess));
|
|
790
1064
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1065
|
+
fs4.writeFileSync(workflowFile, generateWorkflowYml(docsDir, apiUrl));
|
|
1066
|
+
let navPageCount = 0;
|
|
1067
|
+
if (createdConfig || opts.force) {
|
|
1068
|
+
navPageCount = generateNavInConfig(docsDir, { generatedBy: "nrdocs init" });
|
|
794
1069
|
}
|
|
795
|
-
fs2.writeFileSync(workflowFile, generateWorkflowYml(docsDir, apiUrl));
|
|
796
1070
|
console.log("nrdocs initialized successfully!");
|
|
797
1071
|
console.log("");
|
|
798
1072
|
console.log("Created/updated:");
|
|
799
1073
|
if (createdConfig) {
|
|
800
|
-
console.log(` ${
|
|
801
|
-
}
|
|
802
|
-
if (createdIndex) {
|
|
803
|
-
console.log(` ${path3.relative(process.cwd(), indexFile)}`);
|
|
1074
|
+
console.log(` ${path5.relative(process.cwd(), configFile)}`);
|
|
804
1075
|
}
|
|
805
|
-
console.log(` ${
|
|
1076
|
+
console.log(` ${path5.relative(process.cwd(), workflowFile)}`);
|
|
806
1077
|
if (!createdConfig) {
|
|
807
1078
|
console.log("");
|
|
808
|
-
console.log(`Using existing: ${
|
|
1079
|
+
console.log(`Using existing: ${path5.relative(process.cwd(), configFile)}`);
|
|
1080
|
+
}
|
|
1081
|
+
if (navPageCount > 0) {
|
|
1082
|
+
console.log(` content.nav: ${navPageCount} page(s) from markdown under ${docsDir}/`);
|
|
809
1083
|
}
|
|
810
1084
|
console.log("");
|
|
811
1085
|
console.log("Next steps:");
|
|
812
|
-
|
|
1086
|
+
if (navPageCount === 0) {
|
|
1087
|
+
console.log(` 1. Add markdown files under ${docsDir}/, then run: nrdocs nav generate`);
|
|
1088
|
+
} else {
|
|
1089
|
+
console.log(` 1. Edit content.nav in ${path5.relative(process.cwd(), configFile)} to reorder pages`);
|
|
1090
|
+
}
|
|
813
1091
|
console.log(" 2. Commit and push to trigger the workflow");
|
|
814
1092
|
console.log(" 3. Ask your operator to approve the repo");
|
|
815
1093
|
}
|
|
@@ -818,8 +1096,8 @@ async function handleInit(args2) {
|
|
|
818
1096
|
import * as fs8 from "node:fs";
|
|
819
1097
|
|
|
820
1098
|
// src/renderer/index.ts
|
|
821
|
-
import * as
|
|
822
|
-
import * as
|
|
1099
|
+
import * as fs7 from "node:fs";
|
|
1100
|
+
import * as path8 from "node:path";
|
|
823
1101
|
|
|
824
1102
|
// ../../node_modules/.pnpm/markdown-it@14.1.1/node_modules/markdown-it/lib/common/utils.mjs
|
|
825
1103
|
var utils_exports = {};
|
|
@@ -6021,97 +6299,6 @@ function renderMarkdown(content) {
|
|
|
6021
6299
|
return md.render(content);
|
|
6022
6300
|
}
|
|
6023
6301
|
|
|
6024
|
-
// src/renderer/navigation.ts
|
|
6025
|
-
import * as fs3 from "node:fs";
|
|
6026
|
-
import * as path4 from "node:path";
|
|
6027
|
-
function extractTitle(markdownContent, filePath) {
|
|
6028
|
-
const match2 = markdownContent.match(/^#\s+(.+)$/m);
|
|
6029
|
-
if (match2) {
|
|
6030
|
-
return match2[1].trim();
|
|
6031
|
-
}
|
|
6032
|
-
const basename3 = path4.basename(filePath, ".md");
|
|
6033
|
-
if (basename3 === "index") {
|
|
6034
|
-
return "Home";
|
|
6035
|
-
}
|
|
6036
|
-
return basename3.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
6037
|
-
}
|
|
6038
|
-
function findMarkdownFiles(dir, relativeTo) {
|
|
6039
|
-
const results = [];
|
|
6040
|
-
if (!fs3.existsSync(dir)) return results;
|
|
6041
|
-
const entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
6042
|
-
for (const entry of entries) {
|
|
6043
|
-
const fullPath = path4.join(dir, entry.name);
|
|
6044
|
-
if (entry.isDirectory()) {
|
|
6045
|
-
results.push(...findMarkdownFiles(fullPath, relativeTo));
|
|
6046
|
-
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
6047
|
-
results.push(path4.relative(relativeTo, fullPath).replace(/\\/g, "/"));
|
|
6048
|
-
}
|
|
6049
|
-
}
|
|
6050
|
-
return results;
|
|
6051
|
-
}
|
|
6052
|
-
function sortNavPaths(files, indexPath = "index.md") {
|
|
6053
|
-
const normalizedIndex = indexPath.replace(/\\/g, "/");
|
|
6054
|
-
const sorted = [...files].sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
|
|
6055
|
-
const indexIdx = sorted.indexOf(normalizedIndex);
|
|
6056
|
-
if (indexIdx <= 0) return sorted;
|
|
6057
|
-
const without = sorted.filter((f) => f !== normalizedIndex);
|
|
6058
|
-
return [normalizedIndex, ...without];
|
|
6059
|
-
}
|
|
6060
|
-
function mdPathToHref(filePath) {
|
|
6061
|
-
const normalized = filePath.replace(/\\/g, "/");
|
|
6062
|
-
const withoutExt = normalized.replace(/\.md$/, "");
|
|
6063
|
-
if (withoutExt === "index" || withoutExt.endsWith("/index")) {
|
|
6064
|
-
const dir = withoutExt === "index" ? "" : withoutExt.slice(0, -"/index".length);
|
|
6065
|
-
return dir ? `${dir}/` : "";
|
|
6066
|
-
}
|
|
6067
|
-
return `${withoutExt}/`;
|
|
6068
|
-
}
|
|
6069
|
-
function discoverNavEntries(contentDir, options) {
|
|
6070
|
-
const indexPath = (options?.indexPath ?? "index.md").replace(/\\/g, "/");
|
|
6071
|
-
const files = findMarkdownFiles(contentDir, contentDir);
|
|
6072
|
-
const sorted = sortNavPaths(files, indexPath);
|
|
6073
|
-
return sorted.map((file) => {
|
|
6074
|
-
const fullPath = path4.join(contentDir, file);
|
|
6075
|
-
const content = fs3.readFileSync(fullPath, "utf-8");
|
|
6076
|
-
return {
|
|
6077
|
-
title: extractTitle(content, file),
|
|
6078
|
-
path: file
|
|
6079
|
-
};
|
|
6080
|
-
});
|
|
6081
|
-
}
|
|
6082
|
-
function navConfigToNavItems(entries, contentDir) {
|
|
6083
|
-
const items = [];
|
|
6084
|
-
const walk = (list2) => {
|
|
6085
|
-
for (const entry of list2) {
|
|
6086
|
-
const normalizedPath = entry.path.replace(/\\/g, "/");
|
|
6087
|
-
items.push({
|
|
6088
|
-
title: entry.title,
|
|
6089
|
-
path: normalizedPath,
|
|
6090
|
-
href: mdPathToHref(normalizedPath)
|
|
6091
|
-
});
|
|
6092
|
-
if (entry.children?.length) {
|
|
6093
|
-
walk(entry.children);
|
|
6094
|
-
}
|
|
6095
|
-
}
|
|
6096
|
-
};
|
|
6097
|
-
walk(entries);
|
|
6098
|
-
for (const item of items) {
|
|
6099
|
-
const full = path4.join(contentDir, item.path);
|
|
6100
|
-
if (!fs3.existsSync(full)) {
|
|
6101
|
-
throw new Error(`Nav path not found: ${item.path}`);
|
|
6102
|
-
}
|
|
6103
|
-
}
|
|
6104
|
-
return items;
|
|
6105
|
-
}
|
|
6106
|
-
function generateAutoNav(docsDir, indexPath = "index.md") {
|
|
6107
|
-
const entries = discoverNavEntries(docsDir, { indexPath });
|
|
6108
|
-
return entries.map((e) => ({
|
|
6109
|
-
title: e.title,
|
|
6110
|
-
path: e.path,
|
|
6111
|
-
href: mdPathToHref(e.path)
|
|
6112
|
-
}));
|
|
6113
|
-
}
|
|
6114
|
-
|
|
6115
6302
|
// src/renderer/links.ts
|
|
6116
6303
|
function rewriteLinks(html, basePath, owner, repo) {
|
|
6117
6304
|
return html.replace(/<a\s+([^>]*?)href="([^"]*)"([^>]*?)>/g, (_match, before, href, after) => {
|
|
@@ -6281,6 +6468,13 @@ nav.sidebar .site-title{font-weight:700;font-size:1.1rem;color:var(--text);flex:
|
|
|
6281
6468
|
#theme-toggle:hover{background:var(--bg-active)}
|
|
6282
6469
|
nav.sidebar ul{list-style:none}
|
|
6283
6470
|
nav.sidebar li{margin-bottom:0.25rem}
|
|
6471
|
+
nav.sidebar .nav-section{margin-top:0.5rem}
|
|
6472
|
+
nav.sidebar .nav-section>summary{cursor:pointer;font-size:0.8rem;font-weight:600;text-transform:uppercase;letter-spacing:0.04em;color:var(--toc-title);padding:0.35rem 0.6rem;list-style:none;border-radius:4px;user-select:none}
|
|
6473
|
+
nav.sidebar .nav-section>summary::-webkit-details-marker{display:none}
|
|
6474
|
+
nav.sidebar .nav-section>summary::before{content:"\u25B8";display:inline-block;margin-right:0.35rem;transition:transform 0.15s ease}
|
|
6475
|
+
nav.sidebar .nav-section[open]>summary::before{transform:rotate(90deg)}
|
|
6476
|
+
nav.sidebar .nav-section>summary:hover{background:var(--bg-hover);color:var(--text)}
|
|
6477
|
+
nav.sidebar .nav-section ul{padding-left:0.25rem;margin-top:0.15rem;margin-bottom:0.35rem}
|
|
6284
6478
|
nav.sidebar a{color:var(--text-muted);text-decoration:none;padding:0.3rem 0.6rem;display:block;border-radius:4px;font-size:0.95rem}
|
|
6285
6479
|
nav.sidebar a:hover{background:var(--bg-hover);color:var(--text)}
|
|
6286
6480
|
nav.sidebar a.active{background:var(--bg-active);color:var(--link-active);font-weight:500}
|
|
@@ -6322,7 +6516,7 @@ footer a{color:var(--link)}
|
|
|
6322
6516
|
<button type="button" id="theme-toggle" aria-label="Toggle color theme" title="Toggle light/dark mode">☽</button>
|
|
6323
6517
|
</div>
|
|
6324
6518
|
<ul>
|
|
6325
|
-
${
|
|
6519
|
+
${renderNavTree(nav, baseUrl)}
|
|
6326
6520
|
</ul>
|
|
6327
6521
|
</nav>
|
|
6328
6522
|
<div class="content-wrapper">
|
|
@@ -6373,22 +6567,30 @@ ${items}
|
|
|
6373
6567
|
</ul>
|
|
6374
6568
|
</aside>`;
|
|
6375
6569
|
}
|
|
6376
|
-
function
|
|
6377
|
-
return
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6570
|
+
function renderNavTree(entries, baseUrl) {
|
|
6571
|
+
return entries.map((entry) => renderNavNode(entry, baseUrl)).join("\n");
|
|
6572
|
+
}
|
|
6573
|
+
function renderNavNode(entry, baseUrl) {
|
|
6574
|
+
if (entry.kind === "link") {
|
|
6575
|
+
const href = baseUrl + entry.href;
|
|
6576
|
+
const activeClass = entry.active ? ' class="active"' : "";
|
|
6577
|
+
return `<li><a href="${escapeHtml2(href)}"${activeClass}>${escapeHtml2(entry.title)}</a></li>`;
|
|
6578
|
+
}
|
|
6579
|
+
const openAttr = entry.open !== false ? " open" : "";
|
|
6580
|
+
const childItems = entry.children.map((c) => renderNavNode(c, baseUrl)).join("\n");
|
|
6581
|
+
return `<li class="nav-section"><details class="nav-details"${openAttr}><summary>${escapeHtml2(entry.title)}</summary><ul>
|
|
6582
|
+
${childItems}
|
|
6583
|
+
</ul></details></li>`;
|
|
6382
6584
|
}
|
|
6383
6585
|
function escapeHtml2(str) {
|
|
6384
6586
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
6385
6587
|
}
|
|
6386
6588
|
|
|
6387
6589
|
// src/renderer/assets.ts
|
|
6388
|
-
import * as
|
|
6389
|
-
import * as
|
|
6590
|
+
import * as fs5 from "node:fs";
|
|
6591
|
+
import * as path6 from "node:path";
|
|
6390
6592
|
function collectAssets(docsDir) {
|
|
6391
|
-
const resolvedDocsDir =
|
|
6593
|
+
const resolvedDocsDir = path6.resolve(docsDir);
|
|
6392
6594
|
const files = [];
|
|
6393
6595
|
collectFromDir(resolvedDocsDir, resolvedDocsDir, files);
|
|
6394
6596
|
return files;
|
|
@@ -6396,13 +6598,13 @@ function collectAssets(docsDir) {
|
|
|
6396
6598
|
function collectFromDir(dir, rootDir, results) {
|
|
6397
6599
|
let entries;
|
|
6398
6600
|
try {
|
|
6399
|
-
entries =
|
|
6601
|
+
entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
6400
6602
|
} catch {
|
|
6401
6603
|
return;
|
|
6402
6604
|
}
|
|
6403
6605
|
for (const entry of entries) {
|
|
6404
|
-
const fullPath =
|
|
6405
|
-
const resolved =
|
|
6606
|
+
const fullPath = path6.join(dir, entry.name);
|
|
6607
|
+
const resolved = path6.resolve(fullPath);
|
|
6406
6608
|
if (!resolved.startsWith(rootDir)) {
|
|
6407
6609
|
continue;
|
|
6408
6610
|
}
|
|
@@ -6410,37 +6612,37 @@ function collectFromDir(dir, rootDir, results) {
|
|
|
6410
6612
|
if (entry.name.startsWith(".")) continue;
|
|
6411
6613
|
collectFromDir(fullPath, rootDir, results);
|
|
6412
6614
|
} else if (entry.isFile()) {
|
|
6413
|
-
const ext =
|
|
6615
|
+
const ext = path6.extname(entry.name).toLowerCase();
|
|
6414
6616
|
if (ext === ".md") continue;
|
|
6415
6617
|
if (REJECTED_EXTENSIONS.has(ext)) continue;
|
|
6416
6618
|
if (!ALLOWED_ASSET_EXTENSIONS.has(ext)) continue;
|
|
6417
|
-
const relativePath =
|
|
6619
|
+
const relativePath = path6.relative(rootDir, fullPath);
|
|
6418
6620
|
if (relativePath.includes("..")) continue;
|
|
6419
6621
|
results.push({
|
|
6420
6622
|
path: relativePath.replace(/\\/g, "/"),
|
|
6421
|
-
content:
|
|
6623
|
+
content: fs5.readFileSync(fullPath)
|
|
6422
6624
|
});
|
|
6423
6625
|
}
|
|
6424
6626
|
}
|
|
6425
6627
|
}
|
|
6426
6628
|
|
|
6427
6629
|
// src/renderer/mermaid-runtime.ts
|
|
6428
|
-
import * as
|
|
6429
|
-
import * as
|
|
6630
|
+
import * as fs6 from "node:fs";
|
|
6631
|
+
import * as path7 from "node:path";
|
|
6430
6632
|
import { fileURLToPath } from "node:url";
|
|
6431
|
-
var __dirname =
|
|
6633
|
+
var __dirname = path7.dirname(fileURLToPath(import.meta.url));
|
|
6432
6634
|
var MERMAID_RUNTIME_REL = "runtime/mermaid.min.js";
|
|
6433
6635
|
var MERMAID_ARTIFACT_PATH = "_nrdocs/mermaid.min.js";
|
|
6434
6636
|
function mermaidRuntimeCandidates() {
|
|
6435
6637
|
return [
|
|
6436
|
-
|
|
6437
|
-
|
|
6638
|
+
path7.join(__dirname, MERMAID_RUNTIME_REL),
|
|
6639
|
+
path7.join(__dirname, "../../dist/runtime/mermaid.min.js")
|
|
6438
6640
|
];
|
|
6439
6641
|
}
|
|
6440
6642
|
function loadMermaidRuntime() {
|
|
6441
6643
|
for (const candidate of mermaidRuntimeCandidates()) {
|
|
6442
|
-
if (
|
|
6443
|
-
return
|
|
6644
|
+
if (fs6.existsSync(candidate)) {
|
|
6645
|
+
return fs6.readFileSync(candidate);
|
|
6444
6646
|
}
|
|
6445
6647
|
}
|
|
6446
6648
|
throw new Error(
|
|
@@ -6454,40 +6656,44 @@ function mermaidScriptSrcForOutput(outputPath) {
|
|
|
6454
6656
|
}
|
|
6455
6657
|
|
|
6456
6658
|
// src/renderer/index.ts
|
|
6457
|
-
function
|
|
6659
|
+
function resolveNav(resolvedDocsDir, nav, indexPath) {
|
|
6458
6660
|
if (nav && nav !== "auto" && Array.isArray(nav)) {
|
|
6459
|
-
return
|
|
6661
|
+
return {
|
|
6662
|
+
items: navConfigToNavItems(nav, resolvedDocsDir),
|
|
6663
|
+
sidebarConfig: nav
|
|
6664
|
+
};
|
|
6460
6665
|
}
|
|
6461
|
-
|
|
6666
|
+
const sidebarConfig = discoverNavEntries(resolvedDocsDir, { indexPath });
|
|
6667
|
+
return {
|
|
6668
|
+
items: navConfigToNavItems(sidebarConfig, resolvedDocsDir),
|
|
6669
|
+
sidebarConfig
|
|
6670
|
+
};
|
|
6462
6671
|
}
|
|
6463
6672
|
async function renderSite(options) {
|
|
6464
6673
|
const { docsDir, siteTitle, baseUrl, owner, repo, nav, indexPath = "index.md" } = options;
|
|
6465
|
-
const resolvedDocsDir =
|
|
6466
|
-
const navItems =
|
|
6674
|
+
const resolvedDocsDir = path8.resolve(docsDir);
|
|
6675
|
+
const { items: navItems, sidebarConfig } = resolveNav(resolvedDocsDir, nav, indexPath);
|
|
6467
6676
|
const siteBase = `/${owner}/${repo}/`;
|
|
6468
6677
|
const renderedFiles = [];
|
|
6469
6678
|
let siteHasMermaid = false;
|
|
6470
6679
|
for (const navItem of navItems) {
|
|
6471
|
-
const filePath =
|
|
6472
|
-
const markdownContent =
|
|
6680
|
+
const filePath = path8.join(resolvedDocsDir, navItem.path);
|
|
6681
|
+
const markdownContent = fs7.readFileSync(filePath, "utf-8");
|
|
6473
6682
|
const pageHasMermaid = contentHasMermaid(markdownContent);
|
|
6474
6683
|
if (pageHasMermaid) siteHasMermaid = true;
|
|
6475
6684
|
let html = renderMarkdown(markdownContent);
|
|
6476
|
-
const fileDir =
|
|
6685
|
+
const fileDir = path8.dirname(navItem.path);
|
|
6477
6686
|
const baseLinkPath = fileDir === "." ? "" : fileDir;
|
|
6478
6687
|
html = rewriteLinks(html, baseLinkPath, owner, repo);
|
|
6479
6688
|
const pageTitle = extractTitle(markdownContent, navItem.path);
|
|
6480
6689
|
const canonicalUrl = `${baseUrl}${siteBase}${navItem.href}`;
|
|
6481
|
-
const
|
|
6482
|
-
...item,
|
|
6483
|
-
active: item.path === navItem.path
|
|
6484
|
-
}));
|
|
6690
|
+
const sidebar = navConfigToSidebar(sidebarConfig, navItem.path);
|
|
6485
6691
|
const outputPath = navItem.href === "" ? "index.html" : navItem.href.replace(/\/$/, "") + "/index.html";
|
|
6486
6692
|
const fullHtml = wrapInTemplate({
|
|
6487
6693
|
title: pageTitle,
|
|
6488
6694
|
siteTitle,
|
|
6489
6695
|
content: html,
|
|
6490
|
-
nav:
|
|
6696
|
+
nav: sidebar,
|
|
6491
6697
|
canonicalUrl,
|
|
6492
6698
|
baseUrl: siteBase,
|
|
6493
6699
|
includeMermaid: pageHasMermaid,
|
|
@@ -6918,15 +7124,23 @@ var ApiClient = class {
|
|
|
6918
7124
|
password
|
|
6919
7125
|
});
|
|
6920
7126
|
}
|
|
7127
|
+
async setSelfPasswordAllow(owner, repo, allow) {
|
|
7128
|
+
const path12 = allow ? `/api/repos/${owner}/${repo}/allow-self-password` : `/api/repos/${owner}/${repo}/disallow-self-password`;
|
|
7129
|
+
return this.request("POST", path12);
|
|
7130
|
+
}
|
|
6921
7131
|
async listRules() {
|
|
6922
7132
|
return this.request("GET", "/api/auto-approval-rules");
|
|
6923
7133
|
}
|
|
6924
|
-
async addRule(pattern, accessMode, applyExisting) {
|
|
6925
|
-
|
|
7134
|
+
async addRule(pattern, accessMode, applyExisting, defaultAllowSelfPassword) {
|
|
7135
|
+
const body = {
|
|
6926
7136
|
pattern,
|
|
6927
7137
|
access_mode: accessMode,
|
|
6928
7138
|
apply_existing: applyExisting ?? false
|
|
6929
|
-
}
|
|
7139
|
+
};
|
|
7140
|
+
if (defaultAllowSelfPassword !== void 0) {
|
|
7141
|
+
body["default_allow_repo_owner_password"] = defaultAllowSelfPassword;
|
|
7142
|
+
}
|
|
7143
|
+
return this.request("POST", "/api/auto-approval-rules", body);
|
|
6930
7144
|
}
|
|
6931
7145
|
async removeRule(ruleId) {
|
|
6932
7146
|
return this.request("DELETE", `/api/auto-approval-rules/${ruleId}`);
|
|
@@ -6980,102 +7194,6 @@ var ApiClient = class {
|
|
|
6980
7194
|
}
|
|
6981
7195
|
};
|
|
6982
7196
|
|
|
6983
|
-
// src/config/docs-config.ts
|
|
6984
|
-
import * as fs7 from "node:fs";
|
|
6985
|
-
import * as path8 from "node:path";
|
|
6986
|
-
import YAML from "yaml";
|
|
6987
|
-
function loadDocsConfig(docsDir) {
|
|
6988
|
-
const configPath = path8.resolve(docsDir, "nrdocs.yml");
|
|
6989
|
-
if (!fs7.existsSync(configPath)) {
|
|
6990
|
-
throw new Error(`Config file not found: ${configPath}`);
|
|
6991
|
-
}
|
|
6992
|
-
const raw = fs7.readFileSync(configPath, "utf-8");
|
|
6993
|
-
const config2 = YAML.parse(raw);
|
|
6994
|
-
if (!config2 || typeof config2 !== "object") {
|
|
6995
|
-
throw new Error(`Invalid config: ${configPath}`);
|
|
6996
|
-
}
|
|
6997
|
-
const sourceDir = config2.content?.source_dir ?? ".";
|
|
6998
|
-
const contentDir = path8.resolve(docsDir, sourceDir);
|
|
6999
|
-
return { config: config2, configPath, contentDir };
|
|
7000
|
-
}
|
|
7001
|
-
function hasExplicitNav(config2) {
|
|
7002
|
-
return Array.isArray(config2.content?.nav);
|
|
7003
|
-
}
|
|
7004
|
-
function parseNavEntries(nav) {
|
|
7005
|
-
if (!Array.isArray(nav)) {
|
|
7006
|
-
throw new Error('content.nav must be a list or "auto"');
|
|
7007
|
-
}
|
|
7008
|
-
const entries = [];
|
|
7009
|
-
for (const item of nav) {
|
|
7010
|
-
if (!item || typeof item !== "object") {
|
|
7011
|
-
throw new Error("Each nav entry must have title and path");
|
|
7012
|
-
}
|
|
7013
|
-
const rec = item;
|
|
7014
|
-
if (typeof rec["title"] !== "string" || typeof rec["path"] !== "string") {
|
|
7015
|
-
throw new Error("Each nav entry must have title and path strings");
|
|
7016
|
-
}
|
|
7017
|
-
const entry = {
|
|
7018
|
-
title: rec["title"],
|
|
7019
|
-
path: rec["path"].replace(/\\/g, "/")
|
|
7020
|
-
};
|
|
7021
|
-
if (Array.isArray(rec["children"])) {
|
|
7022
|
-
entry.children = parseNavEntries(rec["children"]);
|
|
7023
|
-
}
|
|
7024
|
-
entries.push(entry);
|
|
7025
|
-
}
|
|
7026
|
-
return entries;
|
|
7027
|
-
}
|
|
7028
|
-
function getExplicitNav(config2) {
|
|
7029
|
-
const nav = config2.content?.nav;
|
|
7030
|
-
if (nav === void 0 || nav === "auto") return null;
|
|
7031
|
-
if (Array.isArray(nav)) return parseNavEntries(nav);
|
|
7032
|
-
throw new Error('content.nav must be "auto" or a list of entries');
|
|
7033
|
-
}
|
|
7034
|
-
function validateNavPaths(entries, contentDir) {
|
|
7035
|
-
const errors = [];
|
|
7036
|
-
const seen = /* @__PURE__ */ new Set();
|
|
7037
|
-
const walk = (list2) => {
|
|
7038
|
-
for (const e of list2) {
|
|
7039
|
-
const p = e.path.replace(/\\/g, "/");
|
|
7040
|
-
if (seen.has(p)) {
|
|
7041
|
-
errors.push(`Duplicate nav path: ${p}`);
|
|
7042
|
-
}
|
|
7043
|
-
seen.add(p);
|
|
7044
|
-
const full = path8.join(contentDir, p);
|
|
7045
|
-
if (!fs7.existsSync(full)) {
|
|
7046
|
-
errors.push(`Nav path not found: ${p}`);
|
|
7047
|
-
}
|
|
7048
|
-
if (e.children?.length) walk(e.children);
|
|
7049
|
-
}
|
|
7050
|
-
};
|
|
7051
|
-
walk(entries);
|
|
7052
|
-
return { valid: errors.length === 0, errors };
|
|
7053
|
-
}
|
|
7054
|
-
function writeNavToConfig(configPath, navEntries) {
|
|
7055
|
-
let raw = fs7.readFileSync(configPath, "utf-8");
|
|
7056
|
-
raw = raw.replace(/^# content\.nav generated by: nrdocs nav generate\n/gm, "");
|
|
7057
|
-
const config2 = YAML.parse(raw);
|
|
7058
|
-
if (!config2 || typeof config2 !== "object") {
|
|
7059
|
-
throw new Error(`Invalid config: ${configPath}`);
|
|
7060
|
-
}
|
|
7061
|
-
if (!config2.content) {
|
|
7062
|
-
config2.content = {};
|
|
7063
|
-
}
|
|
7064
|
-
config2.content.nav = navEntries;
|
|
7065
|
-
const doc = new YAML.Document(config2);
|
|
7066
|
-
const header = "# content.nav generated by: nrdocs nav generate\n";
|
|
7067
|
-
const body = doc.toString();
|
|
7068
|
-
fs7.writeFileSync(configPath, header + body, "utf-8");
|
|
7069
|
-
}
|
|
7070
|
-
function formatNavYaml(navEntries) {
|
|
7071
|
-
const partial = {
|
|
7072
|
-
content: {
|
|
7073
|
-
nav: navEntries
|
|
7074
|
-
}
|
|
7075
|
-
};
|
|
7076
|
-
return YAML.stringify(partial).trimEnd();
|
|
7077
|
-
}
|
|
7078
|
-
|
|
7079
7197
|
// src/commands/publish.ts
|
|
7080
7198
|
function parsePublishArgs(args2) {
|
|
7081
7199
|
const opts = {};
|
|
@@ -7228,6 +7346,10 @@ async function handlePublish(args2) {
|
|
|
7228
7346
|
artifact: { format: "tar.gz", size_bytes: archive.length },
|
|
7229
7347
|
nrdocs: { cli_version: "0.1.1" }
|
|
7230
7348
|
}));
|
|
7349
|
+
const docsPasswordRaw = process.env["NRDOCS_DOCS_PASSWORD"];
|
|
7350
|
+
if (typeof docsPasswordRaw === "string" && docsPasswordRaw.length > 0) {
|
|
7351
|
+
formData.append("password", docsPasswordRaw);
|
|
7352
|
+
}
|
|
7231
7353
|
const result = await client.publish(formData, opts.verbose);
|
|
7232
7354
|
if (result.ok) {
|
|
7233
7355
|
const data = result.data;
|
|
@@ -7556,7 +7678,7 @@ async function handleNavGenerate(args2) {
|
|
|
7556
7678
|
console.log(formatNavYaml(entries));
|
|
7557
7679
|
return;
|
|
7558
7680
|
}
|
|
7559
|
-
|
|
7681
|
+
generateNavInConfig(docsDir, { generatedBy: "nrdocs nav generate", indexPath });
|
|
7560
7682
|
if (!opts.json) {
|
|
7561
7683
|
console.log(`Generated navigation for ${entries.length} page(s) in ${path10.relative(process.cwd(), configPath)}`);
|
|
7562
7684
|
console.log("Edit the file to reorder or rename entries, then run publish.");
|
|
@@ -8188,6 +8310,83 @@ async function handlePasswordSet(args2) {
|
|
|
8188
8310
|
console.log(`Password set for ${owner}/${repo}.`);
|
|
8189
8311
|
}
|
|
8190
8312
|
}
|
|
8313
|
+
function parsePasswordAllowArgs(args2) {
|
|
8314
|
+
const opts = {};
|
|
8315
|
+
for (let i = 0; i < args2.length; i++) {
|
|
8316
|
+
const arg = args2[i];
|
|
8317
|
+
if (arg === "--json") {
|
|
8318
|
+
opts.json = true;
|
|
8319
|
+
} else if (!arg?.startsWith("--") && !opts.repo) {
|
|
8320
|
+
opts.repo = arg;
|
|
8321
|
+
}
|
|
8322
|
+
}
|
|
8323
|
+
return opts;
|
|
8324
|
+
}
|
|
8325
|
+
function parsePasswordDisallowArgs(args2) {
|
|
8326
|
+
return parsePasswordAllowArgs(args2);
|
|
8327
|
+
}
|
|
8328
|
+
async function handlePasswordAllow(args2) {
|
|
8329
|
+
const opts = parsePasswordAllowArgs(args2);
|
|
8330
|
+
if (!opts.repo) {
|
|
8331
|
+
console.error("Error: Repository required. Usage: nrdocs password allow owner/repo");
|
|
8332
|
+
process.exit(2);
|
|
8333
|
+
}
|
|
8334
|
+
const parts = opts.repo.split("/");
|
|
8335
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
8336
|
+
console.error('Error: Repository must be in "owner/repo" format.');
|
|
8337
|
+
process.exit(2);
|
|
8338
|
+
}
|
|
8339
|
+
const [owner, repo] = parts;
|
|
8340
|
+
let creds;
|
|
8341
|
+
try {
|
|
8342
|
+
creds = resolveCredentials();
|
|
8343
|
+
} catch (e) {
|
|
8344
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
8345
|
+
process.exit(1);
|
|
8346
|
+
}
|
|
8347
|
+
const client = new ApiClient(creds.api_url, creds.operator_token);
|
|
8348
|
+
const res = await client.setSelfPasswordAllow(owner, repo, true);
|
|
8349
|
+
if (!res.ok) {
|
|
8350
|
+
console.error(`Error: ${res.error?.message ?? "Unknown error"}`);
|
|
8351
|
+
process.exit(1);
|
|
8352
|
+
}
|
|
8353
|
+
if (opts.json) {
|
|
8354
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
8355
|
+
} else {
|
|
8356
|
+
console.log(`Self-service password enabled for ${owner}/${repo}.`);
|
|
8357
|
+
}
|
|
8358
|
+
}
|
|
8359
|
+
async function handlePasswordDisallow(args2) {
|
|
8360
|
+
const opts = parsePasswordDisallowArgs(args2);
|
|
8361
|
+
if (!opts.repo) {
|
|
8362
|
+
console.error("Error: Repository required. Usage: nrdocs password disallow owner/repo");
|
|
8363
|
+
process.exit(2);
|
|
8364
|
+
}
|
|
8365
|
+
const parts = opts.repo.split("/");
|
|
8366
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
8367
|
+
console.error('Error: Repository must be in "owner/repo" format.');
|
|
8368
|
+
process.exit(2);
|
|
8369
|
+
}
|
|
8370
|
+
const [owner, repo] = parts;
|
|
8371
|
+
let creds;
|
|
8372
|
+
try {
|
|
8373
|
+
creds = resolveCredentials();
|
|
8374
|
+
} catch (e) {
|
|
8375
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
8376
|
+
process.exit(1);
|
|
8377
|
+
}
|
|
8378
|
+
const client = new ApiClient(creds.api_url, creds.operator_token);
|
|
8379
|
+
const res = await client.setSelfPasswordAllow(owner, repo, false);
|
|
8380
|
+
if (!res.ok) {
|
|
8381
|
+
console.error(`Error: ${res.error?.message ?? "Unknown error"}`);
|
|
8382
|
+
process.exit(1);
|
|
8383
|
+
}
|
|
8384
|
+
if (opts.json) {
|
|
8385
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
8386
|
+
} else {
|
|
8387
|
+
console.log(`Self-service password disabled for ${owner}/${repo}.`);
|
|
8388
|
+
}
|
|
8389
|
+
}
|
|
8191
8390
|
|
|
8192
8391
|
// src/commands/rules.ts
|
|
8193
8392
|
function parseRulesListArgs(args2) {
|
|
@@ -8206,6 +8405,13 @@ function parseRulesAddArgs(args2) {
|
|
|
8206
8405
|
opts.access = args2[++i];
|
|
8207
8406
|
} else if (arg === "--apply-existing") {
|
|
8208
8407
|
opts.applyExisting = true;
|
|
8408
|
+
} else if (arg === "--self-set-password" && i + 1 < args2.length) {
|
|
8409
|
+
const v = args2[++i];
|
|
8410
|
+
if (v === "allow" || v === "deny") {
|
|
8411
|
+
opts.selfSetPassword = v;
|
|
8412
|
+
} else {
|
|
8413
|
+
opts.selfSetPassword = "__invalid__";
|
|
8414
|
+
}
|
|
8209
8415
|
} else if (arg === "--json") {
|
|
8210
8416
|
opts.json = true;
|
|
8211
8417
|
} else if (!arg?.startsWith("--")) {
|
|
@@ -8229,11 +8435,12 @@ function parseRulesRemoveArgs(args2) {
|
|
|
8229
8435
|
}
|
|
8230
8436
|
function formatRulesTable(rules) {
|
|
8231
8437
|
if (rules.length === 0) return "No rules configured.";
|
|
8232
|
-
const headers = ["ID", "PATTERN", "ACCESS", "ENABLED", "PRIORITY"];
|
|
8438
|
+
const headers = ["ID", "PATTERN", "ACCESS", "SELF-PWD", "ENABLED", "PRIORITY"];
|
|
8233
8439
|
const rows = rules.map((r) => [
|
|
8234
8440
|
String(r["id"] ?? "-").slice(0, 8),
|
|
8235
8441
|
String(r["pattern"] ?? "-"),
|
|
8236
8442
|
String(r["access_mode"] ?? "-"),
|
|
8443
|
+
r["default_allow_repo_owner_password"] === true ? "allow" : "deny",
|
|
8237
8444
|
String(r["enabled"] ?? "-"),
|
|
8238
8445
|
String(r["priority"] ?? "-")
|
|
8239
8446
|
]);
|
|
@@ -8286,6 +8493,11 @@ async function handleRulesAdd(args2) {
|
|
|
8286
8493
|
console.error('Error: --access is required and must be "public" or "password".');
|
|
8287
8494
|
process.exit(2);
|
|
8288
8495
|
}
|
|
8496
|
+
if (opts.selfSetPassword !== void 0 && opts.selfSetPassword !== "allow" && opts.selfSetPassword !== "deny") {
|
|
8497
|
+
console.error('Error: --self-set-password must be "allow" or "deny".');
|
|
8498
|
+
process.exit(2);
|
|
8499
|
+
}
|
|
8500
|
+
const defaultAllowSelfPassword = opts.selfSetPassword === void 0 ? true : opts.selfSetPassword === "allow";
|
|
8289
8501
|
let creds;
|
|
8290
8502
|
try {
|
|
8291
8503
|
creds = resolveCredentials();
|
|
@@ -8294,7 +8506,7 @@ async function handleRulesAdd(args2) {
|
|
|
8294
8506
|
process.exit(1);
|
|
8295
8507
|
}
|
|
8296
8508
|
const client = new ApiClient(creds.api_url, creds.operator_token);
|
|
8297
|
-
const res = await client.addRule(opts.pattern, opts.access, opts.applyExisting);
|
|
8509
|
+
const res = await client.addRule(opts.pattern, opts.access, opts.applyExisting, defaultAllowSelfPassword);
|
|
8298
8510
|
if (!res.ok) {
|
|
8299
8511
|
console.error(`Error: ${res.error?.message ?? "Unknown error"}`);
|
|
8300
8512
|
process.exit(1);
|
|
@@ -8564,11 +8776,19 @@ async function runCommand(args2) {
|
|
|
8564
8776
|
}
|
|
8565
8777
|
break;
|
|
8566
8778
|
case "password":
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8779
|
+
switch (subCmd) {
|
|
8780
|
+
case "set":
|
|
8781
|
+
await handlePasswordSet(args2.slice(2));
|
|
8782
|
+
break;
|
|
8783
|
+
case "allow":
|
|
8784
|
+
await handlePasswordAllow(args2.slice(2));
|
|
8785
|
+
break;
|
|
8786
|
+
case "disallow":
|
|
8787
|
+
await handlePasswordDisallow(args2.slice(2));
|
|
8788
|
+
break;
|
|
8789
|
+
default:
|
|
8790
|
+
console.error("Usage: nrdocs password <set|allow|disallow> <owner/repo> [...]");
|
|
8791
|
+
process.exitCode = 1;
|
|
8572
8792
|
}
|
|
8573
8793
|
break;
|
|
8574
8794
|
case "rules":
|
|
@@ -8682,7 +8902,7 @@ Operator commands:
|
|
|
8682
8902
|
approve Approve a repo for serving
|
|
8683
8903
|
disable Disable serving for a repo
|
|
8684
8904
|
access Change access mode
|
|
8685
|
-
password
|
|
8905
|
+
password Manage passwords (set | allow | disallow)
|
|
8686
8906
|
rules Manage auto-approval rules
|
|
8687
8907
|
status Show repo status
|
|
8688
8908
|
config Show configuration
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nrdocs",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "CLI for nrdocs - serverless docs publishing for private GitHub repos",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,8 +23,9 @@
|
|
|
23
23
|
"@types/markdown-it": "^14.1.2",
|
|
24
24
|
"@types/node": "^20.14.0",
|
|
25
25
|
"esbuild": "^0.28.0",
|
|
26
|
-
"
|
|
26
|
+
"fast-check": "^4.8.0",
|
|
27
27
|
"markdown-it": "^14.1.1",
|
|
28
|
+
"mermaid": "^11.6.0",
|
|
28
29
|
"typescript": "^5.5.0",
|
|
29
30
|
"vitest": "^2.0.0"
|
|
30
31
|
},
|