Skip to content

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.

architecture.config.mjs
/** @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.

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.

  • 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 @type JSDoc 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.

architecture migrate reads the module and writes the same policy as architecture.yaml, schema header included:

Terminal window
pnpm exec architecture migrate

It 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.