From c4351e4b7f868843e92781aeae7ed5b6b5020f43 Mon Sep 17 00:00:00 2001 From: richblack Date: Thu, 16 Apr 2026 13:53:55 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20multi-language=20component=20guide=20?= =?UTF-8?q?=E2=80=94=20TinyGo,=20AssemblyScript,=20Rust?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README: add language comparison table in contributing section, explain AI writing quality differences and why TinyGo is recommended for official components. CONTRIBUTING: full rewrite with separate TinyGo + AssemblyScript sections, each with AI prompt templates, code templates, build commands, and test commands. Rust documented as supported with basic setup reference. Co-Authored-By: Claude Sonnet 4.6 --- CONTRIBUTING.md | 349 ++++++++++++++++++++++++++++++++++-------------- README.md | 15 ++- 2 files changed, 265 insertions(+), 99 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5070de7..1360d13 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,51 +2,64 @@ 感謝你考慮貢獻 arcrun!本文件說明如何新增零件(WASM component)並提交至公眾零件庫。 ---- - -## 開發環境 - -### 必要工具 - -```bash -# TinyGo(編譯 Go → WASM) -brew install tinygo # macOS -# 或參考 https://tinygo.org/getting-started/ - -# wasmtime(本機測試 WASM) -brew install wasmtime # macOS -# 或參考 https://wasmtime.dev - -# Wrangler CLI(部署 Cloudflare Workers) -npm i -g wrangler - -# arcrun CLI -npm i -g arcrun -``` +arcrun 的零件**主要由 AI 撰寫**。你不需要是 TinyGo 或 AssemblyScript 專家,只需要把這份文件和你的 API 文件或需求貼給 AI,讓它生成源碼,你負責編譯、測試、提交。 --- -## 新增零件步驟 +## 選擇開發語言 -### 1. 建立目錄結構 +零件只需要輸出符合 **WASI preview1** 的 `.wasm` 檔案,與使用哪個語言無關。 + +| 語言 | 輸出大小 | AI 撰寫品質 | 說明 | +|------|---------|------------|------| +| **TinyGo** | 極小(10–80KB) | 優秀 | 官方零件使用;語法簡單,AI 出錯率低 | +| **AssemblyScript** | 小(20–150KB) | 良好 | TypeScript 語法,前端開發者最快上手 | +| **Rust** | 小–中(30–300KB) | 良好 | 效能最強;適合複雜演算法,工具鏈稍複雜 | + +**AI 開發建議:** +- 選 **TinyGo**:Go 語法與 TypeScript 差異夠大,AI 不易把 TS 邏輯直接搬過來造成錯誤,是最穩的選擇。 +- 選 **AssemblyScript**:適合已熟悉 TypeScript 的開發者,但要注意 AS 不是 TS — 提示 AI 時明確說「AssemblyScript,不是 TypeScript」。 +- 選 **Rust**:效能要求高時使用;需要更詳細的提示和更仔細的審查。 + +--- + +## 零件規格:共通規則 + +無論使用哪個語言,零件必須遵守: + +- **I/O 模型**:從 `stdin` 讀取 JSON,往 `stdout` 輸出 JSON,不使用 return value +- **回傳格式**:成功 `{"success":true,"result":...}`,失敗 `{"success":false,"error":"..."}` +- **不 panic**:任何錯誤都應輸出 `success:false` JSON,不讓執行器收到空輸出 +- **不使用網路 / 檔案系統**(功能類零件):`no_network_syscall: true` +- **允許網路**(整合類零件):`no_network_syscall: false`,必須宣告 `credentials_required` + +--- + +## 目錄結構 ``` registry/components/my_component/ -├── component.contract.yaml # 零件規格宣告 -├── main.go # 零件邏輯(TinyGo) +├── component.contract.yaml # 零件規格宣告(必填) +├── main.go # TinyGo 源碼(TinyGo 零件) +├── assembly/index.ts # AssemblyScript 源碼(AS 零件) +├── src/lib.rs # Rust 源碼(Rust 零件) └── my_component.wasm # 編譯產出(不提交至 git,CI 自動產生) ``` -### 2. 撰寫 component.contract.yaml +--- + +## component.contract.yaml + +所有語言共用相同的合約格式: ```yaml -canonical_id: "my_component" +canonical_id: "my_component" # 小寫底線,全庫唯一 display_name: "我的零件" -category: "data" # api / logic / data / ai +category: "data" # api / logic / data / ai / style / anim / ui version: "v1" author: "@your-github-username" wasi_target: "preview1" -stability: "floating" +stability: "floating" # floating / stable / pinned runtime_compat: - "cf-workers" - "workerd" @@ -54,14 +67,13 @@ runtime_compat: constraints: max_size_kb: 2048 max_cold_start_ms: 50 - no_network_syscall: true # 功能類零件不允許網路請求 - no_filesystem_syscall: true + no_network_syscall: true # 功能類 true,整合類 false io_model: "stdin_stdout_json" input_schema: type: object - required: [input] + required: [text] properties: - input: + text: type: string description: 輸入文字 output_schema: @@ -71,15 +83,18 @@ output_schema: type: string gherkin_tests: - scenario: "基本轉換" - given: '{"input":"hello"}' + given: '{"text":"hello"}' then_contains: '"result"' + - scenario: "缺少必填欄位" + given: '{}' + then_contains: '"success":false' config_example: | - transform: # 節點名稱(可自訂) - input: "{{input.text}}" # 輸入欄位(必填) + transform: + text: "{{input.text}}" description: "我的零件功能說明。" ``` -**需要 Credential 的整合類零件需額外加入:** +整合類零件額外加入: ```yaml credentials_required: @@ -89,91 +104,236 @@ credentials_required: inject_as: api_token ``` -### 3. 撰寫 main.go +--- -WASM 零件使用 stdin/stdout JSON I/O 模型(WASI preview1): +## TinyGo 零件開發 + +### 環境安裝 + +```bash +# TinyGo +brew install tinygo # macOS +# 其他平台:https://tinygo.org/getting-started/ + +# 本機測試執行器 +brew install wasmtime # macOS +``` + +### 給 AI 的提示範本 + +``` +請幫我用 TinyGo 寫一個 arcrun WASM 零件。 + +需求:[你的需求] + +規則: +- 從 stdin 讀取 JSON,往 stdout 輸出 JSON +- 成功回傳 {"success":true,"result":...} +- 失敗回傳 {"success":false,"error":"..."},不 panic +- 不使用網路、不使用檔案系統(純功能類零件) +- import 只用標準庫(encoding/json, os, fmt, strings 等) + +請生成 main.go 和 component.contract.yaml。 +``` + +### main.go 範本 ```go package main import ( - "encoding/json" - "fmt" - "os" + "encoding/json" + "fmt" + "os" ) type Input struct { - Input string `json:"input"` + Text string `json:"text"` } type Output struct { - Success bool `json:"success"` - Result string `json:"result,omitempty"` - Error string `json:"error,omitempty"` + Success bool `json:"success"` + Result string `json:"result,omitempty"` + Error string `json:"error,omitempty"` } func main() { - var input Input - decoder := json.NewDecoder(os.Stdin) - if err := decoder.Decode(&input); err != nil { - out, _ := json.Marshal(Output{Success: false, Error: "invalid input: " + err.Error()}) - fmt.Println(string(out)) - return - } + var input Input + if err := json.NewDecoder(os.Stdin).Decode(&input); err != nil { + writeError("invalid input: " + err.Error()) + return + } - // 你的邏輯 - result := doTransform(input.Input) + if input.Text == "" { + writeError("text is required") + return + } - out, _ := json.Marshal(Output{Success: true, Result: result}) - fmt.Println(string(out)) + // 你的邏輯 + result := "[transformed] " + input.Text + + out, _ := json.Marshal(Output{Success: true, Result: result}) + fmt.Println(string(out)) } -func doTransform(s string) string { - return "[transformed] " + s +func writeError(msg string) { + out, _ := json.Marshal(Output{Success: false, Error: msg}) + fmt.Println(string(out)) } ``` -### 4. 編譯 WASM +### 編譯 ```bash cd registry/components/my_component tinygo build -o my_component.wasm -target wasi . ``` -### 5. 本機測試 +### 本機測試 ```bash -# 直接測試 WASM -echo '{"input":"hello world"}' | wasmtime run my_component.wasm +echo '{"text":"hello world"}' | wasmtime run my_component.wasm +# 預期:{"success":true,"result":"[transformed] hello world"} -# 預期輸出:{"success":true,"result":"[transformed] hello world"} -``` - -### 6. 驗證 Gherkin 測試 - -確認 contract.yaml 的 `gherkin_tests` 所有場景都通過: - -```bash -# 每個 scenario:given 輸入 → 輸出包含 then_contains -echo '' | wasmtime run my_component.wasm | grep '' +echo '{}' | wasmtime run my_component.wasm +# 預期:{"success":false,"error":"text is required"} ``` --- -## 提交零件至公眾 Registry +## AssemblyScript 零件開發 + +### 環境安裝 ```bash -# 確保已編譯 .wasm -tinygo build -o my_component.wasm -target wasi . +# Node.js >= 18 +node --version + +# 初始化 AS 專案 +npm init -y +npm install --save-dev assemblyscript +npx asinit . + +# 本機測試執行器 +brew install wasmtime # macOS +``` + +### 給 AI 的提示範本 + +``` +請幫我用 AssemblyScript(不是 TypeScript)寫一個 arcrun WASM 零件。 + +需求:[你的需求] + +規則: +- AssemblyScript 是 TypeScript 的子集,編譯為 WASM,不能使用 DOM / Node.js API +- 從 stdin 讀取 JSON(使用 WASI fd_read),往 stdout 輸出 JSON(使用 Console.log) +- 成功回傳 {"success":true,"result":...} +- 失敗回傳 {"success":false,"error":"..."} +- 不使用網路、不使用檔案系統 + +請生成 assembly/index.ts 和 component.contract.yaml。 +注意:AssemblyScript 沒有 JSON.parse,需要手動解析或使用 as-json 套件。 +``` + +### assembly/index.ts 範本 + +```typescript +// AssemblyScript — 注意:這不是 Node.js / TypeScript! +// 沒有 DOM、沒有 fetch、沒有 require + +import { Console } from "as-wasi/assembly"; +import { JSON } from "assemblyscript-json/assembly"; + +export function _start(): void { + // 從 stdin 讀取輸入 + const input = Console.readAll(); + + // 解析 JSON + const parsed = JSON.parse(input); + if (!parsed.isObj) { + Console.log('{"success":false,"error":"invalid input"}'); + return; + } + + const obj = parsed as JSON.Obj; + const textVal = obj.getString("text"); + if (textVal == null) { + Console.log('{"success":false,"error":"text is required"}'); + return; + } + + const text = textVal.valueOf(); + + // 你的邏輯 + const result = "[transformed] " + text; + + Console.log('{"success":true,"result":"' + result + '"}'); +} +``` + +**專案依賴(package.json):** + +```json +{ + "dependencies": { + "as-wasi": "^0.4.7", + "assemblyscript-json": "^1.1.0" + } +} +``` + +### 編譯 + +```bash +cd registry/components/my_component +npm install +npx asc assembly/index.ts \ + --target release \ + --outFile my_component.wasm \ + --exportRuntime \ + --use abort=~lib/wasi_abort +``` + +### 本機測試 + +```bash +echo '{"text":"hello world"}' | wasmtime run my_component.wasm +# 預期:{"success":true,"result":"[transformed] hello world"} +``` + +--- + +## Rust 零件開發 + +Rust 零件支援已就緒,但文件尚在完善中。如果你熟悉 Rust + WASM,歡迎參考 [wasm-wasi 官方文件](https://doc.rust-lang.org/stable/reference/linkage.html),核心要求與其他語言相同:WASI preview1,stdin/stdout JSON I/O。 + +基本設定: + +```bash +rustup target add wasm32-wasip1 +cargo build --target wasm32-wasip1 --release +``` + +--- + +## 提交至公眾 Registry + +```bash +# 確保 .wasm 已編譯 +ls my_component.wasm # 提交(需要 arcrun.dev API Key) acr parts publish ./registry/components/my_component/ ``` -提交後: -- **功能類**零件(無網路請求):體積 + syscall + Gherkin 測試通過後,立即 `author_only`(你自己可用) -- **整合類**零件(有外部 API 呼叫):體積 + syscall 掃描通過後,`author_only` -- 等人工審核通過 → `public`(所有人可用,開始累積執行統計) +提交後流程: + +| 狀態 | 說明 | +|------|------| +| `sandbox_pending` | 沙盒驗收執行中 | +| `author_only` | 驗收通過,你自己可用 | +| `public` | 人工審核通過,所有人可用,開始累積統計 | 查詢審核進度: @@ -183,32 +343,27 @@ acr parts publish --status --- -## 零件類型指引 +## 常見問題 -### 功能類(category: logic / data / ai) +### `no_network_syscall` 設定錯誤 -- 不允許網路請求(`no_network_syscall: true`) -- 不允許檔案系統存取(`no_filesystem_syscall: true`) -- 需通過所有 Gherkin 測試 -- 冷啟動 < 50ms,體積 < 2048KB +- **功能類**(category: logic / data / ai):`no_network_syscall: true`。這類零件應完全沙盒化。 +- **整合類**(category: api):`no_network_syscall: false`,因為要呼叫外部 API。 -### 整合類(category: api) +兩者都需要宣告在 `constraints` 下,設錯會在 syscall 掃描步驟被沙盒拒絕。 -- 允許網路請求(設 `no_network_syscall: false`) -- 必須宣告 `credentials_required` -- 需通過體積和 syscall 掃描 +### `gherkin_tests` 必須包含 happy path 和 error path ---- +至少兩個測試場景:一個輸入正確的 happy path、一個缺少必填欄位或輸入非法的 error path。 -## 程式碼風格 +### 體積超過上限 -- 零件必須回傳包含 `success: bool` 的 JSON -- 錯誤時回傳 `{"success":false,"error":"..."}`,不 panic -- 所有 `required` 欄位缺少時應回傳 `success:false` 而非 crash -- contract 的 `input_schema.required[]` 必須與 main.go 的驗證邏輯一致 +- TinyGo:確認使用 `-target wasi`(而非 `-target wasm`),前者體積更小 +- AssemblyScript:加上 `--optimize` 或 `--target release` +- Rust:使用 `--release` 並加入 `opt-level = "z"` 到 `Cargo.toml` --- ## 問題回報 -開 Issue:[github.com/arcrun/arcrun/issues](https://github.com/arcrun/arcrun/issues) +開 Issue:[github.com/richblack/arcrun/issues](https://github.com/richblack/arcrun/issues) diff --git a/README.md b/README.md index 13ab1b9..2d5916b 100644 --- a/README.md +++ b/README.md @@ -240,9 +240,20 @@ acr parts scaffold gmail ### 貢獻零件 -零件是 TinyGo 編譯的 `.wasm` 檔案,stdin 進 JSON、stdout 出 JSON,就這樣。 +零件是 `.wasm` 檔案,stdin 進 JSON、stdout 出 JSON。用什麼語言編譯不重要,只要輸出符合 WASI preview1 的 `.wasm` 即可。 -**AI 可以直接幫你寫零件。** 把 API 文件和 [CONTRIBUTING.md](CONTRIBUTING.md) 一起貼給它,它生成 `main.go` 和 `component.contract.yaml`,你編譯、測試、提交: +**arcrun 的零件主要由 AI 撰寫。** 這個設計決定影響了語言選擇: + +| 語言 | 輸出大小 | AI 撰寫品質 | 學習曲線 | 備註 | +|------|---------|------------|---------|------| +| **TinyGo** | 極小(10–80KB) | 優秀 | 低(Go 語法簡單) | 官方零件首選;語法與 TS 差異夠大,AI 不易混淆 | +| **AssemblyScript** | 小(20–150KB) | 良好 | 低(TypeScript 語法) | 社群貢獻首選;TS 開發者上手最快 | +| **Rust** | 小–中(30–300KB) | 良好 | 高(Rust 所有權) | 效能最強;適合複雜演算法零件 | +| C / C++ | 小 | 尚可 | 高 | 不建議,現代語言更好 | + +**注意:** AssemblyScript 與 TypeScript 語法高度相似,AI 有時會把純 TS 邏輯直接搬過來造成編譯錯誤。TinyGo 語法差異夠大,AI 出錯率較低。初期如果不確定選哪個,TinyGo 是最穩的選擇。 + +**AI 可以直接幫你寫零件。** 把 API 文件和 [CONTRIBUTING.md](CONTRIBUTING.md) 一起貼給它,指定語言(TinyGo 或 AssemblyScript),它生成源碼和 `component.contract.yaml`,你編譯、測試、提交: ```bash acr parts publish ./my-component/