@qijenchen/design-system 0.1.0-beta.59 → 0.1.0-beta.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md
CHANGED
|
@@ -177,13 +177,13 @@ CLAUDE.md target ≤ 200(Anthropic best-practice)/ transition ≤ 400 / hard cap
|
|
|
177
177
|
| shadcn compat alias 回流 | dark mode 不聯動 |
|
|
178
178
|
| `asChild ? Slot : Native` 內部 JSX 仍渲染多 children | React.Children.only runtime fail — Slot 規範 children 必為單 element,內部 `{icon}{label}{badge}` 多 expression 變 array → throw。asChild 分支 render 只傳 consumer child;tsc/build 過,story 打開才炸 |
|
|
179
179
|
| `tsc -b` 不 emit declaration | 漏 TS4023「cannot be named」;改 export/型別 surface 的 deploy-safety 必跑 `npm run build:lib`(= Netlify build:dts emit .d.ts),tsc -b 全綠會騙過(2026-06-05 Badge union 連掛 3 Netlify build)|
|
|
180
|
+
| `rsync -a` 對「等長 + 同秒」檔靜默跳過 | `-a` 用 size+mtime quick-check;clone 與 build 同秒寫、版本字串等長(`^…beta.NN` 恆 130B)→ 判定沒變不複製 → ship stale。必 `--checksum` + 寫後 fail-closed 斷言(2026-06-07 mirror beta.56-58 連環 ship stale package.json,3-agent 對抗調查 + 本地 `-a` vs `-ac` 復現才抓到)|
|
|
180
181
|
|
|
181
182
|
新 bug → 歸 Meta-Pattern OR 本表 1 行;> 10 條 = 漏寫,**評估 meta-merge 既有 M-rule 而非無腦新增**(meta-patterns velocity ≤ 3/quarter,單 M-rule 必吸收 ≥ 3 prior bugs)。
|
|
182
183
|
|
|
183
184
|
# 專案 Stack
|
|
184
185
|
|
|
185
|
-
Vite + React + TypeScript + Tailwind v4 + shadcn/ui + Storybook + 自訂 Design Token
|
|
186
|
-
必要檔案:`index.html` / `src/main.tsx` / `src/globals.css` / `vite.config.ts` / `package.json` / `tsconfig.json`。
|
|
186
|
+
Vite + React + TypeScript + Tailwind v4 + shadcn/ui + Storybook + 自訂 Design Token。必要檔案:`index.html` / `src/main.tsx` / `src/globals.css` / `vite.config.ts` / `package.json` / `tsconfig.json`。
|
|
187
187
|
完整路徑 + Token 系統 → `packages/design-system/src/tokens/README.md`(charter)— Phase 1 後 DS 內化在 npm workspace。
|
|
188
188
|
|
|
189
189
|
# Path-scoped rules(2026 Anthropic 推薦)
|
|
@@ -36,11 +36,14 @@ CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null)
|
|
|
36
36
|
[ "$TOOL" != "Bash" ] && exit 0
|
|
37
37
|
|
|
38
38
|
# Heuristic:detect `git push origin <branch>` patterns
|
|
39
|
-
# 2026-06-06 fix:要求 `git push`
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
|
|
39
|
+
# 2026-06-06 fix:要求 `git push` 在「命令邊界」(行首 / ; / && / || / $( )擋字串提及(`P='git push origin'`)。
|
|
40
|
+
# 2026-06-07 fix(對抗 sweep):容忍 env-prefix(`GIT_SSH_COMMAND=… git push`)/ `git -C <dir>` /
|
|
41
|
+
# push 前後任意 flag(`--force-with-lease` / `-f` / `--set-upstream` / `--no-verify` 等)。原本只認 `-u`
|
|
42
|
+
# → 漏 fire 在這些「真實 push」上 = 該吐 URL 卻沒吐,defeat hook purpose。
|
|
43
|
+
_ENVP='([A-Za-z_][A-Za-z0-9_]*=[^[:space:]]+[[:space:]]+)*'
|
|
44
|
+
_GITPRE='git[[:space:]]+(-C[[:space:]]+[^[:space:]]+[[:space:]]+|-[A-Za-z][^[:space:]]*[[:space:]]+)*push'
|
|
45
|
+
_PUSHFLAGS='([[:space:]]+-{1,2}[A-Za-z][^[:space:]]*)*'
|
|
46
|
+
if ! echo "$CMD" | grep -qE "(^|[;&|(])[[:space:]]*${_ENVP}${_GITPRE}${_PUSHFLAGS}[[:space:]]+origin\b"; then
|
|
44
47
|
exit 0
|
|
45
48
|
fi
|
|
46
49
|
|
|
@@ -50,21 +53,35 @@ if echo "$CMD" | grep -qE 'push\s+origin\s+--delete'; then
|
|
|
50
53
|
fi
|
|
51
54
|
|
|
52
55
|
CWD=$(pwd)
|
|
56
|
+
# 2026-06-07 fix:偵測「真實 working dir」作 deploy-target 偵測根,跨 repo push 不吐「錯 repo」URL
|
|
57
|
+
# (anchor:syncing ds-product-template 時吐成 DS 站台 URL)。優先 `git -C <dir>`;否則命令鏈中
|
|
58
|
+
# 「最後一個 cd」(= push 時 effective cwd,**非第一個**;`echo x && cd /other && git push` 必用 /other)。
|
|
59
|
+
_GITC=$(echo "$CMD" | grep -oE 'git[[:space:]]+-C[[:space:]]+[^[:space:]]+' | head -1 | sed -E 's/.*-C[[:space:]]+//; s/^"//; s/"$//')
|
|
60
|
+
_LASTCD=$(echo "$CMD" | grep -oE '(^|[;&|(])[[:space:]]*cd[[:space:]]+[^;&|]+' | tail -1 | sed -E 's/.*cd[[:space:]]+//; s/[[:space:]]+$//; s/^"//; s/"$//')
|
|
61
|
+
for _d in "$_GITC" "$_LASTCD"; do
|
|
62
|
+
if [ -n "$_d" ] && [ -d "$_d" ] && git -C "$_d" rev-parse --git-dir >/dev/null 2>&1; then CWD="$_d"; break; fi
|
|
63
|
+
done
|
|
53
64
|
URLS_FOUND=""
|
|
54
65
|
# 2026-06-07 ROOT fix:只從「git push ... origin <ref>」這一段擷取 branch,不是整條 compound command 的
|
|
55
66
|
# 第一個 `origin X`。否則 `git fetch origin --quiet && git push origin main` 會誤抓 fetch 段的 `--quiet`
|
|
56
67
|
# 當 branch → 推導 `--quiet--site` 404(此前 5 道 guard 都沒擋到,因 `--quiet` 全是合法 git ref 字元)。
|
|
57
68
|
# 隔出 push 段(到下個 command 分隔 ; & | 為止)後,取 origin 後第一個 token。
|
|
58
|
-
PUSH_SEG
|
|
69
|
+
# PUSH_SEG 用同一 env/-C/flag-tolerant prefix 隔出 push 段(到下個分隔 ; & | 為止),再取 origin 後第一 token
|
|
70
|
+
PUSH_SEG=$(echo "$CMD" | grep -oE "(^|[;&|(])[[:space:]]*${_ENVP}${_GITPRE}[^;&|]*" | head -1)
|
|
59
71
|
BRANCH=$(echo "$PUSH_SEG" | grep -oE 'origin[[:space:]]+[^[:space:]]+' | head -1 | awk '{print $2}')
|
|
60
72
|
# origin 後第一個 token 是 flag(`git push origin --force` = 無顯式 branch)→ 清空走 current-branch fallback
|
|
61
73
|
case "$BRANCH" in -*) BRANCH="" ;; esac
|
|
62
74
|
# 2026-06-06 fix:refspec `src:dst` → 取 dst(`HEAD:main` → `main`),否則推導出 `HEAD:main--site` 404 URL
|
|
63
75
|
case "$BRANCH" in *:*) BRANCH="${BRANCH##*:}" ;; esac
|
|
64
|
-
# 2026-06-
|
|
65
|
-
#
|
|
76
|
+
# 2026-06-07 fix:full-ref dst — `HEAD:refs/heads/main` → `main`(否則 `refs/heads/main--site` 誤判 preview);
|
|
77
|
+
# `refs/tags/...` → tag push,skip
|
|
78
|
+
case "$BRANCH" in refs/tags/*) exit 0 ;; esac
|
|
79
|
+
BRANCH="${BRANCH#refs/heads/}"
|
|
80
|
+
# 2026-06-06 fix:`git push origin HEAD`(或 `@`)= symbolic ref 指向當前 branch → 解析成真 branch 名。
|
|
81
|
+
# 2026-06-07 fix:detached HEAD 時 show-current 回「空字串 + exit 0」→ BRANCH 留空,交給下方 value-based fallback
|
|
82
|
+
# (原 `|| echo ""` 沒用,因 git 不是失敗而是成功回空)。
|
|
66
83
|
if [ "$BRANCH" = "HEAD" ] || [ "$BRANCH" = "@" ]; then
|
|
67
|
-
BRANCH=$(git -C "$CWD" branch --show-current 2>/dev/null
|
|
84
|
+
BRANCH=$(git -C "$CWD" branch --show-current 2>/dev/null)
|
|
68
85
|
fi
|
|
69
86
|
# 2026-06-06 fix:BRANCH 含非法 git ref 字元(" ' 空白 \ 等)→ 此命令只是「字串裡含 git push origin」
|
|
70
87
|
# (測試迴圈 / 文件 / echo),非真推送 → skip,避免把 `main"` 等垃圾推導成 404 URL 注入 context。
|
|
@@ -74,7 +91,10 @@ if [ -n "$BRANCH" ] && ! echo "$BRANCH" | grep -qE '^[A-Za-z0-9._/-]+$'; then ex
|
|
|
74
91
|
# 否則把 tag 名當 branch 推導出 `v0.1.0-beta.56--site` 404 URL(release tag push 每次都誤吐)
|
|
75
92
|
if echo "$BRANCH" | grep -qE '^v[0-9]'; then exit 0; fi
|
|
76
93
|
if [ -n "$BRANCH" ] && git -C "$CWD" rev-parse --verify --quiet "refs/tags/$BRANCH" >/dev/null 2>&1; then exit 0; fi
|
|
77
|
-
|
|
94
|
+
# 2026-06-07 fix:仍空(bare `git push origin` / detached HEAD / 解析不出)→ 取 current branch(value-based);
|
|
95
|
+
# 仍空 → 無法產生合法 URL(會變 `https://--site` malformed)→ skip,不亂猜 main。
|
|
96
|
+
[ -z "$BRANCH" ] && BRANCH=$(git -C "$CWD" branch --show-current 2>/dev/null)
|
|
97
|
+
[ -z "$BRANCH" ] && exit 0
|
|
78
98
|
|
|
79
99
|
# v3 2026-05-27:curl HEAD verify URL before reporting(per user「你確定有做到」complaint)
|
|
80
100
|
# v4 2026-05-27:add content sniff(防 squat URLs 200 但 unrelated content)
|
|
@@ -147,18 +167,28 @@ if [ -z "$URLS_FOUND" ] && [ -f "$CWD/netlify.toml" ]; then
|
|
|
147
167
|
if [ -n "$REPO_NAME" ]; then CANDIDATES="$CANDIDATES https://${REPO_NAME}.netlify.app"; fi
|
|
148
168
|
fi
|
|
149
169
|
|
|
150
|
-
REAL_URL=""
|
|
170
|
+
REAL_URL=""; REAL_NOTE=""
|
|
151
171
|
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
|
|
172
|
+
# 2026-06-07 fix:優先「200 + Storybook content」;沒有則退「200(內容非 Storybook)」,
|
|
173
|
+
# **不丟棄**已驗 200 的 candidate。anchor:apps/template 是真 product app(非 Storybook)→ 原
|
|
174
|
+
# is_storybook_deploy 把活著的 200 deploy 誤當 squat 報「全 404」,違反 user「不管是否 production 都給連結」。
|
|
175
|
+
FALLBACK_URL=""
|
|
152
176
|
for candidate in $CANDIDATES; do
|
|
153
|
-
if [ "$(verify_url "$candidate")" = "OK" ]
|
|
154
|
-
|
|
155
|
-
|
|
177
|
+
if [ "$(verify_url "$candidate")" = "OK" ]; then
|
|
178
|
+
if is_storybook_deploy "$candidate"; then
|
|
179
|
+
REAL_URL="$candidate"; REAL_NOTE="✅ verified 200 + Storybook content"; break
|
|
180
|
+
elif [ -z "$FALLBACK_URL" ]; then
|
|
181
|
+
FALLBACK_URL="$candidate"
|
|
182
|
+
fi
|
|
156
183
|
fi
|
|
157
184
|
done
|
|
185
|
+
if [ -z "$REAL_URL" ] && [ -n "$FALLBACK_URL" ]; then
|
|
186
|
+
REAL_URL="$FALLBACK_URL"; REAL_NOTE="✅ verified 200(內容非 Storybook — 可能 product app deploy)"
|
|
187
|
+
fi
|
|
158
188
|
if [ -n "$REAL_URL" ]; then
|
|
159
|
-
URLS_FOUND="${URLS_FOUND}🚀 Netlify PRODUCTION(${BRANCH}): ${REAL_URL}
|
|
189
|
+
URLS_FOUND="${URLS_FOUND}🚀 Netlify PRODUCTION(${BRANCH}): ${REAL_URL} ${REAL_NOTE}\n"
|
|
160
190
|
else
|
|
161
|
-
URLS_FOUND="${URLS_FOUND}🚀 Netlify PRODUCTION URL 未驗 — tried: ${CANDIDATES// /, }\n ⚠️ 全 404
|
|
191
|
+
URLS_FOUND="${URLS_FOUND}🚀 Netlify PRODUCTION URL 未驗 — tried: ${CANDIDATES// /, }\n ⚠️ 全 404。需要 user 手動 share dashboard URL,OR 創 \$HOME/.claude/local/deploy-targets.json:\n {\"${OWNER_REPO}\": \"https://<actual-site>.netlify.app\"}\n"
|
|
162
192
|
fi
|
|
163
193
|
else
|
|
164
194
|
# Branch preview:always use `<branch>--<sitename>` pattern,but sitename unknown unless USER_OVERRIDE
|
|
@@ -41,8 +41,17 @@ head_leaked() { echo "$STDOUT" | grep -qE 'HEAD(--|:|\))'; }
|
|
|
41
41
|
fn7() { echo "$STDOUT" | grep -q "feature-test" && ! head_leaked; }
|
|
42
42
|
fn8() { ! head_leaked; }
|
|
43
43
|
fn9() { echo "$STDOUT" | grep -q "feature-test"; }
|
|
44
|
-
has_feature()
|
|
45
|
-
no_quiet()
|
|
44
|
+
has_feature() { echo "$STDOUT" | grep -q "feature-test"; }
|
|
45
|
+
no_quiet() { ! echo "$STDOUT" | grep -q -- "--quiet"; }
|
|
46
|
+
no_refsheads() { ! echo "$STDOUT" | grep -q "refs/heads"; }
|
|
47
|
+
# minimal git repo on branch $2 (+ optional netlify.toml if $3=netlify), at dir $1
|
|
48
|
+
mkrepo() {
|
|
49
|
+
( cd "$1" || exit 1
|
|
50
|
+
git init -q -b "$2" 2>/dev/null || { git init -q; git checkout -q -b "$2"; }
|
|
51
|
+
: > f; [ "${3:-}" = netlify ] && : > netlify.toml
|
|
52
|
+
git add -A 2>/dev/null
|
|
53
|
+
GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t git commit -q -m i 2>/dev/null )
|
|
54
|
+
}
|
|
46
55
|
fn10() { has_feature && no_quiet; } # compound:fetch 段的 --quiet 不可被誤抓為 branch
|
|
47
56
|
fn11() { has_feature; } # compound:pull 段的 main 不可被誤抓(否則 grep feature-test 失敗)
|
|
48
57
|
fn12() { has_feature; } # bare push origin → fallback current branch
|
|
@@ -121,6 +130,55 @@ check "12 bare push origin → fallback current branch" fire 'fn12'
|
|
|
121
130
|
run_hook "git push origin --force"
|
|
122
131
|
check "13 flag-after-origin → fallback current branch (no flag-as-branch)" fire 'fn13'
|
|
123
132
|
|
|
133
|
+
# 14. cross-repo cd-parse:command `cd <other-repo> && git push` → hook 用 cd'd repo 作偵測根
|
|
134
|
+
# (2026-06-07 anchor:syncing ds-product-template 時 hook 吐成 DS 站台 URL)。
|
|
135
|
+
# TMP2 無 netlify.toml → 該 SKIP;若沒 cd-parse、誤用 $TMP〔有 netlify.toml〕→ 會 inject(=回歸)。
|
|
136
|
+
TMP2=$(mktemp -d)
|
|
137
|
+
(
|
|
138
|
+
cd "$TMP2" || exit 1
|
|
139
|
+
git init -q -b other-branch 2>/dev/null || { git init -q; git checkout -q -b other-branch; }
|
|
140
|
+
: > somefile
|
|
141
|
+
git add -A 2>/dev/null
|
|
142
|
+
GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t git commit -q -m init 2>/dev/null
|
|
143
|
+
)
|
|
144
|
+
run_hook "cd $TMP2 && git push origin other-branch"
|
|
145
|
+
check "14 cross-repo cd-parse → uses cd'd repo (no deploy target there → skip, not \$TMP's URL)" skip
|
|
146
|
+
rm -rf "$TMP2"
|
|
147
|
+
|
|
148
|
+
# 15. flag-before-origin `--force-with-lease`(原 trigger 只認 -u → 漏 fire 真實 push)→ fire
|
|
149
|
+
run_hook "git push --force-with-lease origin feature-test"
|
|
150
|
+
check "15 flag-before-origin (--force-with-lease) → fire" fire 'has_feature'
|
|
151
|
+
|
|
152
|
+
# 16. env-prefix `GIT_SSH_COMMAND=… git push`(原被命令邊界擋掉)→ fire
|
|
153
|
+
run_hook "GIT_SSH_COMMAND=ssh git push origin feature-test"
|
|
154
|
+
check "16 env-prefixed git push → fire" fire 'has_feature'
|
|
155
|
+
|
|
156
|
+
# 17. --set-upstream(-u 的長形,原 trigger 漏)→ fire
|
|
157
|
+
run_hook "git push --set-upstream origin feature-test"
|
|
158
|
+
check "17 --set-upstream → fire" fire 'has_feature'
|
|
159
|
+
|
|
160
|
+
# 18. `git -C <other-repo> push` → 用 -C 的 repo 作偵測根(無 netlify → skip,不吐 \$TMP 的 URL)
|
|
161
|
+
TMP3=$(mktemp -d); mkrepo "$TMP3" ob
|
|
162
|
+
run_hook "git -C $TMP3 push origin ob"
|
|
163
|
+
check "18 git -C <repo> → uses that repo (no deploy target → skip)" skip
|
|
164
|
+
rm -rf "$TMP3"
|
|
165
|
+
|
|
166
|
+
# 19. detached HEAD `git push origin HEAD` → branch 解析空 → skip(不吐 https://--site malformed)
|
|
167
|
+
TMP4=$(mktemp -d); mkrepo "$TMP4" m netlify; ( cd "$TMP4" && git checkout -q --detach 2>/dev/null )
|
|
168
|
+
STDOUT=$( cd "$TMP4" && HOME="$TMP4/home" CLAUDE_PROJECT_DIR="$TMP4" bash "$HOOK" <<<'{"hook_event_name":"PostToolUse","tool_name":"Bash","tool_input":{"command":"git push origin HEAD"}}' 2>&1 ); EXIT=$?
|
|
169
|
+
if [ "$EXIT" = 0 ] && ! injected && ! echo "$STDOUT" | grep -qE -- '--site|\(\)'; then echo " PASS 19 detached HEAD → skip (no malformed URL)"; PASS=$((PASS+1)); else echo " FAIL 19 (exit=$EXIT out=${STDOUT:0:140})"; FAIL=$((FAIL+1)); FAILED="${FAILED}\n - 19"; fi
|
|
170
|
+
rm -rf "$TMP4"
|
|
171
|
+
|
|
172
|
+
# 20. cd-after-command `echo x && cd <other> && git push` → 用該 cd 的 repo(非 session/first)
|
|
173
|
+
TMP5=$(mktemp -d); mkrepo "$TMP5" ob
|
|
174
|
+
run_hook "echo hi && cd $TMP5 && git push origin ob"
|
|
175
|
+
check "20 cd-after-command → uses that cd's repo (no deploy → skip)" skip
|
|
176
|
+
rm -rf "$TMP5"
|
|
177
|
+
|
|
178
|
+
# 21. refspec full-ref `HEAD:refs/heads/main` → 取 main,不洩漏 refs/heads
|
|
179
|
+
run_hook "git push origin HEAD:refs/heads/main"
|
|
180
|
+
check "21 refspec refs/heads → no refs/heads leak" fire 'no_refsheads'
|
|
181
|
+
|
|
124
182
|
teardown
|
|
125
183
|
|
|
126
184
|
echo ""
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qijenchen/design-system",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.60",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "World-class design system — components, patterns, tokens, hooks (single source of truth for team distribution).",
|
|
6
6
|
"type": "module",
|