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.
Files changed (2) hide show
  1. package/dist/bin.mjs +493 -273
  2. 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
- import * as readline2 from "node:readline";
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
- index: index.md
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 (!fs2.existsSync(configPath)) return {};
994
+ if (!fs4.existsSync(configPath)) return {};
720
995
  try {
721
- const content = fs2.readFileSync(configPath, "utf-8");
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 = path3.resolve(docsDir);
740
- const configFile = path3.join(docsPath, "nrdocs.yml");
1014
+ const docsPath = path5.resolve(docsDir);
1015
+ const configFile = path5.join(docsPath, "nrdocs.yml");
741
1016
  const existing = readExistingConfig(configFile);
742
- const configExists = fs2.existsSync(configFile);
743
- const dirName = path3.basename(process.cwd());
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 indexFile = path3.join(docsPath, "index.md");
777
- const workflowDir = path3.resolve(".github", "workflows");
778
- const workflowFile = path3.join(workflowDir, "nrdocs.yml");
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
- fs2.mkdirSync(docsPath, { recursive: true });
786
- fs2.mkdirSync(workflowDir, { recursive: true });
1059
+ fs4.mkdirSync(docsPath, { recursive: true });
1060
+ fs4.mkdirSync(workflowDir, { recursive: true });
787
1061
  const createdConfig = !configExists || opts.force;
788
1062
  if (createdConfig) {
789
- fs2.writeFileSync(configFile, generateNrdocsYml(title, apiUrl, opts.requestedAccess));
1063
+ fs4.writeFileSync(configFile, generateNrdocsYml(title, apiUrl, opts.requestedAccess));
790
1064
  }
791
- const createdIndex = !fs2.existsSync(indexFile);
792
- if (createdIndex) {
793
- fs2.writeFileSync(indexFile, generateIndexMd(title));
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(` ${path3.relative(process.cwd(), configFile)}`);
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(` ${path3.relative(process.cwd(), workflowFile)}`);
1076
+ console.log(` ${path5.relative(process.cwd(), workflowFile)}`);
806
1077
  if (!createdConfig) {
807
1078
  console.log("");
808
- console.log(`Using existing: ${path3.relative(process.cwd(), configFile)}`);
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
- console.log(" 1. Add markdown files under docs/, then run: nrdocs nav generate");
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 fs6 from "node:fs";
822
- import * as path7 from "node:path";
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">&#9789;</button>
6323
6517
  </div>
6324
6518
  <ul>
6325
- ${renderNavItems(nav, baseUrl)}
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 renderNavItems(items, baseUrl) {
6377
- return items.map((item) => {
6378
- const href = baseUrl + item.href;
6379
- const activeClass = item.active ? ' class="active"' : "";
6380
- return `<li><a href="${escapeHtml2(href)}"${activeClass}>${escapeHtml2(item.title)}</a></li>`;
6381
- }).join("\n");
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
6385
6587
  }
6386
6588
 
6387
6589
  // src/renderer/assets.ts
6388
- import * as fs4 from "node:fs";
6389
- import * as path5 from "node:path";
6590
+ import * as fs5 from "node:fs";
6591
+ import * as path6 from "node:path";
6390
6592
  function collectAssets(docsDir) {
6391
- const resolvedDocsDir = path5.resolve(docsDir);
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 = fs4.readdirSync(dir, { withFileTypes: true });
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 = path5.join(dir, entry.name);
6405
- const resolved = path5.resolve(fullPath);
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 = path5.extname(entry.name).toLowerCase();
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 = path5.relative(rootDir, fullPath);
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: fs4.readFileSync(fullPath)
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 fs5 from "node:fs";
6429
- import * as path6 from "node:path";
6630
+ import * as fs6 from "node:fs";
6631
+ import * as path7 from "node:path";
6430
6632
  import { fileURLToPath } from "node:url";
6431
- var __dirname = path6.dirname(fileURLToPath(import.meta.url));
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
- path6.join(__dirname, MERMAID_RUNTIME_REL),
6437
- path6.join(__dirname, "../../dist/runtime/mermaid.min.js")
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 (fs5.existsSync(candidate)) {
6443
- return fs5.readFileSync(candidate);
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 resolveNavItems(resolvedDocsDir, nav, indexPath) {
6659
+ function resolveNav(resolvedDocsDir, nav, indexPath) {
6458
6660
  if (nav && nav !== "auto" && Array.isArray(nav)) {
6459
- return navConfigToNavItems(nav, resolvedDocsDir);
6661
+ return {
6662
+ items: navConfigToNavItems(nav, resolvedDocsDir),
6663
+ sidebarConfig: nav
6664
+ };
6460
6665
  }
6461
- return generateAutoNav(resolvedDocsDir, indexPath);
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 = path7.resolve(docsDir);
6466
- const navItems = resolveNavItems(resolvedDocsDir, nav, indexPath);
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 = path7.join(resolvedDocsDir, navItem.path);
6472
- const markdownContent = fs6.readFileSync(filePath, "utf-8");
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 = path7.dirname(navItem.path);
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 navWithActive = navItems.map((item) => ({
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: navWithActive,
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
- return this.request("POST", "/api/auto-approval-rules", {
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
- writeNavToConfig(loaded.configPath, entries);
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
- if (subCmd === "set") {
8568
- await handlePasswordSet(args2.slice(2));
8569
- } else {
8570
- console.error("Usage: nrdocs password set <owner/repo> [--from-stdin]");
8571
- process.exitCode = 1;
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 Set or rotate 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.8",
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
- "mermaid": "^11.6.0",
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
  },