Skip to content

Plugin Docs: Add filesystem validation rules#2476

Open
sunker wants to merge 12 commits intomainfrom
plugin-docs/fs-validation-rules
Open

Plugin Docs: Add filesystem validation rules#2476
sunker wants to merge 12 commits intomainfrom
plugin-docs/fs-validation-rules

Conversation

@sunker
Copy link
Contributor

@sunker sunker commented Feb 24, 2026

What this PR does / why we need it:
This PR adds six filesystem validation rules.

  • `no-spaces-in-names` — spaces in file or folder names break URL slugs
  • `valid-file-naming` — file and folder names should only use `[a-z0-9-]` for clean slugs
  • `nested-dir-has-index` — subdirectories without `index.md` appear as unnamed categories
  • `no-empty-directories` — directories with zero allowed files (md or images) serve no purpose
  • `no-symlinks` — symlinks are not allowed in the docs folder
  • `allowed-file-types` — only `.md` and permitted image formats (png, jpg, jpeg, webp, gif) are allowed

A strict mode (`--strict` flag, on by default) controls whether certain rules report as errors or warnings. Serve-time validation with `strict: false` is planned in a follow-up PR.

It also adds `formatResult` for human-readable CLI output and a `--json` flag for machine-readable output, and syncs the build command's copy filter with the `allowed-file-types` rule.

Which issue(s) this PR fixes:

Part of https://github.com/issues/assigned?issue=grafana%7Cgrafana-catalog-team%7C769

Special notes for your reviewer:

@github-actions
Copy link
Contributor

github-actions bot commented Feb 24, 2026

Hello! 👋 This repository uses Auto for releasing packages using PR labels.

✨ This PR can be merged. It will not be considered when calculating future versions of the npm packages and will not appear in the changelogs.

@grafana-plugins-platform-bot grafana-plugins-platform-bot bot moved this from 📬 Triage to 🔬 In review in Grafana Catalog Team Feb 24, 2026
@sunker sunker self-assigned this Feb 24, 2026
@sunker sunker added the no-changelog Don't include in changelog and version calculations label Feb 24, 2026
@sunker sunker marked this pull request as ready for review February 24, 2026 09:15
@sunker sunker requested a review from a team as a code owner February 24, 2026 09:15
@sunker sunker requested review from Copilot and oshirohugo February 24, 2026 09:15
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds four filesystem validation rules to the plugin-docs-cli tool to ensure documentation follows proper naming and structure conventions. The rules check for proper file naming, directory structure, and prevent common issues that could break URL slugs or result in unnamed categories in the documentation.

Changes:

  • Added four new validation rules: no-spaces-in-names, valid-file-naming, nested-dir-has-index, and no-empty-directories
  • Implemented a strict mode flag (--strict, on by default) that controls whether certain rules report as errors or warnings
  • Added comprehensive test coverage for all new rules and strict/non-strict modes

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/plugin-docs-cli/src/validation/types.ts Added strict boolean field to ValidationInput interface
packages/plugin-docs-cli/src/validation/rules/filesystem.ts Implemented four new filesystem validation rules with strict mode support
packages/plugin-docs-cli/src/validation/rules/filesystem.test.ts Added comprehensive test coverage for all new validation rules
packages/plugin-docs-cli/src/validation/engine.test.ts Updated test fixture to include strict field
packages/plugin-docs-cli/src/commands/validate.command.ts Added strict mode option parameter to validateCommand
packages/plugin-docs-cli/src/bin/run.ts Added command-line argument parsing for --strict flag
Comments suppressed due to low confidence (2)

packages/plugin-docs-cli/src/validation/rules/filesystem.ts:61

  • The asset directory check only examines the directory name, not its path. This means any directory named 'img' at any nesting level will be excluded from validation, even if it's in a subdirectory like 'docs/subdir/img'. Consider whether this is intentional behavior or if asset directories should only be excluded at the root level. If root-level only is intended, check if the directory's parentPath equals input.docsPath.
    // asset directories like img/ are expected to have no markdown
    if (ASSET_DIRS.has(dir.name)) {
      continue;
    }

packages/plugin-docs-cli/src/validation/rules/filesystem.test.ts:56

  • The comment states "img/ has no .md files at all - caught by no-empty-directories instead" but this is incorrect. Since 'img' is in ASSET_DIRS, it's skipped entirely by the continue statement at line 60 in filesystem.ts, so it won't be caught by no-empty-directories either. The comment should reflect that asset directories are excluded from all validation checks, not just nested-dir-has-index.
    // img/ has no .md files at all - caught by no-empty-directories instead
    const finding = findings.find((f) => f.rule === 'nested-dir-has-index' && f.file?.endsWith('img'));

@sunker sunker requested review from academo and mckn February 24, 2026 09:21
recursive: true,
filter: (src) => {
const ext = extname(src).toLowerCase();
return ext === '' || ALLOWED_EXTENSIONS.has(ext);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed before @academo, copy inside the build cmd is now aligned with allowed file types rule

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! just a nit: put these in common constants (not only inside the file system rule) maybe we'll be using it in other places.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I feel like filesystem.ts is already to right home for the ALLOWED_EXTENSIONS const.

const result = await validate({ docsPath, strict: options.strict }, allRules);

console.log(JSON.stringify(result, null, 2));
if (options.json) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not for this PR but good to open an issue: make an option to output json to a file. sometimes is much easier to read an output file than stdout json output.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative is to pipe the output to a file if that would be a good enough solution?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes piping could work for now! Added an issue so we can track this: https://github.com/grafana/grafana-catalog-team/issues/782

const findings = await checkFilesystem({ docsPath: testDocsPath, strict: true });

// img/ has no .md files at all - caught by no-empty-directories instead
const finding = findings.find((f) => f.rule === 'nested-dir-has-index' && f.file?.endsWith('img'));
Copy link
Contributor

@academo academo Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sort of a nit but important to consider: you are trying to create a single set of fixtures that will let you test all situations in a single go. but it creates this test complexity where you now have extract logic for your assertions.

e.g. in this case if you had a dedicated fixture for this case you would save the possible trouble of the fixture changing in the future generating a similar nested-dir-has-index (unlikely I know but consider other cases) in an img directory

just imagine you want to test this case where the directory is in fact called img but it has a md file inside.

my recommendation is to not shy away to create several fixtures for specific situations. This is also how the plugin-validator does it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I think this is a good point. I'll remove this and another three tests

Copy link
Collaborator

@mckn mckn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: regarding the description of this PR. It says something like: "the serve command will use non-strict mode so developers aren't blocked during local previews." But when viewing the code I can't see that the serve command invoke the validate parts at all, right?

// docsPath doesn't exist or isn't readable
}

const mdFiles = entries.filter((e) => e.isFile() && e.name.endsWith('.md'));
Copy link
Contributor

@academo academo Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: even though probably ok this and the next 3 lines can be done a single for-each loop or reduce loop.

another possible way of doing this is loop the entries and for each entry call checkSymlink, checkDirectory, etc... and those functions can return reports you append into your report. so the logic is not spread around like this

just a different style. this also works so I leave it up to you to change it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like readability overrules perf here, so if ok with you I'll keep the four separate loops.

const dirPath = join(dir.parentPath, dir.name);

// asset directories like img/ are expected to have no markdown
if (ASSET_DIRS.has(dir.name)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have a specific good reason to be opinionated on how the assets folders are called? what if I like to use images instead of img? or what if I just creates assets/img and so on.

do we have a strong valid reason to force people into a folder structure like this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reasoning behind this is that IF we ever decide to pull out the images from the archive before sending it to GCS, having them in a predefined folder from the start will make this a lot easier. It doesn't cost much from I DX perspective, so I think this is worth it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can pull images by walking a dir and fetching mime/types. we don't need to enforce any specific dir structure for that.

@sunker
Copy link
Contributor Author

sunker commented Feb 25, 2026

Thanks for feedback @academo @mckn! Think this one is ready for another round now.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-changelog Don't include in changelog and version calculations

Projects

Status: 🔬 In review

Development

Successfully merging this pull request may close these issues.

4 participants