radiant-docs-validator 0.1.3 → 0.1.4
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/index.d.ts +15 -4
- package/dist/index.js +110 -24
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -163,11 +163,11 @@ type Footer = {
|
|
|
163
163
|
socials?: Partial<Record<SocialPlatform, string>>;
|
|
164
164
|
links?: FooterLink[];
|
|
165
165
|
};
|
|
166
|
-
type
|
|
166
|
+
type DocsHrefResolution = {
|
|
167
167
|
kind: "ignored";
|
|
168
168
|
} | {
|
|
169
169
|
kind: "invalid";
|
|
170
|
-
reason: "not-root-absolute" | "generated-route" | "empty-target";
|
|
170
|
+
reason: "not-root-absolute" | "generated-route" | "empty-target" | "path-traversal";
|
|
171
171
|
href: string;
|
|
172
172
|
pathname: string;
|
|
173
173
|
suffix: string;
|
|
@@ -179,10 +179,21 @@ type DocsPageHrefResolution = {
|
|
|
179
179
|
pathname: string;
|
|
180
180
|
suffix: string;
|
|
181
181
|
filePath: string;
|
|
182
|
+
} | {
|
|
183
|
+
kind: "local-asset";
|
|
184
|
+
href: string;
|
|
185
|
+
pathname: string;
|
|
186
|
+
suffix: string;
|
|
187
|
+
filePath: string;
|
|
188
|
+
extension: string;
|
|
189
|
+
publishable: boolean;
|
|
182
190
|
};
|
|
183
191
|
declare function loadOpenApiSpec(filePathOrUrl: string): Promise<any>;
|
|
184
192
|
declare function getConfig(): Promise<DocsConfig>;
|
|
185
|
-
declare
|
|
193
|
+
declare const PUBLISHABLE_STATIC_ASSET_EXTENSIONS: readonly [".avif", ".csv", ".doc", ".docx", ".gif", ".ico", ".jpeg", ".jpg", ".mp3", ".mp4", ".ogg", ".pdf", ".png", ".ppt", ".pptx", ".svg", ".tsv", ".txt", ".wav", ".webm", ".webp", ".xls", ".xlsx", ".zip"];
|
|
194
|
+
declare function isPublishableStaticAssetPath(filePath: string): boolean;
|
|
195
|
+
declare function resolveDocsHref(href: string): DocsHrefResolution;
|
|
196
|
+
declare const resolveDocsPageHref: typeof resolveDocsHref;
|
|
186
197
|
declare function validateMdxContent(): Promise<void>;
|
|
187
198
|
|
|
188
|
-
export { type AssistantButtonConfig, type AssistantButtonSize, type AssistantConfig, type AssistantIcon, type AssistantNavbarButtonConfig, BASE_COLOR_OPTIONS, type BaseColorByMode, type BaseColorOption, type CardButtonTheme, type CardCoverTheme, type CardTheme, type CodeSyntaxThemeConfig, type CodeTheme, DEFAULT_THEME_COLOR_DARK, DEFAULT_THEME_COLOR_LIGHT, type DocsConfig, type
|
|
199
|
+
export { type AssistantButtonConfig, type AssistantButtonSize, type AssistantConfig, type AssistantIcon, type AssistantNavbarButtonConfig, BASE_COLOR_OPTIONS, type BaseColorByMode, type BaseColorOption, type CardButtonTheme, type CardCoverTheme, type CardTheme, type CodeSyntaxThemeConfig, type CodeTheme, DEFAULT_THEME_COLOR_DARK, DEFAULT_THEME_COLOR_LIGHT, type DocsConfig, type DocsHrefResolution, type DocsTheme, type DocsValidatorOptions, type Footer, type FooterLink, type HiddenPageRoute, type Logo, type LogoVariant, type NavGroup, type NavMenu, type NavMenuItem, type NavOpenApi, type NavOpenApiPage, type NavOpenApiPageRef, type NavPage, type NavTag, type NavbarItem, type NavigationItem, PUBLISHABLE_STATIC_ASSET_EXTENSIONS, type SocialPlatform, type TagTheme, type ThemeColorByMode, configureDocsValidator, getConfig, isPublishableStaticAssetPath, loadOpenApiSpec, resolveDocsHref, resolveDocsPageHref, validateMdxContent };
|
package/dist/index.js
CHANGED
|
@@ -289,14 +289,19 @@ function validateFileExistence(filePath, currentPath) {
|
|
|
289
289
|
);
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
|
-
function
|
|
292
|
+
function normalizeDocsRootFilePath(value) {
|
|
293
293
|
let normalized = value.replace(/\\/g, "/").trim();
|
|
294
294
|
if (!normalized) return "";
|
|
295
295
|
normalized = normalized.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
296
|
-
normalized = normalized.replace(/\.(md|mdx)$/i, "");
|
|
297
296
|
const posixNormalized = path.posix.normalize(`/${normalized}`).replace(/^\/+/, "");
|
|
298
297
|
return posixNormalized === "." ? "" : posixNormalized;
|
|
299
298
|
}
|
|
299
|
+
function normalizeDocsFilePath(value) {
|
|
300
|
+
return normalizeDocsRootFilePath(value).replace(/\.(md|mdx)$/i, "");
|
|
301
|
+
}
|
|
302
|
+
function containsPathTraversal(value) {
|
|
303
|
+
return value.replace(/\\/g, "/").split("/").includes("..");
|
|
304
|
+
}
|
|
300
305
|
function normalizeDocsPagePath(value, currentPath, label = "Page path") {
|
|
301
306
|
checkType(value, "string", currentPath, label);
|
|
302
307
|
const trimmedPath = value.trim();
|
|
@@ -1986,6 +1991,35 @@ function validateComponentUsage(content) {
|
|
|
1986
1991
|
var EXTERNAL_PROTOCOL_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
|
|
1987
1992
|
var DOCS_PAGE_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".mdx"]);
|
|
1988
1993
|
var DOCS_PAGE_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".htm", ".html"]);
|
|
1994
|
+
var PUBLISHABLE_STATIC_ASSET_EXTENSIONS = [
|
|
1995
|
+
".avif",
|
|
1996
|
+
".csv",
|
|
1997
|
+
".doc",
|
|
1998
|
+
".docx",
|
|
1999
|
+
".gif",
|
|
2000
|
+
".ico",
|
|
2001
|
+
".jpeg",
|
|
2002
|
+
".jpg",
|
|
2003
|
+
".mp3",
|
|
2004
|
+
".mp4",
|
|
2005
|
+
".ogg",
|
|
2006
|
+
".pdf",
|
|
2007
|
+
".png",
|
|
2008
|
+
".ppt",
|
|
2009
|
+
".pptx",
|
|
2010
|
+
".svg",
|
|
2011
|
+
".tsv",
|
|
2012
|
+
".txt",
|
|
2013
|
+
".wav",
|
|
2014
|
+
".webm",
|
|
2015
|
+
".webp",
|
|
2016
|
+
".xls",
|
|
2017
|
+
".xlsx",
|
|
2018
|
+
".zip"
|
|
2019
|
+
];
|
|
2020
|
+
var PUBLISHABLE_STATIC_ASSET_EXTENSION_SET = new Set(
|
|
2021
|
+
PUBLISHABLE_STATIC_ASSET_EXTENSIONS
|
|
2022
|
+
);
|
|
1989
2023
|
function isIgnoredHref(pathname) {
|
|
1990
2024
|
if (!pathname) return true;
|
|
1991
2025
|
if (pathname.startsWith("#")) return true;
|
|
@@ -1996,54 +2030,67 @@ function isIgnoredHref(pathname) {
|
|
|
1996
2030
|
function getHrefExtension(pathname) {
|
|
1997
2031
|
return path.posix.extname(pathname.replace(/\\/g, "/")).toLowerCase();
|
|
1998
2032
|
}
|
|
1999
|
-
function
|
|
2000
|
-
|
|
2001
|
-
const extension = getHrefExtension(pathname);
|
|
2002
|
-
if (!extension) return true;
|
|
2003
|
-
if (DOCS_PAGE_SOURCE_EXTENSIONS.has(extension)) return true;
|
|
2004
|
-
return DOCS_PAGE_ROUTE_EXTENSIONS.has(extension);
|
|
2033
|
+
function isPublishableStaticAssetPath(filePath) {
|
|
2034
|
+
return PUBLISHABLE_STATIC_ASSET_EXTENSION_SET.has(getHrefExtension(filePath));
|
|
2005
2035
|
}
|
|
2006
|
-
function
|
|
2036
|
+
function resolveDocsHref(href) {
|
|
2007
2037
|
const { pathname, suffix } = splitHrefPathAndSuffix(href);
|
|
2008
2038
|
const baseHref = pathname.trim();
|
|
2009
|
-
if (
|
|
2039
|
+
if (isIgnoredHref(baseHref)) {
|
|
2010
2040
|
return { kind: "ignored" };
|
|
2011
2041
|
}
|
|
2012
|
-
const
|
|
2042
|
+
const normalizedRootFilePath = normalizeDocsRootFilePath(baseHref);
|
|
2013
2043
|
const baseResult = {
|
|
2014
2044
|
href,
|
|
2015
2045
|
pathname: baseHref,
|
|
2016
2046
|
suffix,
|
|
2017
|
-
filePath
|
|
2047
|
+
filePath: normalizedRootFilePath
|
|
2018
2048
|
};
|
|
2049
|
+
if (containsPathTraversal(baseHref)) {
|
|
2050
|
+
return {
|
|
2051
|
+
kind: "invalid",
|
|
2052
|
+
reason: "path-traversal",
|
|
2053
|
+
...baseResult
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2019
2056
|
if (!baseHref.startsWith("/")) {
|
|
2020
2057
|
return {
|
|
2021
2058
|
kind: "invalid",
|
|
2022
2059
|
reason: "not-root-absolute",
|
|
2023
2060
|
...baseResult,
|
|
2024
|
-
suggestedHref:
|
|
2061
|
+
suggestedHref: normalizedRootFilePath ? `/${normalizedRootFilePath}` : void 0
|
|
2025
2062
|
};
|
|
2026
2063
|
}
|
|
2027
|
-
|
|
2028
|
-
if (extension && !DOCS_PAGE_SOURCE_EXTENSIONS.has(extension)) {
|
|
2064
|
+
if (!normalizedRootFilePath) {
|
|
2029
2065
|
return {
|
|
2030
2066
|
kind: "invalid",
|
|
2031
|
-
reason: "
|
|
2067
|
+
reason: "empty-target",
|
|
2032
2068
|
...baseResult
|
|
2033
2069
|
};
|
|
2034
2070
|
}
|
|
2035
|
-
|
|
2071
|
+
const extension = getHrefExtension(baseHref);
|
|
2072
|
+
if (DOCS_PAGE_ROUTE_EXTENSIONS.has(extension)) {
|
|
2036
2073
|
return {
|
|
2037
2074
|
kind: "invalid",
|
|
2038
|
-
reason: "
|
|
2075
|
+
reason: "generated-route",
|
|
2039
2076
|
...baseResult
|
|
2040
2077
|
};
|
|
2041
2078
|
}
|
|
2079
|
+
if (!extension || DOCS_PAGE_SOURCE_EXTENSIONS.has(extension)) {
|
|
2080
|
+
return {
|
|
2081
|
+
kind: "docs-page",
|
|
2082
|
+
...baseResult,
|
|
2083
|
+
filePath: normalizeDocsFilePath(baseHref)
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2042
2086
|
return {
|
|
2043
|
-
kind: "
|
|
2044
|
-
...baseResult
|
|
2087
|
+
kind: "local-asset",
|
|
2088
|
+
...baseResult,
|
|
2089
|
+
extension,
|
|
2090
|
+
publishable: isPublishableStaticAssetPath(normalizedRootFilePath)
|
|
2045
2091
|
};
|
|
2046
2092
|
}
|
|
2093
|
+
var resolveDocsPageHref = resolveDocsHref;
|
|
2047
2094
|
function addRoutableDocsFilePath(routableFilePaths, filePath) {
|
|
2048
2095
|
if (typeof filePath !== "string") return;
|
|
2049
2096
|
const normalizedFilePath = normalizeDocsFilePath(filePath);
|
|
@@ -2104,7 +2151,7 @@ function buildDocsLinkIndex(config, files) {
|
|
|
2104
2151
|
};
|
|
2105
2152
|
}
|
|
2106
2153
|
function validateDocsRootAbsoluteHref(args) {
|
|
2107
|
-
const resolvedHref =
|
|
2154
|
+
const resolvedHref = resolveDocsHref(args.href);
|
|
2108
2155
|
if (resolvedHref.kind === "ignored") return;
|
|
2109
2156
|
if (resolvedHref.kind === "invalid") {
|
|
2110
2157
|
if (resolvedHref.reason === "not-root-absolute") {
|
|
@@ -2118,10 +2165,34 @@ function validateDocsRootAbsoluteHref(args) {
|
|
|
2118
2165
|
`Invalid internal link "${args.href}" in ${args.sourceFile}. Docs page links must point to MDX source paths from the docs root, not generated HTML routes. Use a path like "/guides/quickstart" or "/guides/quickstart.mdx".`
|
|
2119
2166
|
);
|
|
2120
2167
|
}
|
|
2168
|
+
if (resolvedHref.reason === "path-traversal") {
|
|
2169
|
+
throw new Error(
|
|
2170
|
+
`Invalid local URL "${args.href}" in ${args.sourceFile}. Local URLs cannot contain ".." path traversal segments.`
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
2121
2173
|
throw new Error(
|
|
2122
2174
|
`Invalid internal link "${args.href}" in ${args.sourceFile}. Docs page links must point to a docs page file from the docs root, such as "/guides/quickstart".`
|
|
2123
2175
|
);
|
|
2124
2176
|
}
|
|
2177
|
+
if (resolvedHref.kind === "local-asset") {
|
|
2178
|
+
const fullAssetPath = path.join(DOCS_DIR, resolvedHref.filePath);
|
|
2179
|
+
if (!fs.existsSync(fullAssetPath) || !fs.statSync(fullAssetPath).isFile()) {
|
|
2180
|
+
throw new Error(
|
|
2181
|
+
`Invalid local asset "${args.href}" in ${args.sourceFile}. No matching file was found. Expected "${resolvedHref.filePath}" under the docs root.`
|
|
2182
|
+
);
|
|
2183
|
+
}
|
|
2184
|
+
if (!resolvedHref.publishable) {
|
|
2185
|
+
throw new Error(
|
|
2186
|
+
`Invalid local asset "${args.href}" in ${args.sourceFile}. Files with extension "${resolvedHref.extension}" are not published as docs assets.`
|
|
2187
|
+
);
|
|
2188
|
+
}
|
|
2189
|
+
return;
|
|
2190
|
+
}
|
|
2191
|
+
if (args.expectedTarget === "asset") {
|
|
2192
|
+
throw new Error(
|
|
2193
|
+
`Invalid local asset "${args.href}" in ${args.sourceFile}. Asset references must include a supported file extension.`
|
|
2194
|
+
);
|
|
2195
|
+
}
|
|
2125
2196
|
if (args.linkIndex.routableDocsFilePaths.has(resolvedHref.filePath)) {
|
|
2126
2197
|
return;
|
|
2127
2198
|
}
|
|
@@ -2150,7 +2221,16 @@ function createInternalLinkValidationPlugin(args) {
|
|
|
2150
2221
|
validateDocsRootAbsoluteHref({
|
|
2151
2222
|
href: node.url,
|
|
2152
2223
|
sourceFile: args.sourceFile,
|
|
2153
|
-
linkIndex: args.linkIndex
|
|
2224
|
+
linkIndex: args.linkIndex,
|
|
2225
|
+
expectedTarget: "link"
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2228
|
+
if (node.type === "image" && typeof node.url === "string") {
|
|
2229
|
+
validateDocsRootAbsoluteHref({
|
|
2230
|
+
href: node.url,
|
|
2231
|
+
sourceFile: args.sourceFile,
|
|
2232
|
+
linkIndex: args.linkIndex,
|
|
2233
|
+
expectedTarget: "asset"
|
|
2154
2234
|
});
|
|
2155
2235
|
}
|
|
2156
2236
|
if (node.type !== "mdxJsxFlowElement" && node.type !== "mdxJsxTextElement") {
|
|
@@ -2161,12 +2241,15 @@ function createInternalLinkValidationPlugin(args) {
|
|
|
2161
2241
|
for (const attribute of element.attributes) {
|
|
2162
2242
|
const hrefAttribute = attribute;
|
|
2163
2243
|
if (hrefAttribute.type !== "mdxJsxAttribute") continue;
|
|
2164
|
-
if (hrefAttribute.name !== "href")
|
|
2244
|
+
if (hrefAttribute.name !== "href" && hrefAttribute.name !== "src") {
|
|
2245
|
+
continue;
|
|
2246
|
+
}
|
|
2165
2247
|
if (typeof hrefAttribute.value !== "string") continue;
|
|
2166
2248
|
validateDocsRootAbsoluteHref({
|
|
2167
2249
|
href: hrefAttribute.value,
|
|
2168
2250
|
sourceFile: args.sourceFile,
|
|
2169
|
-
linkIndex: args.linkIndex
|
|
2251
|
+
linkIndex: args.linkIndex,
|
|
2252
|
+
expectedTarget: hrefAttribute.name === "src" ? "asset" : "link"
|
|
2170
2253
|
});
|
|
2171
2254
|
}
|
|
2172
2255
|
});
|
|
@@ -2232,12 +2315,15 @@ export {
|
|
|
2232
2315
|
DEFAULT_SHIKI_LIGHT_THEME,
|
|
2233
2316
|
DEFAULT_THEME_COLOR_DARK,
|
|
2234
2317
|
DEFAULT_THEME_COLOR_LIGHT,
|
|
2318
|
+
PUBLISHABLE_STATIC_ASSET_EXTENSIONS,
|
|
2235
2319
|
SHIKI_BUNDLED_THEME_NAMES,
|
|
2236
2320
|
configureDocsValidator,
|
|
2237
2321
|
docsSchema,
|
|
2238
2322
|
getConfig,
|
|
2239
2323
|
isBundledShikiThemeName,
|
|
2324
|
+
isPublishableStaticAssetPath,
|
|
2240
2325
|
loadOpenApiSpec,
|
|
2326
|
+
resolveDocsHref,
|
|
2241
2327
|
resolveDocsPageHref,
|
|
2242
2328
|
validateMdxContent
|
|
2243
2329
|
};
|