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
package/dist/migration-worker.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-thally-docs",
|
|
3
|
-
"version": "0.10.
|
|
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.
|
|
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",
|