@dsh-plugin/dsh-ui-components 0.0.1 → 0.1.0-dev.43613492370
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/LICENSE +21 -21
- package/NOTICE +21 -21
- package/README.md +136 -136
- package/README.zh_CN.md +136 -136
- package/docs/tokens.md +67 -67
- package/lib/basic/form.js +49 -49
- package/lib/basic/styles.js +391 -391
- package/lib/composites/styles.js +427 -427
- package/lib/data/styles.js +257 -257
- package/lib/display/styles.js +310 -310
- package/lib/feedback/styles.js +228 -228
- package/lib/foundation/bridge.js +203 -203
- package/lib/navigation/styles.js +417 -417
- package/package.json +104 -104
- package/scripts/check-size.mjs +69 -69
- package/scripts/dump-npm-latest.mjs +59 -59
- package/scripts/gen-host-tokens.mjs +61 -61
- package/scripts/gen-icons.mjs +188 -178
- package/scripts/verify-no-literal-colors.mjs +63 -63
- package/scripts/verify-tokens.mjs +135 -135
- package/src/basic/button.tsx +76 -76
- package/src/basic/form.tsx +187 -187
- package/src/basic/icon-button.tsx +66 -66
- package/src/basic/index.ts +38 -38
- package/src/basic/input.tsx +140 -140
- package/src/basic/select.tsx +120 -120
- package/src/basic/styles.ts +412 -412
- package/src/basic/toggle.tsx +116 -116
- package/src/composites/data-view.tsx +189 -189
- package/src/composites/index.ts +11 -11
- package/src/composites/indicator.tsx +90 -90
- package/src/composites/menu-select.tsx +230 -230
- package/src/composites/panel.tsx +72 -72
- package/src/composites/settings.tsx +80 -80
- package/src/composites/styles.ts +450 -450
- package/src/data/collection.tsx +71 -71
- package/src/data/index.ts +39 -39
- package/src/data/info.tsx +89 -89
- package/src/data/picker.tsx +113 -113
- package/src/data/styles.ts +278 -278
- package/src/display/container.tsx +67 -67
- package/src/display/feedback.tsx +92 -92
- package/src/display/index.ts +44 -44
- package/src/display/label.tsx +165 -165
- package/src/display/overlay.tsx +103 -103
- package/src/display/styles.ts +332 -332
- package/src/feedback/global.tsx +110 -110
- package/src/feedback/index.ts +19 -19
- package/src/feedback/inline.tsx +50 -50
- package/src/feedback/panel.tsx +93 -93
- package/src/feedback/styles.ts +250 -250
- package/src/foundation/bridge.ts +318 -318
- package/src/foundation/icons.tsx +315 -315
- package/src/foundation/index.ts +33 -33
- package/src/foundation/palette.ts +153 -153
- package/src/foundation/provider.tsx +62 -62
- package/src/foundation/style.ts +63 -63
- package/src/foundation/tokens.ts +62 -62
- package/src/index.ts +18 -18
- package/src/navigation/hierarchy.tsx +82 -82
- package/src/navigation/index.ts +21 -21
- package/src/navigation/indicator.tsx +125 -125
- package/src/navigation/menu.tsx +94 -94
- package/src/navigation/segmented.tsx +136 -136
- package/src/navigation/styles.ts +438 -438
package/scripts/gen-icons.mjs
CHANGED
|
@@ -1,178 +1,188 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* 从 @icon-park/react 生成两条图标通道:
|
|
4
|
-
*
|
|
5
|
-
* 1. src/icons/<Pascal>.ts —— 2658 个逐图标转导出,供
|
|
6
|
-
* `dsh-ui-components/icons/Setting` 这样的子路径按需引入。
|
|
7
|
-
* 2. src/icons-index.ts —— 全量元数据,不导入任何图标模块,供搜索框、
|
|
8
|
-
* 图标选择器与文档站使用。
|
|
9
|
-
*
|
|
10
|
-
* 用法:node scripts/gen-icons.mjs [--check]
|
|
11
|
-
* 不带参数时重写两份产物;带 --check 时只比对,发现差异即以非零码退出。
|
|
12
|
-
*/
|
|
13
|
-
import { readFileSync, readdirSync, rmSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'
|
|
14
|
-
import { createRequire } from 'node:module'
|
|
15
|
-
import { dirname, join } from 'node:path'
|
|
16
|
-
import { fileURLToPath } from 'node:url'
|
|
17
|
-
|
|
18
|
-
const require = createRequire(import.meta.url)
|
|
19
|
-
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
20
|
-
const iconsDir = join(root, 'src', 'icons')
|
|
21
|
-
const indexFile = join(root, 'src', 'icons-index.ts')
|
|
22
|
-
const check = process.argv.includes('--check')
|
|
23
|
-
|
|
24
|
-
/** 图标清单的绝对路径,从本包依赖树解析。 */
|
|
25
|
-
const manifestPath = require.resolve('@icon-park/react/icons.json')
|
|
26
|
-
/** 单个图标模块目录,用于核对转导出目标确实存在。 */
|
|
27
|
-
const modulesDir = join(dirname(manifestPath), 'es', 'icons')
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* 把 IconPark 的 kebab-case 名字转成模块文件名用的 PascalCase。
|
|
31
|
-
*
|
|
32
|
-
* @param name - 例如 `a-cane`。
|
|
33
|
-
* @returns 例如 `ACane`。
|
|
34
|
-
*/
|
|
35
|
-
function pascal(name) {
|
|
36
|
-
return name
|
|
37
|
-
.split('-')
|
|
38
|
-
.filter(Boolean)
|
|
39
|
-
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
40
|
-
.join('')
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 把任意文本转成可安全放进单引号字符串字面量的形式。
|
|
45
|
-
*
|
|
46
|
-
* @param text - 原始文本。
|
|
47
|
-
* @returns 转义后的文本。
|
|
48
|
-
*/
|
|
49
|
-
function quote(text) {
|
|
50
|
-
return text
|
|
51
|
-
.replace(/\\/g, '\\\\')
|
|
52
|
-
.replace(/'/g, "\\'")
|
|
53
|
-
.replace(/\r\n|[\r\n\u2028\u2029]/g, ' ')
|
|
54
|
-
.replace(/\t/g, ' ')
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
58
|
-
if (!Array.isArray(manifest) || manifest.length === 0) {
|
|
59
|
-
throw new Error('icons.json 为空或格式不符合预期')
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** 每个图标一条,按名字排序保证产物稳定。 */
|
|
63
|
-
const entries = manifest
|
|
64
|
-
.map(item => ({
|
|
65
|
-
name: String(item.name),
|
|
66
|
-
pascal: pascal(String(item.name)),
|
|
67
|
-
title: String(item.title ?? ''),
|
|
68
|
-
titleZh: String(item.titleZh ?? item.title ?? ''),
|
|
69
|
-
category: String(item.category ?? ''),
|
|
70
|
-
tags: Array.isArray(item.tag) ? item.tag.map(String) : [],
|
|
71
|
-
rtl: item.rtl === true,
|
|
72
|
-
}))
|
|
73
|
-
.sort((a, b) => (a.pascal < b.pascal ? -1 : a.pascal > b.pascal ? 1 : 0))
|
|
74
|
-
|
|
75
|
-
const collisions = entries.filter((entry, index) => index > 0 && entries[index - 1].pascal === entry.pascal)
|
|
76
|
-
if (collisions.length > 0) {
|
|
77
|
-
throw new Error(`图标名转 PascalCase 后冲突:${collisions.map(entry => `${entry.name}->${entry.pascal}`).join(', ')}`)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
for (const entry of entries) {
|
|
81
|
-
if (!existsSync(join(modulesDir, `${entry.pascal}.js`))) {
|
|
82
|
-
throw new Error(`@icon-park/react 缺少图标模块 es/icons/${entry.pascal}.js(来源图标 ${entry.name})`)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* 单图标转导出文件的内容。
|
|
88
|
-
*
|
|
89
|
-
* @param entry - 一条图标记录。
|
|
90
|
-
* @returns 文件文本。
|
|
91
|
-
*/
|
|
92
|
-
function iconModule(entry) {
|
|
93
|
-
return `export { default } from '@icon-park/react/es/icons/${entry.pascal}.js'\n`
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const metadataRows = entries
|
|
97
|
-
.map(entry => {
|
|
98
|
-
const tags = entry.tags.map(tag => `'${quote(tag)}'`).join(', ')
|
|
99
|
-
return ` { name: '${quote(entry.name)}', pascal: '${entry.pascal}', title: '${quote(entry.title)}', titleZh: '${quote(entry.titleZh)}', category: '${quote(entry.category)}', tags: [${tags}], rtl: ${entry.rtl} },`
|
|
100
|
-
})
|
|
101
|
-
.join('\n')
|
|
102
|
-
|
|
103
|
-
const indexSource = `/**
|
|
104
|
-
* 全量 IconPark 图标元数据,共 ${entries.length} 条。
|
|
105
|
-
*
|
|
106
|
-
* 本文件由 scripts/gen-icons.mjs 从 @icon-park/react/icons.json 生成,请勿手改;
|
|
107
|
-
* 它不导入任何图标模块,因此被 import 时不会把图标打进产物。按名取组件请从
|
|
108
|
-
* \`@dsh-plugin/dsh-ui-components/icons/<pascal>\` 逐图标引入,或使用根导出的
|
|
109
|
-
* 策划命名集 \`Icon\` / \`Icons\`。
|
|
110
|
-
*
|
|
111
|
-
* @module @dsh-plugin/dsh-ui-components/icons-index
|
|
112
|
-
*/
|
|
113
|
-
|
|
114
|
-
/** 一条图标元数据。 */
|
|
115
|
-
export interface IconMeta {
|
|
116
|
-
/** IconPark 的 kebab-case 名。 */
|
|
117
|
-
name: string
|
|
118
|
-
/** 对应的模块文件名,也是 \`./icons/<pascal>\` 子路径的末段。 */
|
|
119
|
-
pascal: string
|
|
120
|
-
/** 图标标题,英文与无译名时为原名。 */
|
|
121
|
-
title: string
|
|
122
|
-
/** 图标中文标题。 */
|
|
123
|
-
titleZh: string
|
|
124
|
-
/** IconPark 分类,例如 \`Edit\`。 */
|
|
125
|
-
category: string
|
|
126
|
-
/** 检索标签。 */
|
|
127
|
-
tags: readonly string[]
|
|
128
|
-
/** 该图标是否有 RTL 变体。 */
|
|
129
|
-
rtl: boolean
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** 全部图标元数据。 */
|
|
133
|
-
export const ICON_INDEX: readonly IconMeta[] = [
|
|
134
|
-
${metadataRows}
|
|
135
|
-
]
|
|
136
|
-
|
|
137
|
-
/** 全部图标数量,供门禁核对。 */
|
|
138
|
-
export const ICON_COUNT = ${entries.length}
|
|
139
|
-
`
|
|
140
|
-
|
|
141
|
-
if (check) {
|
|
142
|
-
const problems = []
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 从 @icon-park/react 生成两条图标通道:
|
|
4
|
+
*
|
|
5
|
+
* 1. src/icons/<Pascal>.ts —— 2658 个逐图标转导出,供
|
|
6
|
+
* `dsh-ui-components/icons/Setting` 这样的子路径按需引入。
|
|
7
|
+
* 2. src/icons-index.ts —— 全量元数据,不导入任何图标模块,供搜索框、
|
|
8
|
+
* 图标选择器与文档站使用。
|
|
9
|
+
*
|
|
10
|
+
* 用法:node scripts/gen-icons.mjs [--check]
|
|
11
|
+
* 不带参数时重写两份产物;带 --check 时只比对,发现差异即以非零码退出。
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync, readdirSync, rmSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'
|
|
14
|
+
import { createRequire } from 'node:module'
|
|
15
|
+
import { dirname, join } from 'node:path'
|
|
16
|
+
import { fileURLToPath } from 'node:url'
|
|
17
|
+
|
|
18
|
+
const require = createRequire(import.meta.url)
|
|
19
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
20
|
+
const iconsDir = join(root, 'src', 'icons')
|
|
21
|
+
const indexFile = join(root, 'src', 'icons-index.ts')
|
|
22
|
+
const check = process.argv.includes('--check')
|
|
23
|
+
|
|
24
|
+
/** 图标清单的绝对路径,从本包依赖树解析。 */
|
|
25
|
+
const manifestPath = require.resolve('@icon-park/react/icons.json')
|
|
26
|
+
/** 单个图标模块目录,用于核对转导出目标确实存在。 */
|
|
27
|
+
const modulesDir = join(dirname(manifestPath), 'es', 'icons')
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 把 IconPark 的 kebab-case 名字转成模块文件名用的 PascalCase。
|
|
31
|
+
*
|
|
32
|
+
* @param name - 例如 `a-cane`。
|
|
33
|
+
* @returns 例如 `ACane`。
|
|
34
|
+
*/
|
|
35
|
+
function pascal(name) {
|
|
36
|
+
return name
|
|
37
|
+
.split('-')
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
40
|
+
.join('')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 把任意文本转成可安全放进单引号字符串字面量的形式。
|
|
45
|
+
*
|
|
46
|
+
* @param text - 原始文本。
|
|
47
|
+
* @returns 转义后的文本。
|
|
48
|
+
*/
|
|
49
|
+
function quote(text) {
|
|
50
|
+
return text
|
|
51
|
+
.replace(/\\/g, '\\\\')
|
|
52
|
+
.replace(/'/g, "\\'")
|
|
53
|
+
.replace(/\r\n|[\r\n\u2028\u2029]/g, ' ')
|
|
54
|
+
.replace(/\t/g, ' ')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
58
|
+
if (!Array.isArray(manifest) || manifest.length === 0) {
|
|
59
|
+
throw new Error('icons.json 为空或格式不符合预期')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 每个图标一条,按名字排序保证产物稳定。 */
|
|
63
|
+
const entries = manifest
|
|
64
|
+
.map(item => ({
|
|
65
|
+
name: String(item.name),
|
|
66
|
+
pascal: pascal(String(item.name)),
|
|
67
|
+
title: String(item.title ?? ''),
|
|
68
|
+
titleZh: String(item.titleZh ?? item.title ?? ''),
|
|
69
|
+
category: String(item.category ?? ''),
|
|
70
|
+
tags: Array.isArray(item.tag) ? item.tag.map(String) : [],
|
|
71
|
+
rtl: item.rtl === true,
|
|
72
|
+
}))
|
|
73
|
+
.sort((a, b) => (a.pascal < b.pascal ? -1 : a.pascal > b.pascal ? 1 : 0))
|
|
74
|
+
|
|
75
|
+
const collisions = entries.filter((entry, index) => index > 0 && entries[index - 1].pascal === entry.pascal)
|
|
76
|
+
if (collisions.length > 0) {
|
|
77
|
+
throw new Error(`图标名转 PascalCase 后冲突:${collisions.map(entry => `${entry.name}->${entry.pascal}`).join(', ')}`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
if (!existsSync(join(modulesDir, `${entry.pascal}.js`))) {
|
|
82
|
+
throw new Error(`@icon-park/react 缺少图标模块 es/icons/${entry.pascal}.js(来源图标 ${entry.name})`)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 单图标转导出文件的内容。
|
|
88
|
+
*
|
|
89
|
+
* @param entry - 一条图标记录。
|
|
90
|
+
* @returns 文件文本。
|
|
91
|
+
*/
|
|
92
|
+
function iconModule(entry) {
|
|
93
|
+
return `export { default } from '@icon-park/react/es/icons/${entry.pascal}.js'\n`
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const metadataRows = entries
|
|
97
|
+
.map(entry => {
|
|
98
|
+
const tags = entry.tags.map(tag => `'${quote(tag)}'`).join(', ')
|
|
99
|
+
return ` { name: '${quote(entry.name)}', pascal: '${entry.pascal}', title: '${quote(entry.title)}', titleZh: '${quote(entry.titleZh)}', category: '${quote(entry.category)}', tags: [${tags}], rtl: ${entry.rtl} },`
|
|
100
|
+
})
|
|
101
|
+
.join('\n')
|
|
102
|
+
|
|
103
|
+
const indexSource = `/**
|
|
104
|
+
* 全量 IconPark 图标元数据,共 ${entries.length} 条。
|
|
105
|
+
*
|
|
106
|
+
* 本文件由 scripts/gen-icons.mjs 从 @icon-park/react/icons.json 生成,请勿手改;
|
|
107
|
+
* 它不导入任何图标模块,因此被 import 时不会把图标打进产物。按名取组件请从
|
|
108
|
+
* \`@dsh-plugin/dsh-ui-components/icons/<pascal>\` 逐图标引入,或使用根导出的
|
|
109
|
+
* 策划命名集 \`Icon\` / \`Icons\`。
|
|
110
|
+
*
|
|
111
|
+
* @module @dsh-plugin/dsh-ui-components/icons-index
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
/** 一条图标元数据。 */
|
|
115
|
+
export interface IconMeta {
|
|
116
|
+
/** IconPark 的 kebab-case 名。 */
|
|
117
|
+
name: string
|
|
118
|
+
/** 对应的模块文件名,也是 \`./icons/<pascal>\` 子路径的末段。 */
|
|
119
|
+
pascal: string
|
|
120
|
+
/** 图标标题,英文与无译名时为原名。 */
|
|
121
|
+
title: string
|
|
122
|
+
/** 图标中文标题。 */
|
|
123
|
+
titleZh: string
|
|
124
|
+
/** IconPark 分类,例如 \`Edit\`。 */
|
|
125
|
+
category: string
|
|
126
|
+
/** 检索标签。 */
|
|
127
|
+
tags: readonly string[]
|
|
128
|
+
/** 该图标是否有 RTL 变体。 */
|
|
129
|
+
rtl: boolean
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** 全部图标元数据。 */
|
|
133
|
+
export const ICON_INDEX: readonly IconMeta[] = [
|
|
134
|
+
${metadataRows}
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
/** 全部图标数量,供门禁核对。 */
|
|
138
|
+
export const ICON_COUNT = ${entries.length}
|
|
139
|
+
`
|
|
140
|
+
|
|
141
|
+
if (check) {
|
|
142
|
+
const problems = []
|
|
143
|
+
/**
|
|
144
|
+
* 读取源码并把行尾归一为 LF。
|
|
145
|
+
*
|
|
146
|
+
* 生成内容始终是 LF;Windows 检出时 Git 的 `core.autocrlf` 会把工作区文件
|
|
147
|
+
* 转成 CRLF,逐字节比较就会让全部图标误报为不一致。
|
|
148
|
+
*
|
|
149
|
+
* @param path - 文件绝对路径。
|
|
150
|
+
* @returns 归一化后的内容。
|
|
151
|
+
*/
|
|
152
|
+
const readNormalized = path => readFileSync(path, 'utf8').replace(/\r\n/g, '\n')
|
|
153
|
+
if (!existsSync(indexFile)) {
|
|
154
|
+
problems.push('缺少 src/icons-index.ts')
|
|
155
|
+
} else if (readNormalized(indexFile) !== indexSource) {
|
|
156
|
+
problems.push('src/icons-index.ts 与 icons.json 不一致')
|
|
157
|
+
}
|
|
158
|
+
const existing = existsSync(iconsDir) ? readdirSync(iconsDir).filter(file => file.endsWith('.ts')) : []
|
|
159
|
+
const expected = new Set(entries.map(entry => `${entry.pascal}.ts`))
|
|
160
|
+
for (const file of existing) {
|
|
161
|
+
if (!expected.has(file)) problems.push(`多余的图标模块 src/icons/${file}`)
|
|
162
|
+
}
|
|
163
|
+
for (const file of expected) {
|
|
164
|
+
if (!existing.includes(file)) {
|
|
165
|
+
problems.push(`缺少图标模块 src/icons/${file}`)
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
const entry = entries.find(item => `${item.pascal}.ts` === file)
|
|
169
|
+
if (readNormalized(join(iconsDir, file)) !== iconModule(entry)) {
|
|
170
|
+
problems.push(`src/icons/${file} 内容与模板不一致`)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (problems.length > 0) {
|
|
174
|
+
console.error(problems.slice(0, 20).join('\n'))
|
|
175
|
+
if (problems.length > 20) console.error(`… 另有 ${problems.length - 20} 项`)
|
|
176
|
+
console.error('运行 `npm run gen:icons` 重新生成')
|
|
177
|
+
process.exit(1)
|
|
178
|
+
}
|
|
179
|
+
console.log(`图标通道校验通过:${entries.length} 个图标`)
|
|
180
|
+
} else {
|
|
181
|
+
rmSync(iconsDir, { recursive: true, force: true })
|
|
182
|
+
mkdirSync(iconsDir, { recursive: true })
|
|
183
|
+
for (const entry of entries) {
|
|
184
|
+
writeFileSync(join(iconsDir, `${entry.pascal}.ts`), iconModule(entry), 'utf8')
|
|
185
|
+
}
|
|
186
|
+
writeFileSync(indexFile, indexSource, 'utf8')
|
|
187
|
+
console.log(`已生成 ${entries.length} 个图标模块与 src/icons-index.ts`)
|
|
188
|
+
}
|
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* 校验源码没有写死颜色。
|
|
4
|
-
*
|
|
5
|
-
* 颜色只能以 `--dsw-*` 令牌、Arco 变量或 `currentColor` / `transparent`
|
|
6
|
-
* 表达,否则宿主换主题时会露出旧配色。唯一放行的是 `src/foundation/bridge.ts`
|
|
7
|
-
* —— Arco 的十级色阶必须是 `R,G,B` 三元组字面量,无法用令牌表达。
|
|
8
|
-
*/
|
|
9
|
-
import { readFileSync, readdirSync } from 'node:fs'
|
|
10
|
-
import { dirname, join } from 'node:path'
|
|
11
|
-
import { fileURLToPath } from 'node:url'
|
|
12
|
-
|
|
13
|
-
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
14
|
-
|
|
15
|
-
/** 允许出现字面色值的文件,相对仓库根。 */
|
|
16
|
-
const ALLOWED = new Set(['src/foundation/bridge.ts', 'src/foundation/palette.ts'])
|
|
17
|
-
|
|
18
|
-
/** 逐条检查规则:命中即报错。 */
|
|
19
|
-
const RULES = [
|
|
20
|
-
{ name: '十六进制色值', pattern: /#[0-9a-fA-F]{3,8}\b/g },
|
|
21
|
-
{ name: 'rgb()/rgba()/hsl() 字面量', pattern: /\b(?:rgba?|hsla?)\(\s*\d/g },
|
|
22
|
-
{ name: '颜色关键字 white/black', pattern: /(?<![\w-])(?:white|black)(?![\w-])/g },
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 递归列出目录下的源文件。
|
|
27
|
-
*
|
|
28
|
-
* @param dir - 起始目录。
|
|
29
|
-
* @returns 文件绝对路径列表。
|
|
30
|
-
*/
|
|
31
|
-
function listSources(dir) {
|
|
32
|
-
const out = []
|
|
33
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
34
|
-
const path = join(dir, entry.name)
|
|
35
|
-
if (entry.isDirectory()) out.push(...listSources(path))
|
|
36
|
-
else if (/\.(tsx?|css)$/.test(entry.name)) out.push(path)
|
|
37
|
-
}
|
|
38
|
-
return out
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const problems = []
|
|
42
|
-
for (const file of listSources(join(root, 'src'))) {
|
|
43
|
-
const relative = file.slice(root.length + 1).replace(/\\/g, '/')
|
|
44
|
-
if (ALLOWED.has(relative)) continue
|
|
45
|
-
const text = readFileSync(file, 'utf8')
|
|
46
|
-
text.split(/\r?\n/).forEach((line, index) => {
|
|
47
|
-
for (const rule of RULES) {
|
|
48
|
-
rule.pattern.lastIndex = 0
|
|
49
|
-
if (rule.pattern.test(line)) {
|
|
50
|
-
problems.push(`${relative}:${index + 1} 出现${rule.name}:${line.trim().slice(0, 100)}`)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (problems.length > 0) {
|
|
57
|
-
console.error(problems.slice(0, 30).join('\n'))
|
|
58
|
-
if (problems.length > 30) console.error(`… 另有 ${problems.length - 30} 处`)
|
|
59
|
-
console.error('改用 --dsw-* 令牌、Arco 变量或 currentColor / transparent')
|
|
60
|
-
process.exit(1)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
console.log('字面颜色校验通过')
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 校验源码没有写死颜色。
|
|
4
|
+
*
|
|
5
|
+
* 颜色只能以 `--dsw-*` 令牌、Arco 变量或 `currentColor` / `transparent`
|
|
6
|
+
* 表达,否则宿主换主题时会露出旧配色。唯一放行的是 `src/foundation/bridge.ts`
|
|
7
|
+
* —— Arco 的十级色阶必须是 `R,G,B` 三元组字面量,无法用令牌表达。
|
|
8
|
+
*/
|
|
9
|
+
import { readFileSync, readdirSync } from 'node:fs'
|
|
10
|
+
import { dirname, join } from 'node:path'
|
|
11
|
+
import { fileURLToPath } from 'node:url'
|
|
12
|
+
|
|
13
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
14
|
+
|
|
15
|
+
/** 允许出现字面色值的文件,相对仓库根。 */
|
|
16
|
+
const ALLOWED = new Set(['src/foundation/bridge.ts', 'src/foundation/palette.ts'])
|
|
17
|
+
|
|
18
|
+
/** 逐条检查规则:命中即报错。 */
|
|
19
|
+
const RULES = [
|
|
20
|
+
{ name: '十六进制色值', pattern: /#[0-9a-fA-F]{3,8}\b/g },
|
|
21
|
+
{ name: 'rgb()/rgba()/hsl() 字面量', pattern: /\b(?:rgba?|hsla?)\(\s*\d/g },
|
|
22
|
+
{ name: '颜色关键字 white/black', pattern: /(?<![\w-])(?:white|black)(?![\w-])/g },
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 递归列出目录下的源文件。
|
|
27
|
+
*
|
|
28
|
+
* @param dir - 起始目录。
|
|
29
|
+
* @returns 文件绝对路径列表。
|
|
30
|
+
*/
|
|
31
|
+
function listSources(dir) {
|
|
32
|
+
const out = []
|
|
33
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
34
|
+
const path = join(dir, entry.name)
|
|
35
|
+
if (entry.isDirectory()) out.push(...listSources(path))
|
|
36
|
+
else if (/\.(tsx?|css)$/.test(entry.name)) out.push(path)
|
|
37
|
+
}
|
|
38
|
+
return out
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const problems = []
|
|
42
|
+
for (const file of listSources(join(root, 'src'))) {
|
|
43
|
+
const relative = file.slice(root.length + 1).replace(/\\/g, '/')
|
|
44
|
+
if (ALLOWED.has(relative)) continue
|
|
45
|
+
const text = readFileSync(file, 'utf8')
|
|
46
|
+
text.split(/\r?\n/).forEach((line, index) => {
|
|
47
|
+
for (const rule of RULES) {
|
|
48
|
+
rule.pattern.lastIndex = 0
|
|
49
|
+
if (rule.pattern.test(line)) {
|
|
50
|
+
problems.push(`${relative}:${index + 1} 出现${rule.name}:${line.trim().slice(0, 100)}`)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (problems.length > 0) {
|
|
57
|
+
console.error(problems.slice(0, 30).join('\n'))
|
|
58
|
+
if (problems.length > 30) console.error(`… 另有 ${problems.length - 30} 处`)
|
|
59
|
+
console.error('改用 --dsw-* 令牌、Arco 变量或 currentColor / transparent')
|
|
60
|
+
process.exit(1)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log('字面颜色校验通过')
|