Skip to Content

CLI — teamsync-worker

teamsync-worker 是 @teamsync/department-workers 的 bin,這是一個私有 npm 套件(不在公開的 npm registry 上;Node ≥ 22)。每個 TeamSync 後端在 GET /starters/cli.tgz(任何部門成員)提供與自己 builder 鎖定的那一份:下載後執行 npm install --global ./cli.tgz,然後直接呼叫 teamsync-worker(它透過 npm 的 bin 連結執行;在原始碼 checkout 中,node /path/to/teamsync-worker.mjs <command> 等價)。npm 需要能連到 registry 或鏡像,以取得四個執行期依賴(esbuild、miniflare、typescript-parser、zod)。見範本與原始碼下載。套件恰好包含 build.mjs、dependency-catalog.mjs、runtime-template.mjs、starter-pack.mjs、teamsync-worker.mjs 與兩個範本 — 不含 deploy-platform.mjs、Wrangler 設定或 dispatcher 原始碼(由它的 files 允許清單決定)。

teamsync-worker init <directory> [--language javascript|typescript] teamsync-worker generate [project-root] teamsync-worker validate [project-root] teamsync-worker test [project-root] teamsync-worker pack [project-root] --out <archive.tar.gz> teamsync-worker dev [project-root] [--port 0] teamsync-worker submit [project-root] --backend <https-origin> --jwt-file <0600-file> teamsync-worker deploy --backend <https-origin> --jwt-file <0600-file> --version <uuid> --base-revision <n> [--wait] [--timeout-ms <n>] [--poll-ms <n>] teamsync-worker status <ticket-uuid> --backend <https-origin> --jwt-file <0600-file> teamsync-worker openapi --backend <https-origin> --jwt-file <0600-file> [--out <openapi.json>]

失敗時對 stderr 印出 teamsync_worker_failed:<message>,exit 1。未知或重複旗標一律拒絕(duplicate option <flag>;init、submit、deploy、status、openapi 為 <command> does not accept <flag>;validate accepts no options、generate accepts no options、test accepts no options、pack requires exactly --out <archive.tar.gz>、dev accepts only --port <number>),而且除了 --wait 以外的每個旗標都恰好帶一個值(缺值時為 invalid option <flag>)。[project-root] 預設為目前目錄。沒有指令、help 或 --help 會印出用法並 exit 0;未知指令以 unknown command <name> 加上用法失敗。

本機指令

  • init — 複製 template/(TS,預設)或 template-javascript/;目標目錄必須不存在。從目錄名推導 module.name(小寫化,其他字元的連續片段變成 -,退回 department-api;結果不是合法的模組名稱時,以 target directory cannot be converted to a valid module name 失敗),並一致地改寫進 teamsync.worker.json、package.json 與 package-lock.json。把範本的 gitignore 改名為 .gitignore(node_modules/、.teamsync/、dist/、coverage/、.env、.env.*、*.log)。骨架另外包含 README.md、README.zh-TW.md、.teamsyncignore 與 tests/。印出 initialized <absolute path>。
  • validate — 先做 preflight(manifest、套件 metadata、lockfile 對目錄、必要檔案 teamsync.worker.json、package.json、package-lock.json、tsconfig.json、README.md、.teamsyncignore、原始碼政策、非空的 tests/),然後在暫存目錄執行真正的權威 builder(build.mjs)。CLI 自己的解析永遠不是權威;builder 拒絕顯示為 authoritative builder rejected project: <code>。印出 valid project: N operations, M source files。
  • generate — 寫出 .teamsync/generated.d.ts(TeamSyncWorker 命名空間:Schemas、逐操作的 Operations、HandlerInput、Handler,以及儲存、設定與機密型別)與 .teamsync/openapi.preview.json(標記 x-teamsync-preview: true),皆 0600。HandlerInput 是 {request, params, query, operationId, config, secrets, storage};secrets 是 WorkerSecrets,其 get(name) 回傳 Promise<string | undefined>。它沒有宣告 teamsync(執行期同樣會傳給 handler 的服務帳號 helper,見服務帳號)。印出 generated types and OpenAPI preview for N operations。
  • test — 用 esbuild 打包 tests/ 下每個檔案(platform: node、target: node22),以只含 PATH TMPDIR TMP TEMP LANG LC_ALL 的乾淨環境跑 node --test,60 秒逾時。印出 {"status":"passed","tests":N};測試失敗以 project tests failed (<exit code or signal>) 結束,逾時以 project tests exceeded 60 seconds 結束,打包失敗(通常是還沒執行 npm ci --ignore-scripts)則為 project test build failed; run npm ci --ignore-scripts first (<reason>),皆 exit 1。
  • pack — 產生確定性壓縮檔並寫到 --out(唯一接受的選項;會建立上層目錄,檔案權限 0600);印出 {bytes, digest, files, output},其中 digest 是壓縮檔的 SHA-256。
  • dev — 用 Miniflare 在 workerd 跑權威 builder 的 bundle(真正的生成 router),SQLite Durable Object 持久化在 .teamsync/dev-storage,HTTP 綁 127.0.0.1、預設 OS 配發 port(--port <0-65535>)。印出 teamsync-worker dev listening at <origin> 與 local docs: <origin>/_teamsync/docs,然後執行到收到 SIGINT 或 SIGTERM(乾淨關閉,exit 0)。本機附加路由:GET /_teamsync/openapi.json 與 GET /_teamsync/docs。dev server 對每個請求強制設定 x-teamsync-dispatch-mode: internal + purpose: invoke + 回執的 release generation — 公開通道、存取模式與 release_generation_mismatch 都無法在本機重現。回應上限 8 MiB;請求上限 1 MiB。.teamsync/dev-storage 以 mode 0700 建立,底下每個目錄維持 0700、每個檔案維持 0600(啟動後、機密同步後、每個請求處理完後都會重新套用),因為它存放本機機密值。

本機 Worker 機密(dev)

啟動時,dev 會讀取選用檔案 <project>/.teamsync/dev-secrets.json,並同步進本機 Durable Object,讓 context.secrets.get("NAME") 在本機就能運作(Worker 機密、管理 Worker 機密):

{ "TS_API_KEY": "local-api-key" }

CLI 自己強制下列規則;任何一項失敗都會讓 dev 以 teamsync_worker_failed:<message> 停止:

規則訊息
檔案不存在無:不做任何同步,.teamsync/dev-storage 裡已有的機密維持原狀
其他任何 stat 錯誤.teamsync/dev-secrets.json is not readable
一般檔案、非 symlink、只有一個 hard link.teamsync/dev-secrets.json must be a regular non-hardlinked file
權限恰為 0600.teamsync/dev-secrets.json permissions must be exactly 0600
大小 1 位元組到 16 MiB.teamsync/dev-secrets.json has an invalid size
有效的 UTF-8 JSON,且是名稱/值組成的物件.teamsync/dev-secrets.json must contain valid UTF-8 JSON、.teamsync/dev-secrets.json must contain an object of name/value pairs
最多 256 個名稱.teamsync/dev-secrets.json may contain at most 256 secrets
名稱符合 ^[A-Z][A-Z0-9_]{0,63}$ 且不以 TEAMSYNC_ 開頭.teamsync/dev-secrets.json contains an invalid or reserved secret name
值是不含 NUL 的非空字串,最多 65 536 個 UTF-8 位元組.teamsync/dev-secrets.json contains an invalid secret value、.teamsync/dev-secrets.json contains a value over the UTF-8 byte limit

同步會取代本機的整組機密:本機有、檔案裡沒有的名稱會被刪除,然後把檔案中的每一筆寫入。檔案只在啟動時讀取一次,所以編輯後要重啟 dev。本機控制呼叫失敗時會顯示 local Worker secret synchronization failed (HTTP <status>) 或 local Worker secret synchronization returned invalid JSON。.teamsync/ 在範本的 .gitignore 與 .teamsyncignore 中,所以這個檔案永遠不會被提交或上傳。

pack 出貨什麼(永遠不出貨什麼)

只有 teamsync.worker.json、package.json、package-lock.json、tsconfig.json 與允許的 src/** 進入壓縮檔 — README.md、.teamsyncignore、tests/ 與其他一切不管 ignore 規則都會被丟掉。.teamsyncignore 是逐行、根錨定的:沒有 ! 否定、沒有 **;* 只在單一段內比對;結尾 / 表示目錄樣式。套用 .teamsyncignore 之前,CLI 一律先略過作業系統垃圾檔:最上層的 __MACOSX 目錄,以及任何深度的 .DS_Store、以 ._ 開頭的名稱、Thumbs.db 與 desktop.ini(後兩者不分大小寫)。範本的 .teamsyncignore 也列了同樣的名稱;伺服器端 builder 沒有變,所以手工做出、含有這些檔案的壓縮檔仍由 builder 判定。

確定性:手工組出的 ustar(mode 0644、uid/gid 0、mtime 0、路徑排序),gzip level 9 且 MTIME 歸零、OS 位元組強制 0xff — 跨機器逐位元組相同;第三方 tar czf 永遠重現不了這個摘要。上限:≤256 檔、解壓 ≤4 MiB、單一原始碼檔 ≤1 MiB(每個 src/data/**/*.json 檔 ≤256 KiB)、壓縮後 ≤2 MiB。symlink 與 hardlink 一律拒絕。

網路指令

所有請求都打 <backend-origin>/private/module/edge-workers/...,帶 Authorization: Bearer <jwt>(JWT 只會出現在這裡)、redirect: "manual"(任何 3xx 都是錯誤)、回應上限 4 MiB、逾時 15 秒(提交 150 秒)。收到非預期的狀態碼時只會印出 TeamSync backend returned HTTP <status>,不顯示錯誤本文,所以要用 curl 重打一次才看得到 detail。逾時與連線失敗分別印出 TeamSync backend request timed out 與 TeamSync backend is unavailable。

  • --backend 必須恰好是 HTTPS origin — 沒有 path、query、憑證或多餘內容(允許一個結尾 /)。
  • --jwt-file 必須是一般檔案(非 symlink、nlink 1)、權限恰為 0600、目前 uid 擁有、≤16 KiB、恰好一行 JWS 形狀內容;字面值 test-token 直接拒絕。
  • --version 必須是 UUID,--base-revision 是 ≥ 0 的整數;--timeout-ms 與 --poll-ms 是 ≥ 1 的整數;status 接受票證 UUID。
指令呼叫期望
submitPOST /worker/versions — 單一 multipart 欄位 project、blob 類型 application/gzip、檔名 department-worker.tar.gz201
deployPOST /worker/deployments {version_id, base_revision}202;印出票證,只有票證已是終態且不是 completed 時才 exit 1
deploy --wait輪詢 GET /worker/deployments/{ticket_id}(預設 poll 1 秒、逾時 240 秒,之後 deployment wait timed out)直到 completed/failed/cancelled/uncertain非 completed 一律 exit 1 — CI 把 uncertain 當失敗
status <ticket>GET /worker/deployments/{ticket}200;不論票證狀態都 exit 0
openapiGET /worker/openapi.json;印出文件,或搭配 --out 寫檔並印出 {"output": <path>};--out 拒絕覆寫(原子 0600 寫入)200

建議的生命週期

curl -sS -H "Authorization: Bearer $JWT" -o cli.tgz https://backend.example/private/module/edge-workers/starters/cli.tgz npm install --global ./cli.tgz # the backend's own CLI teamsync-worker init department-api # scaffold (TS default), also writes .gitignore cd department-api && npm ci --ignore-scripts # edit teamsync.worker.json + src/, then: teamsync-worker generate # types + OpenAPI preview teamsync-worker validate # authoritative gates, locally teamsync-worker test # node --test in a sandboxed env teamsync-worker dev # real router in workerd on 127.0.0.1 (reads .teamsync/dev-secrets.json) teamsync-worker submit --backend https://backend.example --jwt-file ~/.tsw/jwt # → 201, note version.id and worker.latest_revision teamsync-worker deploy --backend … --jwt-file … --version <version.id> --base-revision <worker.latest_revision> --wait teamsync-worker openapi --backend … --jwt-file … --out openapi.json

deploy --wait 除非以 --timeout-ms 另行指定,否則在 240 秒後停止,但上傳之後伺服器最長會等 600 秒讓 release 回應(部署與輪詢):部署較慢時,請把 --timeout-ms 調到大於 600 000,或改用 status <ticket> 接續追蹤。等待逾時並不會取消部署。

提交建立 ready 不可變版本,永不隱式部署。敘事版見快速上手。

Last updated on