create-thally-docs 0.10.106 → 0.10.107

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.
@@ -124,15 +124,37 @@ function operationState(document, method, path, overrides) {
124
124
  const result = visibility(chain, operation, isObj(overrides) ? overrides[`${verb.toUpperCase()} ${path}`] : void 0);
125
125
  return result === "visible" ? "published" : result;
126
126
  }
127
- function webhookState(document, name, overrides) {
127
+ function webhookState(document, name, overrides, method) {
128
128
  if (!isObj(document) || !isObj(document.webhooks) || !Object.hasOwn(document.webhooks, name)) return "unknown";
129
129
  const entry = document.webhooks[name];
130
130
  if (!isObj(entry)) return "unknown";
131
131
  const { chain, item } = viewEntry(document, entry);
132
- const states = METHODS.filter((verb) => isObj(item[verb])).map((verb) => visibility(chain, item[verb], isObj(overrides) ? overrides[`WEBHOOK ${verb.toUpperCase()} ${name}`] : void 0));
132
+ const states = METHODS.filter((verb) => isObj(item[verb]) && (!method || verb === method.toLowerCase())).map((verb) => visibility(chain, item[verb], isObj(overrides) ? overrides[`WEBHOOK ${verb.toUpperCase()} ${name}`] : void 0));
133
133
  if (states.length === 0) return "unknown";
134
134
  return states.includes("visible") ? "published" : states[0];
135
135
  }
136
+ var API_GROUPS_MAX_DEPTH = 32;
137
+ function listedOperationKeys(groups, skipped, depth = 1) {
138
+ if (!Array.isArray(groups)) return [];
139
+ return groups.flatMap((group) => {
140
+ if (!isObj(group) || !Array.isArray(group.pages) || group.group !== void 0 && typeof group.group !== "string") {
141
+ if (skipped) skipped.malformed++;
142
+ return [];
143
+ }
144
+ if (depth > API_GROUPS_MAX_DEPTH) {
145
+ if (skipped) skipped.tooDeep = true;
146
+ return [];
147
+ }
148
+ if (skipped && group.icon !== void 0 && typeof group.icon !== "string") skipped.badIcon++;
149
+ return group.pages.flatMap((page) => typeof page === "string" ? [page] : listedOperationKeys([page], skipped, depth + 1));
150
+ });
151
+ }
152
+ function groupKeyState(document, key, overrides) {
153
+ const match = /^(WEBHOOK )?(\S+) (.+)$/i.exec(key.trim().replace(/\s+/g, " "));
154
+ if (!match) return "unknown";
155
+ const [, webhook, method, path] = match;
156
+ return webhook ? webhookState(document, path, overrides, method) : operationState(document, method, path, overrides);
157
+ }
136
158
 
137
159
  // src/manual-api-check.ts
138
160
  import { unified } from "unified";
@@ -680,20 +702,78 @@ function checkPublicSpecs(projectDir, tabs, issues) {
680
702
  issues.push({ severity: "warning", file: path, message: `${path} is too large to inspect; if it is an OpenAPI file with hidden operations it is served as-is from public/` });
681
703
  }
682
704
  }
705
+ function loadCheckSpec(projectDir, source, overrides) {
706
+ if (/^https?:/i.test(source)) return { source, overrides };
707
+ const specPath = source.startsWith("/") ? join(projectDir, "public", source.slice(1)) : join(projectDir, source);
708
+ try {
709
+ const raw = readFileSync(specPath, "utf8");
710
+ return { source, overrides, document: /\.json$/i.test(source) ? JSON.parse(raw) : parseYaml2(raw) };
711
+ } catch {
712
+ return { source, overrides };
713
+ }
714
+ }
715
+ function checkApiGroupKeys(projectDir, config, issues) {
716
+ const versions = config.navigation?.versions;
717
+ const seen = /* @__PURE__ */ new Set();
718
+ const scopes = [[void 0, config.tabs], ...Object.entries(config.i18n?.navigation ?? {})];
719
+ for (const [locale, tabs] of scopes) {
720
+ for (const tab of Array.isArray(tabs) ? tabs : []) {
721
+ if (!tab || typeof tab !== "object" || seen.has(tab)) continue;
722
+ seen.add(tab);
723
+ checkTabApiGroups(projectDir, tab, locale, versions, issues);
724
+ }
725
+ }
726
+ }
727
+ function checkTabApiGroups(projectDir, tab, locale, versions, issues) {
728
+ const name = `API tab "${tab.tab}"${locale ? ` (locale ${locale})` : ""}`;
729
+ const rendered = !tab.hidden || Boolean(tab.version && Array.isArray(versions) && versions.some((version) => version !== null && typeof version === "object" && version.label === tab.version));
730
+ if (!rendered || typeof tab.api?.source !== "string" || tab.api.navigation === false) return;
731
+ if (tab.api.groups !== void 0 && !Array.isArray(tab.api.groups)) {
732
+ issues.push({
733
+ severity: "warning",
734
+ file: "docs.json",
735
+ message: `${name}: api.groups must be a list of groups, so the tab shows the generated navigation. A list looks like [{ "group": "Name", "pages": ["GET /path"] }]`
736
+ });
737
+ }
738
+ const skipped = { malformed: 0, tooDeep: false, badIcon: 0 };
739
+ const keys = listedOperationKeys(tab.api.groups, skipped);
740
+ if (skipped.malformed > 0) {
741
+ issues.push({
742
+ severity: "warning",
743
+ file: "docs.json",
744
+ message: `${name}: api.groups has ${skipped.malformed} ${skipped.malformed === 1 ? "entry that is" : "entries that are"} not a group with a "pages" list or an operation key. ${skipped.malformed === 1 ? "It is" : "They are"} left out of the sidebar. A group looks like { "group": "Name", "pages": ["GET /path"] }`
745
+ });
746
+ }
747
+ if (skipped.badIcon > 0) {
748
+ issues.push({
749
+ severity: "warning",
750
+ file: "docs.json",
751
+ message: `${name}: api.groups has ${skipped.badIcon} ${skipped.badIcon === 1 ? "group" : "groups"} whose "icon" is not text. The icon is dropped and the group is still listed. Use an icon name or an image path`
752
+ });
753
+ }
754
+ if (skipped.tooDeep) {
755
+ issues.push({
756
+ severity: "warning",
757
+ file: "docs.json",
758
+ message: `${name}: api.groups nests groups deeper than ${API_GROUPS_MAX_DEPTH} levels. Groups beyond that level are left out of the sidebar`
759
+ });
760
+ }
761
+ if (keys.length === 0) return;
762
+ const spec = loadCheckSpec(projectDir, tab.api.source, tab.api.overrides);
763
+ if (!spec.document || typeof spec.document !== "object") return;
764
+ for (const key of keys) {
765
+ const state = groupKeyState(spec.document, key, spec.overrides);
766
+ if (state === "published") continue;
767
+ issues.push({
768
+ severity: "warning",
769
+ file: "docs.json",
770
+ message: `${name}: api.groups lists "${key}", which matches no published operation in ${spec.source}${state === "unknown" ? "" : ` (it is ${state})`}. It is left out of the sidebar. Check the method and path, or publish the operation`
771
+ });
772
+ }
773
+ }
683
774
  function checkUnpublishedOperationPages(projectDir, tabs, pages, issues) {
684
775
  if (pages.length === 0) return;
685
- const specs = tabs.filter((tab) => !tab.hidden && typeof tab.api?.source === "string").map((tab) => {
686
- const source = tab.api.source;
687
- const overrides = tab.api.overrides;
688
- if (/^https?:/i.test(source)) return { source, overrides };
689
- const specPath = source.startsWith("/") ? join(projectDir, "public", source.slice(1)) : join(projectDir, source);
690
- try {
691
- const raw = readFileSync(specPath, "utf8");
692
- return { source, overrides, document: /\.json$/i.test(source) ? JSON.parse(raw) : parseYaml2(raw) };
693
- } catch {
694
- return { source, overrides };
695
- }
696
- });
776
+ const specs = tabs.filter((tab) => !tab.hidden && typeof tab.api?.source === "string").map((tab) => loadCheckSpec(projectDir, tab.api.source, tab.api.overrides));
697
777
  if (specs.length === 0) return;
698
778
  for (const { file, reference } of pages) {
699
779
  const state = pageState(reference, specs);
@@ -915,6 +995,7 @@ async function runCheck(projectDir, options) {
915
995
  }
916
996
  checkPublicSpecs(projectDir, config.tabs, issues);
917
997
  checkUnpublishedOperationPages(projectDir, config.tabs, operationPages, issues);
998
+ checkApiGroupKeys(projectDir, config, issues);
918
999
  if (options.external && externalLinks.length > 0) {
919
1000
  issues.push(...await checkExternalLinks(externalLinks, options.externalLinkOptions));
920
1001
  }
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  parseFrontmatter,
7
7
  runCheck
8
- } from "./chunk-WV3IBK42.js";
8
+ } from "./chunk-3XHWWST2.js";
9
9
  import "./chunk-KDLP27DS.js";
10
10
  import {
11
11
  logo,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCheck
4
- } from "./chunk-WV3IBK42.js";
4
+ } from "./chunk-3XHWWST2.js";
5
5
  import "./chunk-KDLP27DS.js";
6
6
  import "./chunk-J7255UQA.js";
7
7
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-thally-docs",
3
- "version": "0.10.106",
3
+ "version": "0.10.107",
4
4
  "description": "Scaffold an open-source Thally docs site for people, search engines, and AI tools.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -54,7 +54,7 @@
54
54
  "@clack/prompts": "^0.11.0",
55
55
  "@inquirer/prompts": "^7.0.0",
56
56
  "@thallylabs/core": "0.2.16",
57
- "@thallylabs/migrate": "0.2.34",
57
+ "@thallylabs/migrate": "0.2.35",
58
58
  "p-limit": "^6.1.0",
59
59
  "remark-mdx": "^3.1.1",
60
60
  "remark-parse": "^11.0.0",