Use when 使用者說「部署到 YuDefine」「deploy to yudefine」、要求把專案放到 yudefine.com.tw。支援 void.cloud 或自有 Cloudflare 帳號。NOT for BigByte。
Install to Claude Code
npx -y skills add Charles5277/nuxt-supabase-starter --skill yudefine-deploy --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Yudefine Deploy?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/charles5277-yudefine-deploy)More formats (shields.io, HTML) on the badges page.
---
name: yudefine-deploy
description: "Use when 使用者說「部署到 YuDefine」「deploy to yudefine」、要求把專案放到 yudefine.com.tw。支援 void.cloud 或自有 Cloudflare 帳號。NOT for BigByte。"
---
<!-- 🔒 LOCKED — managed by clade · auto-generated by sync-to-cursor; edit source in .claude/ then re-run sync -->
# YuDefine 部署(void.cloud / 自有 CF 雙軌)
YuDefine fleet 內**兩條合法 deploy track**,本 skill 兩條都支援。**MUST** Step 0 詢問使用者該專案走哪條,再執行對應 Step 1+。
> **本 skill 只規範 YuDefine fleet 政策層**(compat flags hard rule、yudefine.com.tw zone DNS、CF token 來源、fleet consumer table、fleet-specific 踩坑)。
>
> **void.cloud 平台 SOP**(CLI 命令、`env.ts`、binding helper API、自動 migration、resource inference 等通用知識)走 **official `void` skill**(`.cursor/skills/void/`)或 **`void mcp`**(`search_docs` / `get_page` / `list_pages`),與 `void` npm package version lockstep。
## Step 0 — 詢問 deploy target(**必跑**)
向使用者用 `AskUserQuestion` 確認 deploy target,不要從專案名 / fleet 慣例直接推斷:
| 選項 | 適用 | Track |
|---|---|---|
| **(A) void.cloud** | 想用 void / VoidZero 平台(一鍵 deploy、自動 provision D1/R2、內建 drizzle / migration / log / domain CLI、無需自己管 CF 帳號 binding) | 走下方 Track A |
| **(B) 自有 Cloudflare 帳號** | 想用 `cloudflare/wrangler-action@v4` GitHub Actions 部署到自家 CF account(D1/R2 自管、wrangler.jsonc binding 明確、用 GitHub Secrets 對接) | 走下方 Track B |
**MUST** 把選項的取捨給使用者:
- (A) **void.cloud trade-off**:少寫 CI workflow、少管 binding,但鎖住 void 平台 + Vite+ 工具鏈 + 自訂網域走 cname.void.app
- (B) **自有 CF trade-off**:完全控制 binding / wrangler config / deploy 流程,但要自己寫 GitHub Actions + secrets sync + D1/R2 provisioning
**Fleet 現況**(只是 reference,**不**是限制;2026-07-14 更新):
- void.cloud(active):
- `yudefine-blog`(apex `yudefine.com.tw`)— current `void@0.10.x`、GitHub OIDC、無 legacy deploy patch
- `co-purchase`(`co-purchase.void.app`,未設自訂網域)— legacy `void@0.8.x`;升 current 後必須退役 patch 與長效 token
- `quotation-generator`(`quotation.yudefine.com.tw`)— legacy `@void-sdk/void@0.6.x`,待獨立 migration
- 自有 CF(wrangler-action):
- `perno` / `nuxt-edge-agentic-rag` / `yuntech-usr-sroi` / `TDMS` / `nuxt-supabase-starter`
- **`rental-scout`**(@nuxthub/core + wrangler-action;2026-05-27 仍未遷 void)
> 同一個 YuDefine 專案**可以**從 (B) 遷到 (A) 或反向 — 走 § 遷移 段。
## 觸發條件
使用者提到「部署到 YuDefine」、「deploy to yudefine」、要 setup 任何 YuDefine fleet 專案的 deploy pipeline、或要求把專案放到 `yudefine.com.tw`。
---
# Track A — void.cloud(YuDefine fleet 配置)
## A.1 Setup — 取得 official void skill + MCP
```bash
npx void init --agents
```
`void init --agents` 會:
- symlink `.cursor/skills/void` + `.cursor/skills/migrate-vite-cloudflare-to-void`(跟 `void` package version 對齊)
- 寫 `void mcp` 進 `.claude/settings.json`(offline docs query:`list_pages` / `get_page` / `search_docs`)
- patch `AGENTS.md` 加 Void instructions section
- patch `.gitignore` 加 Void defaults
- 自動 patch `nuxt.config.ts` / `vite.config.ts` 加 `voidPlugin()`(若缺)
> ⚠️ **不是 `pnpm dlx` / `pnpm add` 預裝再 init** — 用 `npx` 走最新 void package,避免本機 cache stale。
## A.2 後續操作走 official skill / MCP(hard rule)
**MUST**:
- 所有 void CLI 命令、config 細節、API surface、runtime helper、resource inference、auth flow、env.ts 等問題 **MUST** 走以下任一:
- `.cursor/skills/void/SKILL.md` + `docs/**/*.md`(symlink 跟 void package version 對齊)
- `void mcp` query (`list_pages` / `get_page docs/<path>.md` / `search_docs <keyword>`)
- 從 Vite + `@cloudflare/vite-plugin` 遷 void:走 `.cursor/skills/migrate-vite-cloudflare-to-void/`
**NEVER**:
- **NEVER** 從本檔複製 CLI 命令當權威 — 本檔不維護 CLI cache,official skill 跟 void package version lockstep
- **NEVER** 假設「之前可以這樣跑」就還能跑 — void CLI flag drift 是已知風險,每次跑前查 docs
## A.3 YuDefine Fleet 政策層
### A.3.1 compat flags hard rule
Current void 的 compatibility settings **MUST** 先查 official Nuxt integration,並與 `cloudflare-workers.md` § 3.2 對齊。以下配置 1/2/3 規則只適用 legacy `void@0.8.x`:
- **`appType: "framework"` (Nuxt / SvelteKit / Astro 等 — fleet 多數)**:**MUST** 配置 3(`["nodejs_compat", "nodejs_als", "no_nodejs_compat_v2"]`)
- 配置 2(純 v2)對 Nitro `cloudflare-module` preset **不可用** — Nitro build 主動 warn「`Please consider replacing nodejs_compat_v2 with nodejs_compat ... or USE IT AT YOUR OWN RISK as it can cause issues with nitro`」+ 撞 `Cannot read private member #t in get stdout`(yudefine-blog 2026-05-27 first-ever CI deploy 實證;blog 之前 prod live 是 user 本機 manual deploy 沒踩到)
- **`appType: "void"` (pure Vite+ void app,fleet 少見)**:配置 2 可用(無 Nitro 中間層、可直吃 workerd 原生 v2);配置 3 也行
- **禁配置 1**(`["nodejs_compat", "nodejs_als"]` 不含 no_v2)— 必撞 err 10021 `Cannot read private member #t ... in get stdout`
詳見 [`cloudflare-workers.md` § 3.2](../../../../rules/core/cloudflare-workers.md)、[`void 0.8 compat flags pitfall`](../../../../docs/pitfalls/2026-05-25-void-cloud-voidjson-compat-flags-10021.md)。
> Legacy 0.8 曾只採用 void.json flags;current void 可採用 wrangler compatibility settings,不得沿用「永遠不讀 wrangler」的舊假設。
### A.3.2 `compatibility_date` 對齊
`void.json` + `wrangler.jsonc` 的 `compatibility_date` **MUST** 對齊 void Nuxt 官方範例(`.cursor/skills/void/docs/integrations/frameworks/nuxt.md`)。2026-07-14 驗證的官方範例為 `2026-02-24`。
**MUST** 兩個檔對齊(void.json 是 deploy 真相層、wrangler.jsonc 是 dev binding 對齊用)。
### A.3.3 current void 強制
YuDefine fleet 新 consumer **MUST** 使用 current `void@0.10.x`(每次實作前再以 npm / official docs 驗證 latest),**禁用** legacy `@void-sdk/void@0.6.x` 與 `void@0.8.x`。
### A.3.4 Vite+ override
`pnpm-workspace.yaml` **MUST** override `vite` / `vitest` 成 VoidZero fork(voidPlugin 需要 `parseSync` export,純 vite 沒有):
```yaml
catalog:
vite: npm:@voidzero-dev/vite-plus-core@^0.1.22
vite-plus: 0.1.22
vitest: npm:@voidzero-dev/vite-plus-test@^0.1.22
overrides:
vite: 'catalog:'
vitest: 'catalog:'
peerDependencyRules:
allowAny: [vite, vitest]
allowedVersions: { vite: '*', vitest: '*' }
```
### A.3.5 NuxtHub gating
void.cloud + D1 **MUST NOT** 帶 `@nuxthub/core`(per `cloudflare-workers.md` § 1 矩陣第三列)— 用 `void/db` + `void/storage` 不用 NuxtHub helper。
### A.3.6 legacy `patch-void-deploy.ts` 退役規則
`void@0.8.x` 的 SQLite migration handler 走 `copyFileSync` 不 bundle deps(`deploy-OPo_tSWl.mjs:1994`,對比 postgres handler 走 `bundlePgMigrationHandler` rollup bundle),handler 內 `import "../canonical-json-XXX.mjs"` 指向沒被 emit + 也不在 worker upload set 的 path → CF Workers 撞 10021 internal error(per [void-sdk/void#52](https://github.com/void-sdk/void/issues/52))。
只有仍停在 legacy `void@0.8.x` 的 consumer 可暫時安裝 monkey-patch 繞掉:
```bash
cp ~/offline/clade/vendor/snippets/cloudflare-workers/patch-void-deploy.ts scripts/patch-void-deploy.ts
```
`package.json` postinstall:
```jsonc
"scripts": {
"postinstall": "nuxt prepare && node scripts/patch-void-deploy.ts"
}
```
Upstream void-sdk/void#52 已於 2026-05-27 關閉並在當日 release 修正。current void consumer **MUST NOT** 保留此 script、postinstall hook 或 unenv `patchedDependencies`。
### A.3.7 `package.json` scripts naming(pnpm 保留字陷阱)
**禁**把 deploy 命令命名 `deploy`:
```jsonc
// ❌ 錯誤
"scripts": { "deploy": "void deploy" }
// 結果:pnpm 把 `deploy` 當保留字(workspace deploy 命令),
// 跑 `pnpm deploy` 撞 ERR_PNPM_NOTHING_TO_DEPLOY,不會觸發 script
```
**MUST** 用 `void:deploy`(或其他帶 prefix 的 name):
```jsonc
"scripts": {
"void:deploy": "void deploy",
"void:env:check": "void env check"
}
```
CI workflow / chat invocation 一律走 `pnpm run void:deploy` 或 `npx void deploy`。
> Pitfall:`docs/pitfalls/2026-05-27-pnpm-deploy-reserved-word.md`(2026-05-27 新增)
## A.4 yudefine.com.tw 自訂網域 DNS
```bash
void domain add <hostname> --project <project> # 印出要設的 DNS 記錄
```
void 回傳 3 類記錄:**CNAME(流量)** → `cname.void.app`、**TXT `_cf-custom-hostname`**(所有權)、**TXT `_acme-challenge`**(SSL 驗證,可能 2 筆)。
yudefine.com.tw zone 在 **Cloudflare**(NS `*.ns.cloudflare.com`)。設記錄走 CF API(見下方 token):
- **子網域**(如 `rent.yudefine.com.tw`):CNAME → `cname.void.app`,**proxied=false(DNS-only / grey cloud)**。CF-for-SaaS 跨 CF 帳號 proxied 會觸發 1014「CNAME Cross-User Banned」,務必 grey。
- **apex**(`yudefine.com.tw`):一樣 CNAME @ → `cname.void.app` **proxied=false**;Cloudflare **CNAME flattening** 會壓成 A 記錄,**與既有 MX / SPF / TXT 共存**(郵件不受影響,實測 yudefine.com.tw 的 Zoho MX 不動)。
- 三筆 TXT 照 void 給的值建。apex 因 flatten 後對外是 A(非 CNAME),CF-for-SaaS 改靠 `_cf-custom-hostname` TXT **pre-validation** 驗所有權。
建完 `void domain status <hostname>` 追蹤:`pending` → `issuing_cert`(CF 簽 SSL,數分鐘)→ `active`。
CF API 建記錄範例(DNS-only CNAME):
```bash
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE}/dns_records" \
-H "Authorization: Bearer ${CF_DNS_TOKEN}" -H "Content-Type: application/json" \
-d '{"type":"CNAME","name":"<host>","content":"cname.void.app","proxied":false,"ttl":1}'
```
## A.5 Cloudflare 帳號 / token(僅自訂網域 DNS 需要)
固定值(非 secret):
```bash
CLOUDFLARE_ACCOUNT_ID=0eac599c12df10586d97a78179b9f11f
CLOUDFLARE_ZONE_ID=ffc8f1d16b67f9b1676f9dc7c782d4a5 # yudefine.com.tw
```
**Token(從 Notion `🔑 Scrects` 取)**:建 DNS 記錄需要 **Zone `DNS:Edit`** 權限。
- ✅ **用 `cfat_*`**(Notion「YuDefine - for vite-plugin-cloudflare-tunnel」)— 含 Account `Cloudflare Tunnel:Edit` + Zone `SSL:Edit` + **Zone `DNS:Edit`**。建 void 自訂網域 DNS 用這顆。
- ❌ **不要用 `cfut_*`**(「for Worker 通用」)— scope 只有 Workers/KV/R2/D1/Pages,**沒有 Zone DNS:Edit**,建 DNS 會失敗。
```
notion-fetch({ id: "https://www.notion.so/yudefine/Scrects-32fb791117218009a3d4deddf6364828" })
```
> void.cloud 部署本身**不需要** CF token(走 `void auth`)。CF token 只在「設自訂網域 DNS」這步用到。
## A.6 觀測 / 維運
走 `void mcp` 或 official `void` skill 查詢:
- 運行 log:`void project logs` / `void mcp` query
- 部署歷史 / 回滾:`void project rollback`
- Secrets / Domains / Migrations:CLI 對應命令見 `docs/reference/cli.md`
## A.7 Track A 注意事項速查(fleet-specific)
- **legacy 0.8 err 10021 `private member #t`** → 配置 1 或 framework 配置 2 的歷史相容性問題;current void 先查 official docs,不直接套舊 workaround
- **legacy 0.8 err 10021 純 internal error(無 stack)** → 可能是已修正的 SQLite handler emit bug;優先升 current void 並移除 patch,不在 current consumer 新裝 workaround
- **`pnpm deploy` 撞 `ERR_PNPM_NOTHING_TO_DEPLOY`** → scripts.deploy 改名 `void:deploy`(A.3.7)
- **`'vite' does not provide an export named 'parseSync'`** → 沒套 Vite+ override(A.3.4)
- **建 DNS 403 / 權限不足** → 用錯 token,要 `cfat_*` 不是 `cfut_*`(A.5)
- **自訂網域 1014 CNAME Cross-User Banned** → CNAME 設成 proxied,要改 DNS-only(A.4)
- **`void domain status` 卡 error「does not CNAME to this zone」** → apex 正常現象(flatten 成 A),靠 `_cf-custom-hostname` TXT pre-validation
- **drift-check `install latest drizzle-orm`** → void 平台層 pnpm temp-copy 問題(official skill `docs/guide/database.md` 有 SOP)
---
# Track B — 自有 Cloudflare 帳號(wrangler-action)
## 前置需求
- **CF 帳號** + 對應 API token 已加進 GitHub repo secrets(`CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID`),per `~/.cursor/rules/secrets.mdc`
- **D1 / R2** 已在 CF 帳號建好(手動或透過 Cloudflare MCP `d1_database_create` / `r2_bucket_create`)
- consumer 走 NuxtHub 派(`@nuxthub/core` + `hub: {}`)或 raw wrangler.jsonc 派,per `~/.cursor/rules/cloudflare-workers.mdc`
## B.1 CF 資源 provisioning
```bash
# D1(命名慣例:<consumer>-db)
wrangler d1 create <consumer>-db
# 拿到 uuid 填進 nuxt.config.ts hub.db.connection.databaseId 或 wrangler.jsonc
# R2(命名慣例:<consumer>-blob)
wrangler r2 bucket create <consumer>-blob
```
或走 Cloudflare MCP:`mcp__claude_ai_Cloudflare_Developer_Platform__d1_database_create` / `r2_bucket_create`。
## B.2 wrangler.jsonc + nuxt.config.ts binding
兩種 pattern 擇一,per `~/.cursor/rules/cloudflare-workers.mdc` § 4:
**Pattern A**(D1-only 或簡單):binding 寫進 `nuxt.config.ts` `hub.db.connection`,wrangler.jsonc 不寫 `d1_databases`
**Pattern B**(多 binding / DO / AI):binding 全寫進 `wrangler.jsonc`,`hub: {}` 只啟用 helper
詳見 cloudflare-workers.md。
## B.3 GitHub Actions deploy.yml
```yaml
name: Deploy
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
deployments: write
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false
jobs:
deploy:
runs-on: [self-hosted, gh-runner-lxc] # 或 ubuntu-latest
steps:
- uses: actions/checkout@v6
# self-hosted runner 釘 v5:v6 的 pnpm 自我更新會壞掉 persistent runner 上的既有安裝
- uses: pnpm/action-setup@v5
- uses: actions/setup-node@v6
with:
node-version: 24
- run: pnpm config set store-dir /home/runner/.pnpm-store
# wrangler-action@v4 找 $PNPM_HOME/bin/pnpm(v11 layout),pnpm v10 的版面是
# $PNPM_HOME/pnpm。MUST 用 cp,NEVER `ln -sf "$(which pnpm)"`(會 self-link)。
- run: |
if [ -f "$PNPM_HOME/pnpm" ]; then
mkdir -p "$PNPM_HOME/bin"
cp -f "$PNPM_HOME/pnpm" "$PNPM_HOME/bin/pnpm"
fi
- run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build
env:
NITRO_PRESET: cloudflare_module
- name: Apply D1 migrations
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: d1 migrations apply <DB-binding> --remote --config .output/server/wrangler.json
- name: Deploy Worker
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: .output
command: deploy
secrets: |
# 列出所有 runtime secret name
...
env:
# 對應從 GitHub secret pull
...
```
## B.4 自訂網域
走 Cloudflare dashboard 加 Worker route(`<host>.example.com/*` → worker-name),或在 `wrangler.jsonc` `routes` 設好讓 deploy 時自動建。詳見 Cloudflare Workers docs。
## Track B 驗證
- GitHub Actions 跑綠燈
- `curl -I https://<worker>.workers.dev` 應回 200
- 自訂網域:dashboard Worker route 設好後 `curl -I https://<host>` 應回 200
---
# § 遷移(Track B ↔ Track A)
**MUST Read [migration.md](migration.md) before proceeding with any platform migration.**
B → A(NuxtHub → void.cloud)是 fleet 主要遷移方向(10 phase,已實證 co-purchase)。A → B(void → 自有 CF)較少見、保留 high-level。完整步驟見 [migration.md](migration.md)。
---
## 反向相容性
`/yudefine-deploy` 不限制單一 target — 同一 fleet 內可混合多種 deploy 派。Step 0 詢問是「該專案此次的選擇」,不會把 fleet 鎖死成單一 track。
Scanned 9/5/2026
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!