File Discovery
These utilities find the infrastructure files in a project and resolve the dependencies between entities.
File Discovery
Section titled “File Discovery”findInfraFiles
Section titled “findInfraFiles”Recursively scans a directory for TypeScript infrastructure files:
import { findInfraFiles } from "@intentius/chant";
const files = await findInfraFiles("./src");// ["/project/src/resources.ts", "/project/src/config.ts"]The function:
- Returns absolute paths to
.tsfiles - Excludes test files (
*.test.ts,*.spec.ts) - Excludes files whose head carries chant’s generated-file marker or the skip marker
- Walks the tree under the rules every discovery walk shares, below
One walk for every command
Section titled “One walk for every command”Source discovery (findInfraFiles, behind build, graph, list and explain), chant lint, component discovery, Op discovery and chant audit all walk the tree the same way (#2527). Each keeps its own file filter (.ts, *.component.ts, *.op.ts, or every file for the audit), and the walk decides which directories and files reach that filter:
| Rule | What it does |
|---|---|
node_modules and .git | never entered |
| Dot-directories | skipped, such as .cache/ or a tool’s staging copy of the project. chant audit still reads .github/ and .forgejo/, and records .terraform/ for TF023 without entering it |
dist | skipped |
| Git-ignored files | skipped, the way git decides: nested .gitignore files, .git/info/exclude and the global excludes file all count. Only paths below the directory being scanned are asked about, so building or linting a directory that is itself ignored still reads it. chant audit leaves Terraform state paths to TF023, which reads .gitignore itself |
| Child projects | when the scanned directory is inside a project, a subdirectory holding a project config is not entered. A chant.config.json holding only lint keys is not a project config and does not count; a chant.config.ts always does. When the scanned directory is outside every project, every child project is read |
| Op discovery | the Op root is still found by walking up from the working directory, and the walk down from it always stops at child projects, so a project with no config of its own no longer sees its siblings’ Ops. The project’s config is read for its globs only when some *.op.ts file sits where an include could reach it, so a project with no Op file has no config evaluated and no tsx loaded for Op discovery (#3976) |
exclude and include | the project’s globs. exclude drops what it names, and include re-admits what exclude or any rule above would skip, apart from node_modules and .git |
| Workspace members and example groups | each member’s directory and each directory an example group matches leave the root project’s walk, and only when a workspace declaration exists. Without one, discovery loads no workspace code. A declaration that fails to read leaves the walk unchanged, and chant workspace ls reports its errors |
Up to v0.80.0 the walks disagreed about every rule in this table apart from node_modules, and each walk decided child projects in its own way. Source and component discovery read the first child project they met as the project’s own source and skipped every later one. v0.80.0 printed a warning on stderr for each file whose status this changed, naming the glob that keeps the old behaviour. To keep reading a directory the walk now skips, add it to include:
{ "include": ["src"] }The change is on the level-0 exception list.
importModule
Section titled “importModule”Dynamically imports a TypeScript module and returns its exports:
import { importModule } from "@intentius/chant";
const exports = await importModule("./src/resources.ts");On import failure, throws a DiscoveryError with type "import".
collectEntities
Section titled “collectEntities”Collects all Declarable entities from imported modules:
import { collectEntities } from "@intentius/chant";
const entities = collectEntities(modules);// Map { "MyResource" => {...}, "MyFunction" => {...} }Dependency Analysis
Section titled “Dependency Analysis”detectCycles
Section titled “detectCycles”Detects circular dependencies in an entity graph using DFS:
import { detectCycles } from "@intentius/chant";
const cycles = detectCycles(graph);topologicalSort
Section titled “topologicalSort”Orders entities so dependencies appear before dependents:
import { topologicalSort } from "@intentius/chant";
const order = topologicalSort(deps);// ["Database", "Cache", "App"]buildDependencyGraph
Section titled “buildDependencyGraph”Builds a dependency graph from entity relationships, detecting dependencies from AttrRef references and direct Declarable property values.
Reference Resolution
Section titled “Reference Resolution”resolveAttrRefs
Section titled “resolveAttrRefs”Resolves all AttrRef instances by setting their logical names:
import { resolveAttrRefs } from "@intentius/chant";
resolveAttrRefs(entities);This enables AttrRef.toJSON() to work correctly during serialization.
Complete Discovery Pipeline
Section titled “Complete Discovery Pipeline”discover
Section titled “discover”The main discovery pipeline that orchestrates all steps:
import { discover } from "@intentius/chant";
const result = await discover("./src/infra");// result.entities: Map of entity name to Declarable// result.dependencies: Map of entity to dependency set// result.sourceFiles: Array of discovered file paths// result.errors: Array of any errors encounteredThe pipeline runs these steps in sequence:
- File scan — find all infrastructure TypeScript files
- Import — dynamically load modules
- Collect — extract declarable entities from exports
- Resolve — set logical names and resolve AttrRefs
- Graph — build dependency graph
Error Handling
Section titled “Error Handling”Discovery operations can throw DiscoveryError with different types:
"import"— module loading failed"resolution"— entity name conflicts or resolution issues"circular"— circular dependencies detected