@conformetry/generation

中文文档
0.0.78发布于 2026/11/10MITTypeScriptES Module构建来源已验证近 30 天下载 9793

Template rendering and file generation runtime for Conformetry

原始使用文档
文档目录12 节
  1. Usage
  2. Hooks
  3. The context
  4. Writing from a hook
  5. The format list
  6. When a hook throws
  7. Rendering
  8. Adapters
  9. Exports
  10. Test
  11. License
  12. 👔 Conformetry

👔 Conformetry Generation

npm

The generation runtime for Conformetry: it walks a template tree, renders every path and every file, and writes the result.

npm install --save-dev @conformetry/generation

Usage

import { GenerationService } from "@conformetry/generation";

const result = await generationService.runGenerator({
  configurationDirectoryPath: "/workspace/configuration",
  definition: {
    name: "nestjs-service-module",
    templateDirectoryPath: "templates/nestjs-service-module",
  },
  inputs: { name: "billing" },
  instancePath: "packages/billing/src/modules",
});
// → { generatedFilePaths: [...], outputDirectoryPath: "..." }

The lifecycle is preGenerate → render → postGenerate → format, and the rendered file paths are sorted. A path a hook pushes onto the list is appended after the sort.

Hooks

A generator may declare two functions that run around generation. The hooks example runs both.

HookRunsgeneratedFilePaths holds
preGenerateBefore anything is writtenNothing: it is an empty array
postGenerateAfter the template is rendered, before formattingEvery rendered path, sorted

Each hook receives one GeneratorHookContext, may be sync or async, and is awaited. Hooks are plain functions, so a configuration cannot declare them in JSON, which cannot carry a function; any JavaScript or TypeScript configuration can. Import the types from @conformetry/configuration, which re-exports GeneratorHook, GeneratorHookContext, and GeneratorHooks from @conformetry/core; a configuration needs no other dependency to type its hooks.

import path from "node:path";

import type {
  ConformetryConfiguration,
  GeneratorHookContext,
} from "@conformetry/configuration";

const configuration: ConformetryConfiguration = [
  {
    hooks: {
      postGenerate: async (context: GeneratorHookContext): Promise<void> => {
        const receiptPath = path.join(
          context.configurationDirectoryPath,
          "receipts",
          `${context.substitutions["nameKebabCase"] ?? ""}.log`,
        );

        await context.filesystem.writeFile(receiptPath, "generated\n");
        context.generatedFilePaths.push(receiptPath);
      },
    },
    name: "receipt",
    templatePath: "templates/receipt",
  },
];

export default configuration;

The context

FieldWhat it holds
definitionThe generator's runtime definition: name, templateDirectoryPath, hooks
inputThe input values the run was given, by input name
substitutionsWhat the template renders with: the name variants, then inputs over them
outputDirectoryPathWhere the run writes, exactly as the host passed it
generatedFilePathsThe list described above. It is mutable, and what the formatter receives
filesystemThe adapter the run itself reads and writes through
configurationDirectoryPathAbsolute directory of the configuration file that declared the generator

Paths differ by host:

  • outputDirectoryPath and generatedFilePaths are whatever the host passed. Under the command line that is the form --directory was given in, so possibly relative. Under Nx they are absolute.
  • filesystem takes absolute paths only. Build them from configurationDirectoryPath, never from the working directory, so a hook behaves the same however the command was started.
  • configurationDirectoryPath is absolute under both hosts, taken from the file that was actually loaded.
  • definition.templateDirectoryPath is the configuration's templatePath as written, so workspace-relative, under the command line. Under Nx it is resolved against the workspace root, so absolute.

There is no replaceInFile helper. A hook that edits a rendered file reads it with filesystem.readFile, changes the text, and writes it back.

Writing from a hook

Write through context.filesystem, not node:fs. Under the command line the adapter writes to disk. Under Nx it writes to the generator's virtual tree, so the write appears in the change list, --dry-run previews it, and nothing reaches disk until Nx flushes. A write made with node:fs skips the tree: it lands immediately, even under --dry-run, and stays when the run later fails.

Write only inside the workspace. The Nx adapter maps an absolute path inside the workspace to a tree path. It cannot do that for a path outside, so the write is silently re-rooted inside the workspace instead, while a read of the same path falls back to disk. Build every path from configurationDirectoryPath.

makeDirectory does nothing under Nx; writing a file creates its directories. Reads through the Nx adapter look in the tree first, so a hook sees files rendered earlier in the same run.

The format list

The formatter receives context.generatedFilePaths after postGenerate. A file the hook wrote is not on that list, so push its path to have it formatted:

context.generatedFilePaths.push(receiptPath);

The command-line host formats nothing by default, so the list there only decides what the run reports. The Nx host formats every file changed in the tree, hook writes included, whether or not it was pushed; push it anyway so both hosts report the same files.

When a hook throws

The run stops at the throw and the error propagates.

HostpreGenerate throwspostGenerate throws
Command lineNothing was writtenThe rendered files stay on disk; the formatter does not run
NxThe tree is untouchedRendered files sit in the tree, and nx g flushes nothing for a failed run

The command line prints a hook's error with its stack trace and exits non-zero. It does not say which hook threw, so put the generator and hook name in the message. Under Nx the run is atomic on disk for both hooks, but only for writes made through context.filesystem.

Rendering

RenderingService is the single owner of template substitution across the whole toolchain. Generation renders templates to create files; validation renders the same templates to compare against files that already exist. Both must substitute identically or validation would flag the files the generator itself produced — which is why neither reimplements this.

Contents and paths are both rendered with mustache, HTML escaping disabled so substituted values cannot corrupt source code. buildNameSubstitutions derives nameCamelCase, nameKebabCase, namePascalCase, and nameSnakeCase from a single name; callers merge their own inputs over the result, so an explicit input of the same key always wins.

An interpolated placeholder nobody supplied raises MissingSubstitutionError. Mustache would otherwise render it as an empty string rather than leaving the token visible, and because generation and validation render identically, both halves of the loop lost the same value and agreed that nothing was wrong. A hole rendered into both sides of a comparison is not something that comparison can report, so the renderer refuses instead. Validation never renders the hole either: it reads the missing value from the instance, and reports when it cannot.

Section tags are exempt, deliberately: {{#field}} and {{^field}} are conditionals, so an absent name is how a template asks for a block to be skipped or taken. A supplied value that is the empty string is an answer too — only an absent key is a hole. Together those are how a template says "optional": ask with a section, interpolate inside it.

Paths once used a __field__ syntax of their own, on the assumption that braces were not portable across filesystems. They are — and the separate syntax could not tell a placeholder from a Python dunder, so a template shipping __init__.py depended on init never being a substitution.

Adapters

Filesystem and formatter access go through FileSystemAdapter and FormatterAdapter, defaulting to disk and a no-op. That is how @conformetry/nx reuses this runtime unchanged against an Nx generator Tree. Rendering deliberately is not an adapter, for the reason above.

Exports

GenerationService, GenerationModule, and the DirectoryEntry, FileSystemAdapter, FormatterAdapter, and GeneratorDefinition types. The hook types are re-exported for configurations from @conformetry/configuration.

Test

nx run conformetry-generation:vitest

License

MIT — see LICENSE.

👔 Conformetry

This project was generated from the nestjs-service-project conformetry template.

版本与发布记录

@conformetry/generation 共发布 76 个版本,首次发布于 2026/10/05;过去 12 个月发布了 76 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.0.78latest2026/11/10今天
0.0.772026/11/10今天
0.0.762026/11/091 天前
0.0.752026/11/091 天前
0.0.742026/11/091 天前
0.0.732026/11/082 天前

关于 @conformetry/generation 的常见问题

@conformetry/generation 是什么?

Template rendering and file generation runtime for Conformetry

如何安装 @conformetry/generation?

在项目目录运行 npm install @conformetry/generation,或使用 pnpm add / yarn add / bun add。

@conformetry/generation 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@conformetry/generation 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@conformetry/generation 可以商用吗?

@conformetry/generation 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@conformetry/generation 的下载量有多少?

根据 npm 官方下载统计,@conformetry/generation 在 2026/10/11 – 2026/11/09 这 30 天内被下载 9,793 次,日均约 326 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@conformetry/generation 还在维护吗?

最新版本 0.0.78 发布于 2026/11/10(今天);至今共发布 76 个版本,其中过去 12 个月发布了 76 个;首次发布于 2026/10/05。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。