scout_router 0.1.0 copy "scout_router: ^0.1.0" to clipboard
scout_router: ^0.1.0 copied to clipboard

Scout Router — statically analyse your go_router configuration and emit a structured route manifest (YAML, JSON, or Markdown) at compile time. Part of the Scout Application Capability Intelligence Pla [...]

scout_router #

Part of the Scout Application Capability Intelligence Platform

A build_runner plugin that statically analyses your go_router configuration and emits a structured route manifest at compile time — no reflection, zero runtime overhead.

Run dart run build_runner build and get a route_map.yaml (or .json or .md) that shows every route, its access level, path/query parameters, and the Dart type of state.extra — all resolved from source, no app execution needed.


Why #

go_router gives you a declarative routing tree at runtime, but there is no built-in way to inspect that tree statically — for documentation, security audits, or generating navigation helpers. This plugin fills that gap.

Typical uses:

  • Security review: confirm every sensitive route is behind the correct guard.
  • Documentation: commit a route_map.md that reviewers can read without running the app.
  • Onboarding: new team members can see the entire navigation graph in one file.
  • Navigation codegen: downstream tools can read route_map.json to generate typed navigator wrappers.

Quick start #

1. Add to dev_dependencies #

# pubspec.yaml
dev_dependencies:
  scout_router: ^0.1.0
  build_runner: ^2.4.0

2. Configure build.yaml #

# build.yaml
targets:
  $default:
    builders:
      scout_router:
        options:
          router_file: lib/router.dart   # path to your GoRouter file
          router_class: AppRouter        # class containing GoRoute definitions
          output_format: yaml            # yaml | json | markdown

3. Run #

dart run build_runner build
# or watch mode:
dart run build_runner watch

Output: route_map.yaml (or .json / .md) in your package root.


Access levels #

The inspector can tag each route with an access-level label derived from static list membership or path prefixes — fully configurable, not hardcoded to any naming convention.

# build.yaml
options:
  access_levels:
    # Map a static List<String> field name in [router_class] → access label.
    publicRoutes: public
    adminRoutes: admin
  path_prefix_rules:
    # Fallback: match by path prefix → access label.
    /admin: admin
  default_access: authenticated   # used when no rule matches

In your router class, just maintain the lists you already have:

class AppRouter {
  static const publicRoutes  = ['/login', '/register', '/'];
  static const adminRoutes   = ['/admin', '/admin/users'];
}

Any naming scheme works — map your field names to labels in build.yaml.


Output formats #

YAML (default) #

routes:
  - path: /
    name: home
    access: public
  - path: /products/:id
    access: authenticated
    pathParams: [id]
  - path: /checkout
    access: authenticated
    queryParams: [coupon]
    extra:
      type: CheckoutArgs
      fields:
        - cartId: String
        - expressShipping: bool
  - path: /admin
    access: admin
    children:
      - path: /admin/users
        access: admin
        queryParams: [page, q]

JSON #

{
  "routes": [
    {
      "path": "/",
      "access": "public",
      "name": "home"
    },
    {
      "path": "/checkout",
      "access": "authenticated",
      "queryParams": ["coupon"],
      "extra": {
        "type": "CheckoutArgs",
        "fields": [
          { "name": "cartId", "type": "String", "required": true },
          { "name": "expressShipping", "type": "bool", "required": true }
        ]
      }
    }
  ]
}

Markdown #

Produces a document with a route tree, a summary table, and per-access-level route tables — ready to commit alongside your code.

# Route Map

## Summary

| Access Level | Count |
|---|---|
| `public` | 3 |
| `authenticated` | 8 |
| `admin` | 4 |

## Route Tree

├── / [public]
├── /products [public]
│   └── /products/:id [public]
├── /checkout [authenticated]
└── /admin [admin]
    ├── /admin/users [admin]
    └── /admin/orders [admin]

Extra type resolution #

If your builder passes a typed object via state.extra, the inspector resolves it:

GoRoute(
  path: '/checkout',
  builder: (context, state) {
    final args = state.extra as CheckoutArgs;   // ← extracted
    return CheckoutPage(args: args);
  },
)

The output includes the class name and its public fields, scanned from source:

extra:
  type: CheckoutArgs
  fields:
    - cartId: String
    - expressShipping: bool

Point the model_scan_path glob at where your arg classes live:

options:
  model_scan_path: lib/navigation/**/*.dart

ShellRoute and StatefulShellRoute #

Shell routes are transparent wrappers — the inspector recurses into their routes: (or branches:) and promotes the children to the parent level, so they appear at the correct path depth without a spurious wrapper node in the output.


All build.yaml options #

Option Default Description
router_file lib/router.dart Path to the file containing your router class
router_class AppRouter Class name that holds GoRoute definitions
output_format yaml yaml, json, or markdown
output_path (derived from format) Override the output file path
access_levels {} Map of list-field-name → access label
path_prefix_rules {} Map of path prefix → access label
default_access authenticated Label when no rule matches
model_scan_path lib/**/*.dart Glob for scanning extra-type model classes
private_routes [] Paths to exclude from the output

Committing the output #

The generated file is deterministic — commit it to your repo and diff it in PRs. A changed route_map.yaml in a PR is an immediately visible signal that routing or access-level logic changed, making security reviews much easier.


What gets scanned #

Route definitions are discovered from the router_file whether they live in:

  • a List field on the configured router class (static, instance, const, or final);
  • a top-level List variable;
  • a GoRouter(routes: [...]) constructor — inline, in a field, or returned from a getter/function;
  • across files: ...featureARoutes spreads and bare-identifier route lists (GoRouter(routes: appRoutes), routes: childRoutes) are followed by name into any file under model_scan_path. This lets you split the router by feature module:
    // app_router.dart
    final routes = [...authRoutes, ...shopRoutes, ...adminRoutes];
    // features/shop/routes.dart
    final shopRoutes = [GoRoute(path: '/shop', ...)];
    

Limitations #

  • Pure static analysis: dynamic string interpolation in paths (e.g. '/$prefix/detail' where prefix is a runtime variable) cannot be resolved.
  • Cross-file references are resolved by name (parse-only), not through the import graph. Two top-level symbols with the same name in different libraries would collide; in practice route-list names are unique. Full import-aware resolution is a planned enhancement.

Part of Scout #

scout_router is one of the foundation packages of the Scout Application Capability Intelligence Platform. Scout runs scout_router's route analysis as one of three passes to produce scout.manifest.json — a machine-readable capability graph consumed by AI systems, enforced in CI, and maintained at compile time with zero runtime overhead.


License #

MIT

0
likes
130
points
5
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

Scout Router — statically analyse your go_router configuration and emit a structured route manifest (YAML, JSON, or Markdown) at compile time. Part of the Scout Application Capability Intelligence Platform. Zero runtime overhead, no reflection.

Repository (GitHub)
View/report issues

Topics

#scout #go-router #routing #code-generation #build-runner

License

MIT (license)

Dependencies

analyzer, build, glob, path

More

Packages that depend on scout_router