Windmill 编写 C 脚本完整指南:Main 方法规范、NuGet 依赖管理与 CLI 本地迭代工作流

发布时间:2026/9/14 22:02:34
Windmill 编写 C 脚本完整指南:Main 方法规范、NuGet 依赖管理与 CLI 本地迭代工作流 Windmill 编写 C# 脚本完整指南Main 方法规范、NuGet 依赖管理与 CLI 本地迭代工作流【免费下载链接】windmillOpen-source developer platform to power your entire infra and turn scripts into webhooks, workflows and UIs. Fastest workflow engine (13x vs Airflow). Open-source alternative to Retool and Temporal.项目地址: https://gitcode.com/GitHub_Trending/wi/windmillWindmill 是一个开源开发者平台能把脚本快速变成 Webhook、工作流和 UI。在 Windmill 中编写 C# 脚本时代码需要遵循public static Main入口约定依赖通过#r nuget: ...指令声明而本地开发则围绕wmill script preview、wmill script run与wmill generate-metadata三条命令展开。本文以仓库中的 write-script-csharp 技能文档为主体结合 CLI 与 Worker 端源码完整讲解 C# 脚本的编写规范、NuGet 包管理机制、本地预览与部署的区别以及元数据同步的原理与实操。C# 脚本入口规范public static Main与 Python、Bash 等直接解释执行的脚本语言不同Windmill 的 C# 脚本本质是一个被编译并调用的 .NET 程序因此必须提供一个明确的入口方法。规范如下public class Script { public static object Main(string name, int count) { return new { Name name, Count count }; } }几个关键约束见 SKILL.md类名无关紧要类可以叫Script也可以叫任意其他名字Worker 端会自动定位方法必须是public static只有公开静态方法才能作为脚本入口被反射调用返回类型可以是object或任意具体类型返回值会被序列化为 JSON 写入result.json作为脚本执行结果返回方法名固定为Main。签名解析参数类型如何映射脚本的Main方法签名并非随意声明Windmill 会在编译前用 tree-sitter 解析它用于两件事一是生成.script.yaml输入 Schema进而驱动 UI 自动生成参数表单二是生成 Worker 端的参数包装代码。解析实现在 backend/parsers/windmill-parser-csharp/src/lib.rs类型映射规则find_typ见 lib.rsC# 类型Windmill 内部类型string/char字符串Strint/long/short/uint等整数族整数Intfloat/double/decimal浮点数Floatbool布尔Boolbyte/sbyteBytesstring[]等数组列表List其他自定义/泛型类型Unknown同时解析器还会识别async修饰符is_async与返回类型是否为voidreturns_void参数支持默认值如string myString World这些信息最终决定 Worker 生成的调用代码形态。仓库自带测试用例验证了这些行为例如 test_parse_csharp_sig 验证了string、int、string[]三种参数的解析结果。引入 NuGet 包#r nuget: ...指令C# 脚本引入第三方库的方式与 Python 脚本在顶部写依赖解析注释类似在文件最顶部使用#r指令声明#r nuget: Newtonsoft.Json, 13.0.3 #r nuget: RestSharp, 110.2.0 using Newtonsoft.Json; using RestSharp; public class Script { public static object Main(string url) { var client new RestClient(url); var request new RestRequest(); var response client.Get(request); return JsonConvert.DeserializeObject(response.Content); } }#r指令的解析原理Worker 端使用正则解析所有位于文件开头第一个非注释行之前的#r nuget: 包名, 版本行见 csharp_executor.rs 与解析器实现 parse_csharp_reqs / parse_nuget_req指令必须严格以#r nuget:开头内容为包名, 版本版本号可选省略版本时 Worker 会解析出最新版本因此为了让构建可复现建议始终显式锁定版本指令行必须集中在文件最顶部遇到第一个非#开头的行后停止解析这些#r行在编译前会被从源码中剔除lines_to_remove不会进入最终编译单元。从指令到可执行文件csproj 生成与构建缓存Worker 拿到脚本后并不直接dotnet run而是经历一个完整的编译流水线csharp_executor.rs把#r指令翻译成PackageReference生成临时Main.csproj关键配置包括TargetFramework默认net9.0可用DOTNET_TARGET_FRAMEWORK环境变量覆盖、ImplicitUsingsenable、StartupObjectWindmillScriptCSharpInternal.Wrapper、RestorePackagesWithLockFiletrue根据解析出的Main签名生成Wrapper.cs它从args.json反序列化出参数结构体再调用你的Script.Main(...)并把返回值JsonSerializer.Serialize后写入result.jsonasync/void的组合会生成四种不同调用形态csharp_executor.rs执行dotnet restore --use-lock-file生成packages.lock.json锁定依赖执行dotnet publish --configuration Release产出单文件可执行程序编译产物按内容哈希存入全局缓存object store同哈希的脚本后续直接命中缓存、跳过重复编译冷启动耗时因此被大幅压缩csharp_executor.rs。注意 C# 支持需要 Worker 编译时开启csharpfeature且运行时环境需要dotnet可执行文件见 csharp_executor.rs 的check_executor_binary_exists。语言本身通过迁移 20241029132207_add-csharp-support.up.sql 加入SCRIPT_LANG枚举并默认把csharp追加进 worker 标签。CLI 工作流本地迭代的三条核心命令C# 脚本以及其他语言脚本的日常开发都围绕三条命令展开SKILL.md命令作用是否部署wmill script preview script_path本地迭代默认命令运行本地文件不部署否wmill script run path运行工作区中已部署的脚本版本否针对远端wmill generate-metadata重新生成本地.script.yaml输入 Schema与.lock依赖锁并刷新wmill-lock.yaml中的内容哈希否仅写本地文件git push/wmill sync push把本地改动部署到工作区是Preview 还是 Run按意图选择而不是凭习惯这是最容易踩坑的一点。判断标准很简单SKILL.md如果用户说“运行这个脚本”“试试”“测一下”“看能不能跑通”且本地脚本文件有未部署的改动一律用wmill script preview。不要先 push 再script run——push 是一次部署为了测试而部署会用未经验证的改动覆盖工作区版本只有两种情况才用wmill script run用户明确说“运行已部署的版本/服务器上的版本”或本地根本没有正在编辑的脚本只是调用一个已存在的脚本只有用户明确要求 deploy / publish / push / 发布时才执行git push或wmill sync push并且通常要求 preview 已经验证过改动。从源码看script run与script preview是两个独立子命令script.ts二者都支持-d --data以 JSON 字符串、文件名或 stdin 的-传入输入参数-s --silent只输出最终结果适合脚本化调用--tag覆盖任务派发到的 worker 标签。preview 的本质是调用后端的预览运行接口runScriptPreview见 script.ts把本地文件内容作为参数提交给 Worker 执行然后轮询任务结果——所以它虽然不部署但仍然会真实执行脚本代码并可能产生副作用应当在用户要求测试/preview或确认执行意图时由你亲自运行。编写完成后的下一步主动提议测试如果用户没有明确要求运行/测试写完后用一句话给出下一步即可例如“需要我用示例参数运行wmill script preview吗”不要抛出一个多选项菜单。如果用户在原请求中已经要求测试/运行则直接执行wmill script preview path -d args参数从脚本声明的参数里挑合理值填入。保持元数据同步wmill generate-metadata详解Windmill 的 git-sync 工作流里每个脚本旁边会有三个派生文件.script.yaml输入 Schema驱动 UI 参数表单、.lock解析后的依赖锁、以及仓库根的wmill-lock.yaml为每个条目记录内容哈希。当你新增/删除 import 或修改main的参数时内容哈希失效这三个文件就会与代码脱节SKILL.md。如果放任不管git-sync 和 CI 里会出现大量虚假 diff。此时运行wmill generate-metadata它会重新解析依赖并更新.lock/.script.lock重新生成.script.yaml输入 Schema刷新wmill-lock.yaml中的内容哈希。命令本身只写本地文件不是部署命令描述见 generate-metadata.ts。但它会重新解析依赖因此可能升级未锁版本的依赖——这与在 UI 里部署的效果一致是预期行为而非 bug。所以默认做法是先主动提议用户同意后再运行而不是每次编辑后静默执行除非项目AGENTS.md显式选择自动同步元数据。运行后应 diff 一下再生成的.lock文件把变化的依赖版本告知用户如Newtonsoft.Json 13.0.1 → 13.0.3即使项目开启了Metadata: auto也应告知因为这是信息而非确认门槛。在代码里锁版本才能让依赖保持固定。关键行为与常用选项不带路径参数时generate-metadata只重新生成内容哈希发生漂移的条目不会全量重建导入会传播编辑一个被其他脚本 import 的共享模块会使所有 importer 都标记为 stale——一行改动可能重新生成许多锁这是设计使然它们的锁必须反映被导入的代码如果波及范围超出预期先跑wmill generate-metadata --dry-run预览它只列出每个 stale 条目及其原因content changed或depends on path不做任何修改再用路径参数缩小范围如wmill generate-metadata f/foo或加--strict-folder-boundaries严格限定在指定目录内支持的选项完整列表generate-metadata.ts--yes跳过确认、--dry-run、--lock-only、--schema-only只重建 schema跳过 flows/apps、--skip-scripts/--skip-flows/--skip-apps、--strict-folder-boundaries、--parallel n、-i --includes/-e --excludeswmill generate-metadata rehash当磁盘上的.lock和.script.yaml已经正确、只需刷新wmill-lock.yaml的哈希时使用哈希漂移或引导缺失条目。它直接从磁盘重新记录哈希不访问后端、不改变任何依赖。实现上generateMetadata会先构建一棵双向依赖树DoubleLinkedDependencyTree收集所有 stale 脚本/flow/app再通过tree.propagateStaleness()沿 import 关系传播过期状态generate-metadata.ts这与script push前的元数据检查逻辑一致——push 时如果发现条目没有元数据或哈希过期会给出黄色警告script.ts。部署到工作区部署是唯一会改动远端状态的步骤SKILL.md根据仓库与工作区的接线方式二选一git-sync 接线本地 commit 后git push由 git-sync 自动同步CLI 接线wmill sync push手动推送。wmill sync push底层逐文件调用handleFilescript.ts它会推断语言、读取.script.yaml元数据与.lock、扫描__mod/模块目录然后调用后端创建/更新脚本接口若远端已存在相同内容与元数据则判定为 up-to-date 并跳过写入skip_if_nooptrue避免产生幻影提交。推送时可用--message附带部署说明。完整工作流串联从零写一个 C# 脚本在仓库中创建脚本文件如f/hello.cs按规范编写public static Main顶部用#r声明 NuGet 依赖本地迭代wmill script preview f/hello.cs -d {name: world, count: 3}验证逻辑与结果编辑了参数或依赖后运行wmill generate-metadata必要时先--dry-run刷新 Schema、锁与内容哈希并 diff 检查依赖版本变化确认无误后用户明确要求部署时通过git push或wmill sync push发布到工作区想查看工作区里已部署版本的运行结果时才用wmill script run f/hello。这套流程的关键纪律是preview 验证本地改动、generate-metadata 保持元数据同步、run 只针对已部署版本、deploy 只在用户明确要求时执行。理解wmill-lock.yaml哈希、.lock依赖锁与.script.yamlSchema 三者之间的关系就能在 C#及其他语言脚本开发中避免绝大多数 git-sync 噪音问题。【免费下载链接】windmillOpen-source developer platform to power your entire infra and turn scripts into webhooks, workflows and UIs. Fastest workflow engine (13x vs Airflow). Open-source alternative to Retool and Temporal.项目地址: https://gitcode.com/GitHub_Trending/wi/windmill创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考