Using a JavaScript manifest
architecture.config.mjs is read too. Its default export — or, failing that, the module
namespace — is the manifest, decoded exactly as a YAML one is, defs, use and include
included: a module may include YAML or JSON files, and an error inside one still names its
line.
/** @type {import("@goodbones/core").Manifest} */export default { resolve: { scopes: [{ files: "", language: "typescript", options: { tsconfig: "tsconfig.json" } }], unresolved: "error", }, tree: { "src/domain/": { message: "domain/ is the model. It reaches nothing but itself.", imports: { message: "domain/ may not reach the framework.", allow: ["src/domain/**"] }, children: { "*.ts": {} }, }, },};It is found by name after the data forms — architecture.yaml, .yml, .json, then
.config.mjs — and only one of the four may be present.
When it is worth it
Section titled “When it is worth it”A manifest generated from other data. A monorepo that derives its package nodes from
pnpm-workspace.yaml, a tree assembled from a table of modules, a policy shared across
repositories and specialised in each: code that produces data belongs in code, and a module
is where it runs.
Most manifests are not that. They are literals — every example on these pages is — and a
literal is better off as data: one file every host reads the same way, an editor that
validates it as it is typed, and an error that names a line. The one code feature a literal
manifest tends to use, a shared constant, is what defs and use
exist for.
What it gives up
Section titled “What it gives up”- A line number. A decode error names the path —
tree["src/"].members[0].subject— and not where it was written. From a data file the same error names the file, line and column. - Editor validation. The
@typeJSDoc above checks keys; the JSON Schema a YAML manifest names checks values and enums as well. - Every other host. Only a Node host can evaluate a module. A host in another language reads the data forms and nothing else.
- Being inert. A data file is read; a module is run — at every load, in oxlint’s language server as much as in CI.
Moving to YAML
Section titled “Moving to YAML”architecture migrate reads the module and writes the same policy as architecture.yaml,
schema header included:
pnpm exec architecture migrateIt decodes the module first and writes nothing if that fails. It hoists nothing into defs,
since which subtrees are worth naming is the author’s call, and it cannot carry comments,
which it says; port the ones worth keeping by hand. Then delete the module: a repository with
two manifests is refused rather than read from whichever comes first. The two forms decode to
the same policy, so architecture check before and after reports the same findings.