radiant-docs-validator 0.1.9 → 0.1.11

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 CHANGED
@@ -205,8 +205,14 @@ type RadiantComponentValidationOptions = {
205
205
  sourceFile: string;
206
206
  validateLinkHref?: (href: string) => void;
207
207
  validateAssetHref?: (href: string) => void;
208
+ validateIconHref?: (href: string, context: IconValidationContext) => void;
209
+ };
210
+ type IconValidationContext = {
211
+ componentName: string;
212
+ propName: string;
213
+ sourceFile: string;
208
214
  };
209
215
  declare function validateRadiantComponentProps(componentName: string, props: Record<string, unknown>, options: RadiantComponentValidationOptions): void;
210
216
  declare function validateRadiantComponentNode(node: MdxJsxElementNode, options: RadiantComponentValidationOptions): void;
211
217
 
212
- 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 RadiantComponentValidationOptions, type SocialPlatform, type TagTheme, type ThemeColorByMode, configureDocsValidator, getConfig, isPublishableStaticAssetPath, loadOpenApiSpec, resolveDocsHref, resolveDocsPageHref, validateMdxContent, validateRadiantComponentNode, validateRadiantComponentProps };
218
+ 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 IconValidationContext, 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 RadiantComponentValidationOptions, type SocialPlatform, type TagTheme, type ThemeColorByMode, configureDocsValidator, getConfig, isPublishableStaticAssetPath, loadOpenApiSpec, resolveDocsHref, resolveDocsPageHref, validateMdxContent, validateRadiantComponentNode, validateRadiantComponentProps };
package/dist/index.js CHANGED
@@ -239,6 +239,21 @@ function assertHexColor(componentName, propName, value, sourceFile) {
239
239
  );
240
240
  }
241
241
  }
242
+ function validateIconProp(componentName, propName, value, options) {
243
+ if (typeof value !== "string") return;
244
+ if (value.trim().length === 0) {
245
+ componentError(
246
+ componentName,
247
+ `Invalid prop "${propName}": expected an Iconify name like "lucide:settings", an HTTP(S) URL, or a docs-root absolute local path like "/icons/settings.svg"`,
248
+ options.sourceFile
249
+ );
250
+ }
251
+ options.validateIconHref?.(value, {
252
+ componentName,
253
+ propName,
254
+ sourceFile: options.sourceFile
255
+ });
256
+ }
242
257
  function validateCard(props, options) {
243
258
  const componentName = "Card";
244
259
  assertNoUnknownProps(
@@ -251,6 +266,7 @@ function validateCard(props, options) {
251
266
  assertType(componentName, "title", props.title, ["string"], options.sourceFile);
252
267
  assertType(componentName, "href", props.href, ["string"], options.sourceFile);
253
268
  assertType(componentName, "icon", props.icon, ["string"], options.sourceFile);
269
+ validateIconProp(componentName, "icon", props.icon, options);
254
270
  if (typeof props.href === "string") {
255
271
  options.validateLinkHref?.(props.href);
256
272
  }
@@ -268,6 +284,7 @@ function validateCard(props, options) {
268
284
  assertType("Card.cover", "colors", props.cover.colors, ["array"], options.sourceFile);
269
285
  assertType("Card.cover", "patternSeed", props.cover.patternSeed, ["string"], options.sourceFile);
270
286
  assertType("Card.cover", "colorSeed", props.cover.colorSeed, ["string"], options.sourceFile);
287
+ validateIconProp("Card.cover", "icon", props.cover.icon, options);
271
288
  if (Array.isArray(props.cover.colors)) {
272
289
  if (props.cover.colors.length < 1 || props.cover.colors.length > 4) {
273
290
  componentError(
@@ -408,12 +425,14 @@ function validateRadiantComponentProps(componentName, props, options) {
408
425
  assertRequired("Tab", "label", props.label, options.sourceFile);
409
426
  assertType("Tab", "label", props.label, ["string"], options.sourceFile);
410
427
  assertType("Tab", "icon", props.icon, ["string"], options.sourceFile);
428
+ validateIconProp("Tab", "icon", props.icon, options);
411
429
  return;
412
430
  }
413
431
  if (componentName === "Accordion") {
414
432
  assertRequired("Accordion", "title", props.title, options.sourceFile);
415
433
  assertType("Accordion", "title", props.title, ["string"], options.sourceFile);
416
434
  assertType("Accordion", "icon", props.icon, ["string"], options.sourceFile);
435
+ validateIconProp("Accordion", "icon", props.icon, options);
417
436
  assertType("Accordion", "defaultOpen", props.defaultOpen, ["boolean"], options.sourceFile);
418
437
  assertEnum(
419
438
  "Accordion",
@@ -434,6 +453,7 @@ function validateRadiantComponentProps(componentName, props, options) {
434
453
  );
435
454
  assertType("Callout", "title", props.title, ["string", "boolean"], options.sourceFile);
436
455
  assertType("Callout", "icon", props.icon, ["string", "boolean"], options.sourceFile);
456
+ validateIconProp("Callout", "icon", props.icon, options);
437
457
  assertType("Callout", "accent", props.accent, ["boolean"], options.sourceFile);
438
458
  assertType("Callout", "color", props.color, ["string"], options.sourceFile);
439
459
  if (props.title === true) {
@@ -725,6 +745,22 @@ function validateIcon(icon, currentPath) {
725
745
  );
726
746
  }
727
747
  }
748
+ function validateComponentIcon(icon, currentPath) {
749
+ const trimmedIcon = icon.trim();
750
+ if (trimmedIcon !== icon) {
751
+ throwConfigError(
752
+ "Component icon cannot include leading or trailing whitespace.",
753
+ currentPath
754
+ );
755
+ }
756
+ if (!isUrl(icon) && !icon.startsWith("/") && !icon.includes(":")) {
757
+ throwConfigError(
758
+ `Invalid component icon "${icon}". Component icons must use an Iconify name like "lucide:settings", an HTTP(S) URL, or a docs-root absolute local path like "/icons/settings.svg". Bare icon names are ambiguous.`,
759
+ currentPath
760
+ );
761
+ }
762
+ validateIcon(icon, currentPath);
763
+ }
728
764
  var AVAILABLE_COMPONENTS = [
729
765
  "Callout",
730
766
  "Tabs",
@@ -1309,39 +1345,59 @@ async function validateNavigationNode(item, currentPath, groupDepth = 0) {
1309
1345
  return;
1310
1346
  }
1311
1347
  }
1312
- function getFirstPagePathFromPageItems(items) {
1348
+ function getFirstRouteFromPageItems(items) {
1313
1349
  for (const item of items) {
1314
1350
  if (typeof item === "string") {
1315
- return item;
1351
+ return {
1352
+ type: "mdx",
1353
+ filePath: item
1354
+ };
1316
1355
  }
1317
1356
  if ("page" in item) {
1318
- return item.page;
1357
+ return {
1358
+ type: "mdx",
1359
+ filePath: item.page
1360
+ };
1361
+ }
1362
+ if ("openapi" in item) {
1363
+ return {
1364
+ type: "openapi"
1365
+ };
1319
1366
  }
1320
1367
  if ("group" in item) {
1321
- const nestedPath = getFirstPagePathFromPageItems(item.pages);
1322
- if (nestedPath) {
1323
- return nestedPath;
1368
+ const nestedRoute = getFirstRouteFromPageItems(item.pages);
1369
+ if (nestedRoute) {
1370
+ return nestedRoute;
1324
1371
  }
1325
1372
  }
1326
1373
  }
1327
1374
  return void 0;
1328
1375
  }
1329
- function getFirstPagePathFromNavigation(navigation) {
1376
+ function getFirstRouteFromNavigation(navigation) {
1330
1377
  if (navigation.pages) {
1331
- return getFirstPagePathFromPageItems(navigation.pages);
1378
+ return getFirstRouteFromPageItems(navigation.pages);
1332
1379
  }
1333
1380
  if (navigation.menu) {
1334
1381
  for (const menuItem of navigation.menu.items) {
1335
1382
  const submenuPages = menuItem.submenu.pages;
1336
- if (!submenuPages) {
1337
- continue;
1383
+ if (submenuPages) {
1384
+ const firstRoute = getFirstRouteFromPageItems(submenuPages);
1385
+ if (firstRoute) {
1386
+ return firstRoute;
1387
+ }
1338
1388
  }
1339
- const firstPath = getFirstPagePathFromPageItems(submenuPages);
1340
- if (firstPath) {
1341
- return firstPath;
1389
+ if (menuItem.submenu.openapi) {
1390
+ return {
1391
+ type: "openapi"
1392
+ };
1342
1393
  }
1343
1394
  }
1344
1395
  }
1396
+ if (navigation.openapi) {
1397
+ return {
1398
+ type: "openapi"
1399
+ };
1400
+ }
1345
1401
  return void 0;
1346
1402
  }
1347
1403
  async function validateNavOpenApi(navOpenApi, currentPath) {
@@ -2526,12 +2582,12 @@ async function validateConfig(config) {
2526
2582
  await validateNavigation(config.navigation);
2527
2583
  config.home = validateHome(config.home);
2528
2584
  if (config.home === void 0) {
2529
- const fallbackHome = getFirstPagePathFromNavigation(config.navigation);
2530
- if (fallbackHome) {
2531
- config.home = fallbackHome;
2532
- } else if (!config.navigation.openapi) {
2585
+ const fallbackRoute = getFirstRouteFromNavigation(config.navigation);
2586
+ if (fallbackRoute?.type === "mdx") {
2587
+ config.home = fallbackRoute.filePath;
2588
+ } else if (!fallbackRoute) {
2533
2589
  throwConfigError(
2534
- "Home is undefined and no documentation page exists in navigation to use as fallback.",
2590
+ "Home is undefined and no navigation route exists to use as fallback.",
2535
2591
  ["home"]
2536
2592
  );
2537
2593
  }
@@ -2913,6 +2969,13 @@ function createComponentValidationPlugin(args) {
2913
2969
  linkIndex: args.linkIndex,
2914
2970
  expectedTarget: "asset"
2915
2971
  });
2972
+ },
2973
+ validateIconHref: (href, context) => {
2974
+ validateComponentIcon(href, [
2975
+ context.sourceFile,
2976
+ context.componentName,
2977
+ context.propName
2978
+ ]);
2916
2979
  }
2917
2980
  });
2918
2981
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "radiant-docs-validator",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Shared validation for Radiant documentation repositories",
5
5
  "type": "module",
6
6
  "scripts": {