Skip to content

Commit d516eeb

Browse files
committed
feat: Add grouped token exports, sanitized file metadata, and CLI options for the new token reporting flow.
1 parent 4e98944 commit d516eeb

File tree

12 files changed

+713
-4
lines changed

12 files changed

+713
-4
lines changed

.changeset/group-token-report.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tailwindcss-patch": minor
3+
---
4+
5+
Add grouped token exports, sanitized file metadata, and CLI options for the new token reporting flow.

.tw-patch/tw-token-report.json

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
{
2+
"page.html": [
3+
{
4+
"rawCandidate": "class",
5+
"file": "page.html",
6+
"relativeFile": "page.html",
7+
"extension": "html",
8+
"start": 41,
9+
"end": 46,
10+
"length": 5,
11+
"line": 4,
12+
"column": 10,
13+
"lineText": " <div class=\"bg-blue-500 text-white\">"
14+
},
15+
{
16+
"rawCandidate": "bg-blue-500",
17+
"file": "page.html",
18+
"relativeFile": "page.html",
19+
"extension": "html",
20+
"start": 48,
21+
"end": 59,
22+
"length": 11,
23+
"line": 4,
24+
"column": 17,
25+
"lineText": " <div class=\"bg-blue-500 text-white\">"
26+
},
27+
{
28+
"rawCandidate": "text-white",
29+
"file": "page.html",
30+
"relativeFile": "page.html",
31+
"extension": "html",
32+
"start": 60,
33+
"end": 70,
34+
"length": 10,
35+
"line": 4,
36+
"column": 29,
37+
"lineText": " <div class=\"bg-blue-500 text-white\">"
38+
},
39+
{
40+
"rawCandidate": "class",
41+
"file": "page.html",
42+
"relativeFile": "page.html",
43+
"extension": "html",
44+
"start": 82,
45+
"end": 87,
46+
"length": 5,
47+
"line": 5,
48+
"column": 10,
49+
"lineText": " <p class=\"md:text-lg underline\">Token scan</p>"
50+
},
51+
{
52+
"rawCandidate": "md:text-lg",
53+
"file": "page.html",
54+
"relativeFile": "page.html",
55+
"extension": "html",
56+
"start": 89,
57+
"end": 99,
58+
"length": 10,
59+
"line": 5,
60+
"column": 17,
61+
"lineText": " <p class=\"md:text-lg underline\">Token scan</p>"
62+
},
63+
{
64+
"rawCandidate": "underline",
65+
"file": "page.html",
66+
"relativeFile": "page.html",
67+
"extension": "html",
68+
"start": 100,
69+
"end": 109,
70+
"length": 9,
71+
"line": 5,
72+
"column": 28,
73+
"lineText": " <p class=\"md:text-lg underline\">Token scan</p>"
74+
},
75+
{
76+
"rawCandidate": "scan",
77+
"file": "page.html",
78+
"relativeFile": "page.html",
79+
"extension": "html",
80+
"start": 117,
81+
"end": 121,
82+
"length": 4,
83+
"line": 5,
84+
"column": 45,
85+
"lineText": " <p class=\"md:text-lg underline\">Token scan</p>"
86+
}
87+
],
88+
"button.tsx": [
89+
{
90+
"rawCandidate": "export",
91+
"file": "button.tsx",
92+
"relativeFile": "button.tsx",
93+
"extension": "tsx",
94+
"start": 0,
95+
"end": 6,
96+
"length": 6,
97+
"line": 1,
98+
"column": 1,
99+
"lineText": "export function Button() {"
100+
},
101+
{
102+
"rawCandidate": "function",
103+
"file": "button.tsx",
104+
"relativeFile": "button.tsx",
105+
"extension": "tsx",
106+
"start": 7,
107+
"end": 15,
108+
"length": 8,
109+
"line": 1,
110+
"column": 8,
111+
"lineText": "export function Button() {"
112+
},
113+
{
114+
"rawCandidate": "return",
115+
"file": "button.tsx",
116+
"relativeFile": "button.tsx",
117+
"extension": "tsx",
118+
"start": 29,
119+
"end": 35,
120+
"length": 6,
121+
"line": 2,
122+
"column": 3,
123+
"lineText": " return ("
124+
},
125+
{
126+
"rawCandidate": "className",
127+
"file": "button.tsx",
128+
"relativeFile": "button.tsx",
129+
"extension": "tsx",
130+
"start": 50,
131+
"end": 59,
132+
"length": 9,
133+
"line": 3,
134+
"column": 13,
135+
"lineText": " <button className=\"text-red-500 px-4 py-2 rounded\">"
136+
},
137+
{
138+
"rawCandidate": "text-red-500",
139+
"file": "button.tsx",
140+
"relativeFile": "button.tsx",
141+
"extension": "tsx",
142+
"start": 61,
143+
"end": 73,
144+
"length": 12,
145+
"line": 3,
146+
"column": 24,
147+
"lineText": " <button className=\"text-red-500 px-4 py-2 rounded\">"
148+
},
149+
{
150+
"rawCandidate": "px-4",
151+
"file": "button.tsx",
152+
"relativeFile": "button.tsx",
153+
"extension": "tsx",
154+
"start": 74,
155+
"end": 78,
156+
"length": 4,
157+
"line": 3,
158+
"column": 37,
159+
"lineText": " <button className=\"text-red-500 px-4 py-2 rounded\">"
160+
},
161+
{
162+
"rawCandidate": "py-2",
163+
"file": "button.tsx",
164+
"relativeFile": "button.tsx",
165+
"extension": "tsx",
166+
"start": 79,
167+
"end": 83,
168+
"length": 4,
169+
"line": 3,
170+
"column": 42,
171+
"lineText": " <button className=\"text-red-500 px-4 py-2 rounded\">"
172+
},
173+
{
174+
"rawCandidate": "rounded",
175+
"file": "button.tsx",
176+
"relativeFile": "button.tsx",
177+
"extension": "tsx",
178+
"start": 84,
179+
"end": 91,
180+
"length": 7,
181+
"line": 3,
182+
"column": 47,
183+
"lineText": " <button className=\"text-red-500 px-4 py-2 rounded\">"
184+
},
185+
{
186+
"rawCandidate": "me",
187+
"file": "button.tsx",
188+
"relativeFile": "button.tsx",
189+
"extension": "tsx",
190+
"start": 106,
191+
"end": 108,
192+
"length": 2,
193+
"line": 4,
194+
"column": 13,
195+
"lineText": " Click me"
196+
}
197+
]
198+
}

packages/tailwindcss-patch/README-cn.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ pnpm dlx tw-patch install
3434

3535
# 生成类名清单到配置的输出文件
3636
pnpm dlx tw-patch extract
37+
38+
# 扫描项目,导出包含文件/位置的 Tailwind token
39+
pnpm dlx tw-patch tokens --format lines
3740
```
3841

3942
### `extract` 常用参数
@@ -48,6 +51,16 @@ pnpm dlx tw-patch extract
4851

4952
CLI 会通过 `@tailwindcss-mangle/config` 加载 `tailwindcss-patch.config.ts`。旧配置仍可使用,详情请参考 [迁移指南](./MIGRATION.md)
5053

54+
### `tokens` 常用参数
55+
56+
| 参数 | 说明 |
57+
| ------------------------ | ----------------------------------------------- |
58+
| `--cwd <dir>` | 指定扫描时使用的工作目录。 |
59+
| `--output <file>` | 覆盖输出文件路径(默认 `.tw-patch/tw-token-report.json`)。 |
60+
| `--format <json\|lines\|grouped-json>` | 选择 JSON(默认)、按行输出,或按文件路径分组的 JSON。 |
61+
| `--group-key <relative\|absolute>` | 分组 JSON 的键(默认使用相对路径)。 |
62+
| `--no-write` | 只打印预览,不写入磁盘。 |
63+
5164
## 编程接口
5265

5366
```ts
@@ -81,6 +94,12 @@ const patcher = new TailwindcssPatcher({
8194

8295
await patcher.patch()
8396
const { classList, filename } = await patcher.extract()
97+
const tokenReport = await patcher.collectContentTokens()
98+
console.log(tokenReport.entries[0])
99+
const groupedTokens = await patcher.collectContentTokensByFile()
100+
console.log(groupedTokens['src/button.tsx'][0].rawCandidate)
101+
// 如果需要保留绝对路径:
102+
// await patcher.collectContentTokensByFile({ key: 'absolute', stripAbsolutePaths: false })
84103
```
85104

86105
构造函数既可以接收上述新版对象,也可以传入旧的 `patch`/`cache` 结构;内部会自动完成兼容转换。
@@ -89,6 +108,8 @@ const { classList, filename } = await patcher.extract()
89108

90109
- `normalizeOptions`:归一化用户输入并应用默认值。
91110
- `CacheStore`:读写类名缓存,支持合并或覆盖策略。
111+
- `extractProjectCandidatesWithPositions`:扫描内容源并返回带位置的 Tailwind token 信息。
112+
- `groupTokensByFile`:将 token 报告转换为 `{ [filePath]: TailwindTokenLocation[] }` 形式。
92113
- `extractValidCandidates`:利用 Tailwind Oxide 扫描 v4 CSS 与内容源。
93114
- `runTailwindBuild`:在 v2/v3 项目中运行 Tailwind PostCSS 插件以预热上下文。
94115

packages/tailwindcss-patch/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ pnpm dlx tw-patch install
3434

3535
# Extract all classes into the configured output file
3636
pnpm dlx tw-patch extract
37+
38+
# Capture every token (candidate) with file/position metadata
39+
pnpm dlx tw-patch tokens --format lines
3740
```
3841

3942
### Extract options
@@ -48,6 +51,16 @@ pnpm dlx tw-patch extract
4851

4952
The CLI loads `tailwindcss-patch.config.ts` via `@tailwindcss-mangle/config`. Legacy configs continue to work; see the [migration guide](./MIGRATION.md) for hints on the new fields.
5053

54+
### Token report options
55+
56+
| Flag | Description |
57+
| ------------------------ | --------------------------------------------------------------------------- |
58+
| `--cwd <dir>` | Use a different working directory when loading configuration. |
59+
| `--output <file>` | Override the token report target file (defaults to `.tw-patch/tw-token-report.json`). |
60+
| `--format <json\|lines\|grouped-json>` | Choose between a JSON payload (default), newline summaries, or JSON grouped by file path. |
61+
| `--group-key <relative\|absolute>` | Control grouped-json keys (defaults to relative paths). |
62+
| `--no-write` | Skip writing to disk and only print a preview. |
63+
5164
## Programmatic API
5265

5366
```ts
@@ -81,6 +94,12 @@ const patcher = new TailwindcssPatcher({
8194

8295
await patcher.patch()
8396
const { classList, filename } = await patcher.extract()
97+
const tokenReport = await patcher.collectContentTokens()
98+
console.log(tokenReport.entries[0]) // { rawCandidate, file, line, column, ... }
99+
const groupedTokens = await patcher.collectContentTokensByFile()
100+
console.log(groupedTokens['src/button.tsx'][0].rawCandidate)
101+
// Preserve absolute file paths:
102+
// await patcher.collectContentTokensByFile({ key: 'absolute', stripAbsolutePaths: false })
84103
```
85104

86105
The constructor accepts either the new object shown above or the historical `patch`/`cache` shape. Conversions happen internally so existing configs remain backwards compatible.
@@ -89,6 +108,8 @@ The constructor accepts either the new object shown above or the historical `pat
89108

90109
- `normalizeOptions` – normalise raw user input to the runtime shape.
91110
- `CacheStore` – read/write class caches respecting merge or overwrite semantics.
111+
- `extractProjectCandidatesWithPositions` – gather Tailwind tokens for every configured source file with location metadata.
112+
- `groupTokensByFile` – convert a token report into a `{ [filePath]: TailwindTokenLocation[] }` map.
92113
- `extractValidCandidates` – scan Tailwind v4 CSS/content sources with the Tailwind Oxide scanner.
93114
- `runTailwindBuild` – run the Tailwind PostCSS plugin for v2/v3 projects to prime runtime contexts.
94115

packages/tailwindcss-patch/src/api/tailwindcss-patcher.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
1+
import type { SourceEntry } from '@tailwindcss/oxide'
12
import type { PackageInfo } from 'local-pkg'
23
import type { LegacyTailwindcssPatcherOptions } from '../options/legacy'
34
import type { NormalizedTailwindcssPatchOptions } from '../options/types'
4-
import type { ExtractResult, TailwindcssPatchOptions } from '../types'
5+
import type {
6+
ExtractResult,
7+
TailwindcssPatchOptions,
8+
TailwindTokenByFileMap,
9+
TailwindTokenFileKey,
10+
TailwindTokenReport,
11+
} from '../types'
512
import process from 'node:process'
613
import fs from 'fs-extra'
714
import { getPackageInfoSync } from 'local-pkg'
815
import path from 'pathe'
916
import { coerce } from 'semver'
1017
import { CacheStore } from '../cache/store'
11-
import { extractValidCandidates as extractCandidates } from '../extraction/candidate-extractor'
18+
import {
19+
extractValidCandidates as extractCandidates,
20+
extractProjectCandidatesWithPositions,
21+
groupTokensByFile,
22+
} from '../extraction/candidate-extractor'
1223
import logger from '../logger'
1324
import { fromLegacyOptions } from '../options/legacy'
1425
import { normalizeOptions } from '../options/normalize'
@@ -242,4 +253,27 @@ export class TailwindcssPatcher {
242253

243254
// Backwards compatibility helper used by tests and API consumers.
244255
extractValidCandidates = extractCandidates
256+
257+
async collectContentTokens(options?: { cwd?: string, sources?: SourceEntry[] }): Promise<TailwindTokenReport> {
258+
return extractProjectCandidatesWithPositions({
259+
cwd: options?.cwd ?? this.options.projectRoot,
260+
sources: options?.sources ?? this.options.tailwind.v4?.sources ?? [],
261+
})
262+
}
263+
264+
async collectContentTokensByFile(options?: {
265+
cwd?: string
266+
sources?: SourceEntry[]
267+
key?: TailwindTokenFileKey
268+
stripAbsolutePaths?: boolean
269+
}): Promise<TailwindTokenByFileMap> {
270+
const report = await this.collectContentTokens({
271+
cwd: options?.cwd,
272+
sources: options?.sources,
273+
})
274+
return groupTokensByFile(report, {
275+
key: options?.key,
276+
stripAbsolutePaths: options?.stripAbsolutePaths,
277+
})
278+
}
245279
}

0 commit comments

Comments
 (0)