# Prompt — NPM-003: GitHub Packages publish workflow + package.json + RELEASING.md (v0.1 publish 阻塞最后一项)

> **角色**：executor（被 plan owner 调用 / 或单独跑）
> **范围**：3 个文件操作 — 新建 `.github/workflows/publish.yml` / 修 `package.json` / 新建 `docs/RELEASING.md`
>
> ⚠️ **不要 commit / 不要 git add** — dirty 累积到 plan owner 复审通过后由 user 决定 commit
> ⚠️ 完成后 **STOP**，按 §4 报告格式回报
> ⚠️ **不扩范围**：不删 git tag（user 手动）/ 不实际 publish / 不配置 GitHub repo settings / 不动 license / 不动 CHANGELOG.md / 不改 src/ / 不动现有别 session dirty 文件
> ⚠️ **不做的事**：不删 local `v0.1.0` tag（user 命令一行搞定）/ 不 commit 现有 dirty 文件（含 Phase 1 audit 副作用：`published-vs-code-audit.md` / `tokenized-diff-report.json` / `_metrics/`）

---

## §0 — Plan owner 已定决策（user 拍板 2026-05-11）

User 在 NPM-003 backlog entry + Phase 1 baseline 复审后拍板**全部决策**：

- **D1 = B 路线** — 发到 **GitHub Packages**（`https://npm.pkg.github.com`，scope `@tvu`）；license 保 `UNLICENSED`；外部 consumer install 需配 `.npmrc` auth
- **D2 = tag-triggered + changesets 集成** — `git tag v*.*.* && git push --tags` 触发 CI；CI workflow 用 `${{ secrets.GITHUB_TOKEN }}` + `permissions: packages: write`；不加 husky/cron 主动提醒（0.1.0 保持简单）
- **D3 = Option A（7 strict + 2 warn-only）** — 基于 Phase 1 baseline（7 PASS / 2 FAIL）：
  - **7 strict（任一 fail 阻 publish；放进 `prepublishOnly`）**：
    1. `audit:icon-fill-currentcolor`
    2. `audit:no-hardcoded-design-tokens`
    3. `audit:component-no-inline-svg`
    4. `audit:figma-conformance`
    5. `audit:docs-site`
    6. `audit:component-tokens`
    7. `audit:tokenized-diff`
  - **2 warn-only（continue-on-error；publish 不阻塞，只 log）**：
    1. `audit:design-system`（baseline FAIL：Tab.vue 硬编码 `#1f1f1f` / `#353535` + Icon.vue / Logo.vue raw SVG binding — 等 follow-up backlog 修完后升 strict）
    2. `audit:published-vs-code`（baseline FAIL：Components 36 matched / 2 figma-only 漂移 — 等 follow-up backlog 修完后升 strict）
- **Version 模型 = X 路线** — 0.1.0 = 首次发布（CHANGELOG `[0.1.0]` 已固化 commit `7e54d50d`，包含 103 commits 的 foundation 工作）；0.1.1+ 起每次发布严格走 changeset → bump → tag → push 流程
- **v0.1.0 tag handling = user 手动**（不在本 prompt 范围）：执行后 user 跑 `git tag -d v0.1.0 && git tag v0.1.0 && git push origin master --tags` 触发首次 publish

executor 不需重新决策，**直接按 §2 写定文件**。

---

## §1 — 上下文 / 现状基线（Phase 1 已 verify）

### 1.1 项目位置

- 项目目标见 [`AGENTS.md`](../../../AGENTS.md) + [`docs/PROJECT_GOAL.md`](../../PROJECT_GOAL.md)
- v0.1 publish 阻塞物：NPM-001 ✅（commit `7e54d50d`）/ NPM-002 ✅（commit `babee0fd`）/ INFRA-F27 ✅（commit `5125e625`）/ **NPM-003（本任务最后一项 — 完成后 v0.1 publish 解锁）**

### 1.2 当前 package.json 关键字段（Phase 1 已实测，本任务改 3 字段）

| 字段 | 当前 | 本任务改成 | 原因 |
|---|---|---|---|
| `name` | `@tvu/design-system` | **不动** | scope `@tvu` 与 D1=B GitHub Packages 吻合 |
| `version` | `0.1.0` | **不动** | Version 模型 X — 0.1.0 = 首次发布 |
| `license` | `UNLICENSED` | **不动** | D1=B GitHub Packages 私域可保 UNLICENSED |
| `repository.url` | `git+https://github.com/NancyZeng0210/TVU-Design-System.git` | **不动** | 与 GitHub Packages auto-link 吻合 |
| `prepare` | `vue-tsc --noEmit && vite build && build-icon-dist.mjs` | **不动** | pnpm install 时跑 typecheck + build，CI 用 |
| `prepublishOnly` | 不存在 | **新增**（7 strict audit 串）| publish 前最后 gate；任一 fail 阻 publish |
| `engines` | 不存在 | **新增** `{ "node": ">=20" }` | CI runs-on Node 20，对外 consumer 也约束 |
| `publishConfig` | 不存在 | **新增** `{ "registry": "https://npm.pkg.github.com", "access": "restricted" }` | GitHub Packages registry 指向 |
| `files` | `["dist"]` | **不动** | 仅 dist/ 打包入 npm tarball |

### 1.3 现有 .github/workflows/ 目录（Phase 1 已 verify）

- 仓库根 0 `.github/` 目录 ✅
- 本任务新建首个 workflow file：`.github/workflows/publish.yml`

### 1.4 changesets 工具链已就位（NPM-001 落地物）

- `@changesets/cli@^2.31.0` devDep ✅
- `.changeset/config.json`：access=`restricted` / baseBranch=`master` ✅
- 3 npm script alias（changeset / changeset:status / changeset:version）✅

### 1.5 Pre-flight 状态

- 本地 = origin/master，无未推 commit ✅
- 别 session 进行中 6+3 个 dirty 文件（Phase 1 audit 副作用 + pickup §4）—— **本任务不动**
- 现有 local `v0.1.0` tag 指向 `49fb8f1e`（stale placeholder，user 在执行后手动删 + 重建在 HEAD —— **不在 executor 范围**）

---

## §2 — 任务（按顺序执行）

### 2.1 新建 `.github/workflows/publish.yml`

需先建目录 `.github/workflows/`，然后写入下面**一字不改内容**：

````yaml
name: Publish to GitHub Packages

on:
  push:
    tags:
      - 'v*.*.*'

permissions:
  contents: read
  packages: write

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v3
        with:
          version: 10.28.2

      - name: Setup Node + GitHub Packages registry
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: 'https://npm.pkg.github.com'
          scope: '@tvu'
          cache: 'pnpm'

      - name: Install (triggers prepare → typecheck + build)
        run: pnpm install --frozen-lockfile

      # Warn-only audit gates — failures logged as workflow warnings, do not block publish
      - name: audit:design-system (warn-only)
        continue-on-error: true
        run: pnpm audit:design-system

      - name: audit:published-vs-code (warn-only)
        continue-on-error: true
        run: pnpm audit:published-vs-code

      # Strict audit gates run via prepublishOnly inside `pnpm publish`
      - name: Publish to GitHub Packages
        run: pnpm publish --no-git-checks
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
````

**`--no-git-checks` 原因**：audit 脚本（特别 `audit:tokenized-diff` / `audit:published-vs-code`）在 CI 跑时可能写出 report 产物到 `figma-data/normalized/` / `docs/internal/`，触发 pnpm publish 默认的 ERR_PNPM_GIT_UNCLEAN 阻断。用 `--no-git-checks` 绕过；strict audit 已在 `prepublishOnly` 拦关键 fail。

### 2.2 修改 `package.json`

**位置精确指示**：

#### (a) 在 `scripts` 段加 `prepublishOnly`（位置：现有 `"prepare"` 之后，`"changeset"` 之前；保持其它 script 顺序不动）

```json
"prepublishOnly": "pnpm run audit:icon-fill-currentcolor && pnpm run audit:no-hardcoded-design-tokens && pnpm run audit:component-no-inline-svg && pnpm run audit:figma-conformance && pnpm run audit:docs-site && pnpm run audit:component-tokens && pnpm run audit:tokenized-diff",
```

#### (b) 在 `peerDependencies` 段**之前**新增 `engines` 段

```json
"engines": {
  "node": ">=20"
},
```

#### (c) 在 `engines` 段**之后**、`peerDependencies` 段**之前**新增 `publishConfig` 段

```json
"publishConfig": {
  "registry": "https://npm.pkg.github.com",
  "access": "restricted"
},
```

**最终 package.json `engines` + `publishConfig` 位置示意**：

```json
{
  ...
  "scripts": { ..., "prepublishOnly": "...", ... },
  ...
  "files": ["dist"],
  "engines": {
    "node": ">=20"
  },
  "publishConfig": {
    "registry": "https://npm.pkg.github.com",
    "access": "restricted"
  },
  "peerDependencies": {
    "vue": "^3.4.0"
  },
  "devDependencies": { ... }
}
```

JSON 语法注意：每个新增段末尾的 `,` 与下一段开头一致；`peerDependencies` 前的段末尾必须含 `,`。

### 2.3 新建 `docs/RELEASING.md`

完整 inline 内容（**executor 一字不改 copy 进文件**）：

````markdown
# Releasing — TVU Design System

This document describes the release flow for `@tvu/design-system` published to GitHub Packages.

> **Target audience**: maintainers releasing new versions.
> **External consumers**: see [`README.md`](../README.md) install section + the "External consumer setup" section below for `.npmrc` config.

---

## Pre-release checklist

Before releasing any version:

- [ ] On `master` branch, fully synced with `origin/master` (`git status` clean, `git log origin/master..HEAD` empty)
- [ ] All intended changes committed and pushed
- [ ] At least one `.changeset/*.md` file exists for the release (for 0.1.1+; not required for initial 0.1.0)
- [ ] Local typecheck passes: `pnpm exec vue-tsc --noEmit`
- [ ] Local build succeeds: `pnpm build`
- [ ] 7 strict audits pass locally (`prepublishOnly` will enforce this in CI too):
  ```bash
  pnpm run audit:icon-fill-currentcolor
  pnpm run audit:no-hardcoded-design-tokens
  pnpm run audit:component-no-inline-svg
  pnpm run audit:figma-conformance
  pnpm run audit:docs-site
  pnpm run audit:component-tokens
  pnpm run audit:tokenized-diff
  ```

---

## Initial 0.1.0 release (one-time)

`v0.1.0` was created early as a placeholder pointing at a stale commit. Before first publish, move it to current `HEAD`:

```bash
# Delete stale local tag (it was never pushed to origin)
git tag -d v0.1.0

# Recreate at current HEAD
git tag v0.1.0

# Push tag — this triggers .github/workflows/publish.yml in CI
git push origin master --tags
```

CI will then run typecheck + build + 7 strict audits + 2 warn-only audits + `pnpm publish` to GitHub Packages.

Verify success at: `https://github.com/NancyZeng0210/TVU-Design-System/packages`

---

## Standard release flow (0.1.1+)

For every release after 0.1.0:

### Step 1 — Accumulate changesets (every meaningful PR)

```bash
pnpm changeset
```

The CLI will:
- Ask which bump type (`patch` / `minor` / `major`)
- Ask for a one-line summary
- Generate `.changeset/<random-name>.md` with your summary + version bump intent

Commit the generated file as part of your PR.

### Step 2 — Decide release time + run version bump

When ready to publish (e.g. you accumulated several changesets and want to ship them):

```bash
# View pending changesets
pnpm changeset:status

# Aggregate changesets → bump package.json version + update CHANGELOG.md
pnpm changeset:version
```

This will:
- Read all `.changeset/*.md` files
- Determine new version (max bump type wins)
- Update `package.json` `version` field
- Append a new section to `CHANGELOG.md` aggregating all changeset summaries
- Delete consumed `.changeset/*.md` files

### Step 3 — Commit + tag + push

```bash
git add -A
git commit -m "release v<NEW_VERSION>"
git tag v<NEW_VERSION>
git push origin master --tags
```

CI auto-publishes on tag push.

---

## Audit gate policy

| Audit | Gate | Behavior on failure |
|---|---|---|
| `audit:icon-fill-currentcolor` | **strict** | Blocks publish |
| `audit:no-hardcoded-design-tokens` | **strict** | Blocks publish |
| `audit:component-no-inline-svg` | **strict** | Blocks publish |
| `audit:figma-conformance` | **strict** | Blocks publish |
| `audit:docs-site` | **strict** | Blocks publish |
| `audit:component-tokens` | **strict** | Blocks publish |
| `audit:tokenized-diff` | **strict** | Blocks publish |
| `audit:design-system` | warn-only | Logs warning; does not block |
| `audit:published-vs-code` | warn-only | Logs warning; does not block |

2 warn-only audits will upgrade to strict once their FAIL items are resolved (see follow-up backlog `CANONICAL-007` / `BRIDGE-MOCKUP-004`).

---

## External consumer setup

To install `@tvu/design-system` from GitHub Packages, consumers need a GitHub Personal Access Token (PAT) with `read:packages` scope.

### Step 1 — Create PAT

`https://github.com/settings/tokens/new` → scope `read:packages` → generate.

### Step 2 — Configure `.npmrc`

In the consumer project root (or `~/.npmrc` for global):

```ini
@tvu:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=YOUR_PAT_HERE
```

> Do not commit `.npmrc` containing the token. Use environment variable interpolation if needed:
> ```ini
> //npm.pkg.github.com/:_authToken=${GITHUB_PACKAGES_PAT}
> ```

### Step 3 — Install

```bash
pnpm add @tvu/design-system
```

---

## Troubleshooting

| Symptom | Cause | Fix |
|---|---|---|
| `pnpm publish` exits with `ERR_PNPM_GIT_UNCLEAN` | Audit scripts wrote report files | CI workflow uses `--no-git-checks` to bypass; local publish: commit reports or use the same flag |
| CI `Publish` step fails with `401 Unauthorized` | Missing `permissions: packages: write` or wrong token | Check workflow `permissions:` block and `NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}` env |
| CI workflow not triggered after tag push | Tag pattern mismatch (must be `vX.Y.Z`) | Use semver-compatible tag like `v0.1.0`, not `0.1.0` or `release-0.1.0` |
| `prepublishOnly` audit chain fails locally | One of 7 strict audits has FAIL output | Run the failing audit alone (`pnpm run audit:<name>`); fix code; retry |
| `audit:design-system` / `audit:published-vs-code` always warns in CI | Known baseline FAIL items (see `CANONICAL-007` / `BRIDGE-MOCKUP-004` backlog) | Track in backlog; do not block publish |

---

## Reference

- Workflow file: [`.github/workflows/publish.yml`](../.github/workflows/publish.yml)
- changesets config: [`.changeset/config.json`](../.changeset/config.json)
- CHANGELOG: [`CHANGELOG.md`](../CHANGELOG.md)
- semver pre-1.0 rules: [https://semver.org/#spec-item-4](https://semver.org/#spec-item-4)
````

---

## §3 — 验证（exit 0 / 输出预期）

执行完 §2 全部步骤后，按下面 verify：

| 命令 | 预期输出 / exit |
|---|---|
| `ls .github/workflows/publish.yml` | 存在 |
| `grep -c "^on:" .github/workflows/publish.yml` | `1` |
| `grep -c "tags:" .github/workflows/publish.yml` | `1`（trigger pattern） |
| `grep -c "permissions:" .github/workflows/publish.yml` | `1` |
| `grep -c "packages: write" .github/workflows/publish.yml` | `1` |
| `grep -c "continue-on-error: true" .github/workflows/publish.yml` | `2`（2 warn-only audits） |
| `grep -c "NODE_AUTH_TOKEN" .github/workflows/publish.yml` | `1` |
| `node -e "const p = require('./package.json'); console.log(p.scripts.prepublishOnly ? 'YES' : 'NO');"` | `YES` |
| `node -e "const p = require('./package.json'); console.log(p.engines && p.engines.node);"` | `>=20` |
| `node -e "const p = require('./package.json'); console.log(p.publishConfig && p.publishConfig.registry);"` | `https://npm.pkg.github.com` |
| `node -e "const p = require('./package.json'); console.log(p.publishConfig && p.publishConfig.access);"` | `restricted` |
| `node -e "const p = require('./package.json'); console.log(p.scripts.prepublishOnly.split('&&').length);"` | `7`（7 strict audit chain） |
| `ls docs/RELEASING.md` | 存在 |
| `grep -c "^## " docs/RELEASING.md` | ≥ `5`（Pre-release / Initial 0.1.0 / Standard 0.1.1+ / Audit gate policy / External consumer setup / Troubleshooting / Reference） |
| `grep -c "pnpm changeset" docs/RELEASING.md` | ≥ `3` |
| `grep -c "git tag v" docs/RELEASING.md` | ≥ `2` |
| `pnpm exec vue-tsc --noEmit; echo "exit: $?"` | `exit: 0`（package.json 字段不影响 typecheck） |
| `node -e "JSON.parse(require('fs').readFileSync('./package.json', 'utf-8'))"; echo "exit: $?"` | `exit: 0`（package.json 仍是 valid JSON） |

**不在本任务验证**：实际 `pnpm publish` / 实际 tag push / 实际 CI run — 这些是 user 在 §0 出范围动作（删 + 重建 v0.1.0 tag + push）后才会发生。

---

## §4 — 报告格式（完成后 STOP 回报到对话）

```
## NPM-003 执行报告

### 文件改动
- 新增：.github/workflows/publish.yml (XX 行)
- 修改：package.json（+3 字段：prepublishOnly / engines / publishConfig；+N/-M 行）
- 新增：docs/RELEASING.md (XX 行)

### 验证
- §3 全部 17 条命令 PASS / FAIL（贴关键输出）
- package.json JSON 仍 valid ✅/❌
- vue-tsc typecheck 通过 ✅/❌

### 未解决项 / blocker
- 无 / 列出

### Out-of-scope 提示（给 user）
- 手动跑 user 触发首次 publish 的命令链（不在本 prompt 范围）：
  ```
  git tag -d v0.1.0
  git tag v0.1.0
  git push origin master --tags
  ```
- 现有 dirty 文件（Phase 1 audit 副作用 + 别 session）的 commit 决策仍由 user 单独定

STOP — 等 plan owner 复审 + user 拍板 commit。
```

---

## §5 — Blocker 自检（pre-flight 强制）

executor 在跑 §2 之前 verify，命中任一 → STOP，按 §4 仅填"未解决项"：

- [ ] `git status --short` 应只有：Phase 1 audit 副作用 + 别 session 进行中产物（pickup §4）+ 本 prompt 文件本身 ?? 状态。**不应**有 `.github/` / `package.json` / `docs/RELEASING.md` 已经被改的痕迹（如有 → blocker，可能别 session 已动）
- [ ] `git log origin/master..HEAD` 应为空（0 未推 commit）。如非空 → blocker
- [ ] `ls .github/workflows/ 2>&1 | head -1` 应是 "no such file or directory"。如已存在 → blocker（不要 overwrite）
- [ ] `ls docs/RELEASING.md 2>&1 | head -1` 应是 "no such file"。如已存在 → blocker
- [ ] `grep '"prepublishOnly"' package.json` 应 0 命中。如已存在 → blocker（不要 overwrite）
- [ ] `grep '"engines"' package.json` 应 0 命中。如已存在 → blocker
- [ ] `grep '"publishConfig"' package.json` 应 0 命中。如已存在 → blocker

---

## §6 — 反模式自检（plan owner 已过，executor 不需重做；列在此供审阅）

| # | 反模式 | 自检结论 |
|---|---|---|
| 1 | 硬编码项目级规则到工具 | ✗ workflow 跑现有 `package.json` audit alias；audit 真源在 figma-sync/*.mjs；workflow 不内嵌规则 |
| 2 | 打补丁方案 | ✗ publish.yml + prepublishOnly 是 mainline 发布工程实践，非补丁 |
| 3 | to-do list 思维（无产出契约） | ✗ §3 验证 17 条机械化（YAML 关键字段计数 / JSON 字段存在性 / RELEASING.md section count）|
| 4 | 没问下游怎么消费 | ✗ 下游：外部 consumer 装包 from GitHub Packages → `.npmrc` 配 PAT → `pnpm add @tvu/design-system`；RELEASING.md §External consumer setup 已写明 |
| 5 | 扩展时改哪里 | ✗ 加新 audit 到 strict gate = `package.json` `prepublishOnly` chain 末尾加 `&& pnpm run audit:<new>`（一处）；从 warn 升 strict 同样一处 |

---

## §7 — 关联

- backlog entry: [`docs/internal/backlog.md`](../backlog.md) §NPM-003
- handoff: [`_plans/next-session-pickup-2026-05-11.md`](../_plans/next-session-pickup-2026-05-11.md) §3 第 1 步
- 系统性 review: [`_reports/systematic-review-2026-05-09.md`](../_reports/systematic-review-2026-05-09.md) §1.3
- Phase 1 baseline 数据：本对话已固化（7 PASS / 2 FAIL；plan owner 已据此决定 D3=Option A 分层）
- 前置依赖：
  - NPM-001 ✅（changesets 工具链就位，commit `7e54d50d`）
  - NPM-002 ✅（README registry install + GETTING_STARTED.md，commit `babee0fd`）
  - INFRA-F27 ✅（build-icon-dist currentColor 修复，commit `5125e625`）
- Follow-up（plan owner 在 NPM-003 commit 后写 backlog entry，**不在 executor 范围**）：
  - `CANONICAL-007` — Tab.vue 硬编码 `#1f1f1f` / `#353535` + Icon.vue / Logo.vue raw SVG binding（解锁 `audit:design-system` 升 strict）
  - `BRIDGE-MOCKUP-004` — 2 figma-only components 漂移（解锁 `audit:published-vs-code` 升 strict）
- 后续：v0.1 publish（user 自跑 §0 出范围命令触发）→ NPM-003 闭环 → v0.1 publish 阻塞链解锁
