调用
一个代码生成器框架,其中生成器是返回数据的纯函数,而不是副作用.
写发电机一次。 运行它的真实。 预览方式 --dry-run. 测试没有嘲笑。 同样的代码,不同的翻译.
generate: (answers) => writeFile(`src/${answers.name}.ts`, code)
// Returns a Task describing "write this file"
// Not a file write. Data.
安装
npm install -g @canonical/summon # or: bun add -g @canonical/summon
CLI运行在平地节点下-不需要Bun. 它只用一个
示例生成器和 summon init (脚手架自己的发电机). 真实的
发电机在自己的软件包中生活; 将您需要的软件包安装在同一软件包中
位置和Summon自动发现它们:
npm install -g @canonical/summon-application # summon application / domain / page / wrapper
npm install -g @canonical/summon-component # summon component
使用发电机
提醒自动从已安装的软件包中发现生成器 . 见 安装发电机包 安装现有发电机包, 或者说 创建生成器 来创造一个新的.
# See what's available
summon
# Run a generator interactively
summon component react
# Pass answers as positional argument (when supported)
summon component react src/components/Button
# Or as a flag
summon component react --component-path=src/components/Button
# Preview first (nothing written to disk)
summon component react src/components/Button --dry-run
# Show debug output
summon component react src/components/Button --verbose
每个提示都变成CLI旗. 布尔提示 default: true 使用该 --no- 要禁用前缀。 发电机也可定义a 位置参数 允许更清洁的命令引用.
CLI 选项
| 旗帜 | 说明 |
|---|---|
-d, --dry-run | 不写入文件预览 |
-y, --yes | 跳过确认提示并预览 |
-v, --verbose | 显示调试输出 |
--show-files | 在干线中显示文件内容 |
-l, --llm | LLM 模式: 有下调输出的干运行, 无提示, 无邮票 |
--format <type> | 输出格式 : json (简单的干燥,没有提示,没有邮票) |
--no-preview | 略过文件预览 |
--no-generated-stamp | 禁用生成的文件章注释 |
LLM 用法 : Summon将LLMs视为一等CLI用户. 使用 --llm 用于结构化的平分输出或 --format json 为机器解剖JSON。 两者都意味着 --dry-run --yes --show-files --no-generated-stamp数字 :
# Structured markdown (answers table, plan table, fenced code blocks)
summon component react src/components/Button --llm
# Machine-parseable JSON
summon component react src/components/Button --format json
# Session-level LLM mode via environment variable
export SUMMON_LLM=1
summon component react src/components/Button
# Structured help for LLMs
summon component react --help --llm
Shell 自动补全
Summon支持为Bash,Zsh和Fish壳完成TAB. 完成情况 动态 - 它们自动检测新安装的发电机包,而不需要重新运行设置.
# Automatic setup (recommended)
summon --setup-completion
# Or manual installation for each shell:
# Zsh
echo '. <(summon --completion)' >> ~/.zshrc
# Bash (may need: brew install bash-completion on macOS)
summon --completion >> ~/.summon-completion.sh
echo 'source ~/.summon-completion.sh' >> ~/.bash_profile
# Fish
echo 'summon --completion-fish | source' >> ~/.config/fish/config.fish
设置后, 重新启动您的 shell 或源代码配置文件 。 然后说:
summon <TAB> # Shows: component, application, init, ...
summon component <TAB> # Shows: react, svelte, lit
summon component react <TAB> # Shows: --component-path, --no-with-styles, ...
summon component react --comp<TAB> # Completes to: --component-path
完成特性 :
- 生成器名称 - 用 TAB 导航命令树
- 发电机旗 - 所有提示成为可完成的旗帜
- 确认提示 - 显示
--no-X用于提示default: true - 选择/ 多选择 - 完成现有选择
- 路径提示 - 包含“路径”、“目录”、“文件”等提示的文件系统路径补全.
删除自动补全 :
summon --cleanup-completion
位置参数
生成器可以定义一个提示为 位置参数,允许用户提供主输入而不用旗:
# With positional argument support
summon component react src/components/Button
# Equivalent to
summon component react --component-path=src/components/Button
位置参数在使用 TAB 时也会获得文件系统路径补全 :
summon component react src/comp<TAB> # Completes to: src/components/
安装发电机包
CLI 捆绑 application, (中文(简体) ), domain, (中文(简体) ), page,以及 wrapper 发电机.
其他发电机——包括公务发电机 @canonical/summon-component 船舶作为
后面的单独包 summon-* 或者说 @scope/summon-* 命名大会.
将一个安装到 同一地点 作为CLI(既包括全球项目,也包括两个项目)
)和召唤自动发现:
# Component generators (react/svelte/lit)
npm install -g @canonical/summon-component # or: bun add -g ...
# Now available (completions work immediately!)
summon component react
summon component svelte
创建生成器
生成器是一个纯函数,它接收答案并返回 a Task- 描述该怎么做的数据结构.
// src/module/types.ts
interface ModuleAnswers {
name: string;
withTests: boolean;
}
// src/module/generator.ts
import type { GeneratorDefinition } from "@canonical/summon";
import { debug, info, writeFile, mkdir, sequence_, when } from "@canonical/summon";
import type { ModuleAnswers } from "./types.js";
export const generator = {
meta: {
name: "module",
description: "Creates a new module",
version: "0.1.0",
},
prompts: [
// positional: true allows `summon module src/utils` instead of `--name=src/utils`
{ name: "name", type: "text", message: "Module name:", positional: true },
{ name: "withTests", type: "confirm", message: "Include tests?", default: true },
],
generate: (answers) => sequence_([
info(`Creating module: ${answers.name}`),
debug("Creating module directory"),
mkdir(`src/${answers.name}`),
debug("Creating index file"),
writeFile(`src/${answers.name}/index.ts`, `export const ${answers.name} = {};\n`),
when(answers.withTests, debug("Creating test file")),
when(answers.withTests,
writeFile(`src/${answers.name}/index.test.ts`, `test("works", () => {});\n`)
),
info(`Created module at src/${answers.name}`),
]),
} as const satisfies GeneratorDefinition<ModuleAnswers>;
// src/module/index.ts (barrel)
export { generator } from "./generator.js";
export type { ModuleAnswers } from "./types.js";
// src/index.ts (package barrel)
import type { AnyGenerator } from "@canonical/summon";
import { generator as moduleGenerator } from "./module/index.js";
export const generators = {
"module": moduleGenerator,
} as const satisfies Record<string, AnyGenerator>;
软件包结构
每个发电机应被分为三个文件,以便维持:
my-summon-package/
├── package.json
├── src/
│ ├── index.ts # Package barrel - exports generators record
│ ├── module/
│ │ ├── index.ts # Generator barrel
│ │ ├── types.ts # Answer types
│ │ └── generator.ts # Generator definition
│ └── templates/ # EJS templates (optional)
└── README.md
{
"name": "@myorg/summon-module",
"main": "src/index.ts",
"peerDependencies": {
"@canonical/summon": "workspace:*"
}
}
地方发展
为在当地开发发电机,将发电机连接到全球:
# From your generator package directory
cd /path/to/my-summon-package
bun link # for bun
npm link # for npm
# Now available everywhere
summon module src/utils
项目-当地软件包 ./node_modules)优于全球链接的版本,因此可以使用特定项目版本来覆盖.
测试发电机
由于发电机返回数据,测试是直接的,不需要模拟:
import { dryRun, getAffectedFiles } from "@canonical/summon";
import { generators } from "./index";
const generator = generators["module"];
test("creates expected files", () => {
const task = generator.generate({ name: "utils", withTests: true });
const { effects } = dryRun(task);
expect(getAffectedFiles(effects)).toEqual([
"src/utils/index.ts",
"src/utils/index.test.ts",
]);
});
test("skips test file when disabled", () => {
const task = generator.generate({ name: "utils", withTests: false });
const { effects } = dryRun(task);
expect(getAffectedFiles(effects)).not.toContain("src/utils/index.test.ts");
});
干流解译器维持虚拟文件系统,所以基于 exists() 即使没有碰到磁盘,工作也是正确的.
修道院模式
调用 a 摩纳德 来写任务。 如果你用过"承诺",你已经理解了核心思想.
什么是摩纳德?
Monad是一些有上下文(如"可能失败"或"有效果")的连锁操作的设计模式. 把它当作一个管道,每个步骤都可以:
- 转换值 (单位:千美元)
map——对结果应用函数 - 连锁业务 (单位:千美元)
flatMap)——利用结果创造出下一步 - 故障后短路 —— 错误自动传播
// Promise (async context)
fetchUser(id)
.then(user => fetchOrders(user.id)) // chain
.then(orders => orders.length) // transform
.catch(handleError); // recover
// Task (effect context)
task(readFile("config.json"))
.map(content => JSON.parse(content)) // transform
.flatMap(config => writeFile( // chain
config.outputPath,
generateCode(config)
))
.recover(err => pure(defaultConfig)); // recover
任务类型
Task<A> 代表计算:
- 描述效果( 文件 I/ O、 shell 命令等)
- 最终产生类型值
A - 可能因错误而失败
// A Task is one of three things:
type Task<A> =
| { _tag: "Pure"; value: A } // Done, here's the value
| { _tag: "Fail"; error: TaskError } // Failed with error
| { _tag: "Effect"; effect: Effect; cont: ... } // Do this effect, then continue
编译任务
这个 TaskBuilder 为组成提供流利的API:
import { task, pure } from "@canonical/summon";
// Build a pipeline
const pipeline = task(readFile("input.txt"))
.map(content => content.toUpperCase()) // Transform the value
.flatMap(upper => writeFile("output.txt", upper)) // Chain another effect
.andThen(info("Done!")) // Sequence (ignore previous value)
.recover(err => pure(void 0)); // Handle errors
// pipeline is still just data — nothing has executed yet
关键操作
| 运行 | 说明 | 示例 |
|---|---|---|
pure(value) | 在任务中环绕一个值 | pure(42) |
map(fn) | 转换结果 | .map(x => x * 2) |
flatMap(fn) | 与另一个任务连锁 | .flatMap(x => writeFile(...)) |
andThen(task) | 顺序, 前一个丢弃 | .andThen(info("next")) |
recover(fn) | 处理错误 | .recover(e => pure(default)) |
tap(fn) | 边效果, 保留值 | .tap(x => debug(x)) |
为什么Monads为发电机?
- 可编曲 ——小任务结合成复杂的工作流程
- 可预见 ——错误在不明确处理的情况下在每一步骤上传播
- 可测试 ——输油管为数据;检查时无运行效果
- 声明 - 说明 什麽 做,没有 怎么样 要做到这一点
// Complex workflow, reads like a recipe
const scaffoldFeature = (name: string) =>
task(mkdir(`src/features/${name}`))
.andThen(template({
source: "templates/feature.ts.ejs",
dest: `src/features/${name}/index.ts`,
vars: { name },
}))
.andThen(when(config.withTests,
template({
source: "templates/test.ts.ejs",
dest: `src/features/${name}/index.test.ts`,
vars: { name },
})
))
.andThen(info(`Created feature: ${name}`))
.unwrap();
关于更深入地潜入“作为数据的效果”哲学,见: 解释。 。 。 .
调温引擎
Summon默认使用EJS进行模板渲染,但支持自定义调温引擎通过 TemplatingEngine 接口.
默认 (EJS)
import { template, templateDir } from "@canonical/summon";
// Uses EJS by default
template({
source: "templates/component.tsx.ejs",
dest: "src/components/<%= name %>.tsx",
vars: { name: "Button" },
});
自定义引擎
执行 TemplatingEngine 用于使用 Handlebars、Mustache、Nunjucks 或任何其他引擎:
import type { TemplatingEngine } from "@canonical/summon";
import Handlebars from "handlebars";
import * as fs from "node:fs/promises";
const handlebarsEngine: TemplatingEngine = {
render(template, vars) {
return Handlebars.compile(template)(vars);
},
async renderAsync(template, vars) {
return Handlebars.compile(template)(vars);
},
async renderFile(templatePath, vars) {
const content = await fs.readFile(templatePath, "utf-8");
return Handlebars.compile(content)(vars);
},
};
// Use in templates
template({
source: "templates/component.hbs",
dest: "src/components/{{name}}.tsx",
vars: { name: "Button" },
engine: handlebarsEngine,
});
接口
interface TemplatingEngine {
/** Render a template string synchronously */
render(template: string, vars: Record<string, unknown>): string;
/** Render a template string asynchronously */
renderAsync(template: string, vars: Record<string, unknown>): Promise<string>;
/** Render a template file asynchronously */
renderFile(templatePath: string, vars: Record<string, unknown>): Promise<string>;
}
这个 engine 选项在 template(), (中文(简体) ), templateDir(), (中文(简体) ), renderString(), (中文(简体) ), renderStringAsync(),以及 renderFile()。 。 。 .
文档
许可证
GPL-3.0 (英语)