@fredwsw/frontend-rules 1.0.0 → 1.0.1
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/README.md +26 -276
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,302 +1,52 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frontend-rules
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一个用于将最新 `.cursor` 规则同步到当前项目根目录的小工具。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 快速开始
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- 在任意项目根目录执行:
|
|
7
|
+
在你的项目根目录执行:
|
|
9
8
|
|
|
10
9
|
```bash
|
|
11
|
-
pnpm dlx @
|
|
10
|
+
pnpm dlx @fredwsw/frontend-rules@latest
|
|
12
11
|
```
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
- 规则和技能同步到 `.cursor`
|
|
16
|
-
- 组件库文档同步到 `docs`
|
|
13
|
+
## 使用前提
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
- 已安装 Node.js `>=18`
|
|
16
|
+
- 已安装 `pnpm`
|
|
17
|
+
- 当前命令行所在目录是你希望应用规则的项目根目录
|
|
19
18
|
|
|
20
|
-
##
|
|
19
|
+
## 执行后会发生什么
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
- 工具会引导你选择需要的规则模板
|
|
22
|
+
- 将选中的规则文件写入当前项目(通常是 `.cursor/rules` 相关目录)
|
|
23
|
+
- 如果目标文件已存在,按工具提示处理覆盖或更新
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
frontend-rules/
|
|
26
|
-
bin/
|
|
27
|
-
cli.js
|
|
28
|
-
template/
|
|
29
|
-
rules/
|
|
30
|
-
common.mdc
|
|
31
|
-
uv-ui-docs.mdc
|
|
32
|
-
...
|
|
33
|
-
docs/
|
|
34
|
-
uv-ui-docs/
|
|
35
|
-
...
|
|
36
|
-
skills/
|
|
37
|
-
...
|
|
38
|
-
package.json
|
|
39
|
-
README.md
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
说明:
|
|
43
|
-
|
|
44
|
-
- `template/rules`:规则文件模板。
|
|
45
|
-
- `template/docs`:组件库文档模板,按用户选择复制。
|
|
46
|
-
- `template/skills`:技能模板。
|
|
47
|
-
- `bin/cli.js`:`pnpm dlx` 实际执行入口。
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## 二、`package.json` 示例
|
|
52
|
-
|
|
53
|
-
> 包名改成你的正式名:`@sanshui/frontend-rules`
|
|
54
|
-
|
|
55
|
-
```json
|
|
56
|
-
{
|
|
57
|
-
"name": "@sanshui/frontend-rules",
|
|
58
|
-
"version": "1.0.0",
|
|
59
|
-
"description": "Sync latest .cursor rules into project root",
|
|
60
|
-
"type": "module",
|
|
61
|
-
"bin": {
|
|
62
|
-
"frontend-rules": "./bin/cli.js"
|
|
63
|
-
},
|
|
64
|
-
"files": [
|
|
65
|
-
"bin",
|
|
66
|
-
"template",
|
|
67
|
-
"README.md"
|
|
68
|
-
],
|
|
69
|
-
"engines": {
|
|
70
|
-
"node": ">=18"
|
|
71
|
-
},
|
|
72
|
-
"dependencies": {
|
|
73
|
-
"@inquirer/prompts": "^7.8.4",
|
|
74
|
-
"fs-extra": "^11.2.0"
|
|
75
|
-
},
|
|
76
|
-
"license": "MIT"
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## 三、`bin/cli.js` 示例
|
|
83
|
-
|
|
84
|
-
> 作用:基础规则与技能始终复制到 `.cursor`,组件库文档支持交互多选后按需复制到项目根目录 `docs`。支持 `--dry-run`。
|
|
85
|
-
|
|
86
|
-
```js
|
|
87
|
-
#!/usr/bin/env node
|
|
88
|
-
import path from "node:path";
|
|
89
|
-
import process from "node:process";
|
|
90
|
-
import { fileURLToPath } from "node:url";
|
|
91
|
-
import fs from "fs-extra";
|
|
92
|
-
import { checkbox } from "@inquirer/prompts";
|
|
93
|
-
|
|
94
|
-
const cwd = process.cwd();
|
|
95
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
96
|
-
const __dirname = path.dirname(__filename);
|
|
97
|
-
const root = path.resolve(__dirname, "..");
|
|
98
|
-
const sourceDir = path.join(root, "template");
|
|
99
|
-
const cursorTargetDir = path.join(cwd, ".cursor");
|
|
100
|
-
const docsTargetRootDir = path.join(cwd, "docs");
|
|
101
|
-
const args = new Set(process.argv.slice(2));
|
|
102
|
-
const dryRun = args.has("--dry-run");
|
|
103
|
-
const allLibs = args.has("--all-libs");
|
|
104
|
-
const libsArg = [...args].find((arg) => arg.startsWith("--libs="));
|
|
105
|
-
|
|
106
|
-
function parseLibsArg() {
|
|
107
|
-
if (!libsArg) {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
const value = libsArg.slice("--libs=".length).trim();
|
|
111
|
-
if (!value) {
|
|
112
|
-
return [];
|
|
113
|
-
}
|
|
114
|
-
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function getLibraryOptions() {
|
|
118
|
-
const docsRoot = path.join(sourceDir, "docs");
|
|
119
|
-
const exists = await fs.pathExists(docsRoot);
|
|
120
|
-
if (!exists) {
|
|
121
|
-
return [];
|
|
122
|
-
}
|
|
123
|
-
const entries = await fs.readdir(docsRoot, { withFileTypes: true });
|
|
124
|
-
return entries.filter((entry) => entry.isDirectory()).map((entry) => {
|
|
125
|
-
const docsDirName = entry.name;
|
|
126
|
-
return {
|
|
127
|
-
key: docsDirName.replace(/-docs$/i, ""),
|
|
128
|
-
docsDirName,
|
|
129
|
-
docsSourcePath: path.join(docsRoot, docsDirName),
|
|
130
|
-
ruleFileName: `${docsDirName}.mdc`,
|
|
131
|
-
ruleSourcePath: path.join(sourceDir, "rules", `${docsDirName}.mdc`),
|
|
132
|
-
};
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async function promptLibraries(options) {
|
|
137
|
-
if (options.length === 0) {
|
|
138
|
-
return [];
|
|
139
|
-
}
|
|
140
|
-
if (!process.stdin.isTTY) {
|
|
141
|
-
return options;
|
|
142
|
-
}
|
|
143
|
-
const selectedKeys = await checkbox({
|
|
144
|
-
message: "请选择组件库(方向键移动,空格勾选,回车确认)",
|
|
145
|
-
choices: options.map((option) => ({ name: option.key, value: option.key })),
|
|
146
|
-
});
|
|
147
|
-
if (!selectedKeys || selectedKeys.length === 0) {
|
|
148
|
-
return [];
|
|
149
|
-
}
|
|
150
|
-
return options.filter((option) => new Set(selectedKeys).has(option.key));
|
|
151
|
-
}
|
|
25
|
+
## 推荐用法
|
|
152
26
|
|
|
153
|
-
|
|
154
|
-
if (allLibs) {
|
|
155
|
-
return options;
|
|
156
|
-
}
|
|
157
|
-
const parsedLibKeys = parseLibsArg();
|
|
158
|
-
if (parsedLibKeys !== null) {
|
|
159
|
-
return options.filter((option) => new Set(parsedLibKeys).has(option.key));
|
|
160
|
-
}
|
|
161
|
-
return promptLibraries(options);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async function copyWithDryRun(from, to) {
|
|
165
|
-
if (dryRun) {
|
|
166
|
-
console.log("[frontend-rules] copy:", from, "->", to);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
await fs.copy(from, to, { overwrite: true, recursive: true });
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async function main() {
|
|
173
|
-
await fs.ensureDir(cursorTargetDir);
|
|
174
|
-
await fs.ensureDir(docsTargetRootDir);
|
|
175
|
-
|
|
176
|
-
await copyWithDryRun(path.join(sourceDir, "rules", "common.mdc"), path.join(cursorTargetDir, "rules", "common.mdc"));
|
|
177
|
-
await copyWithDryRun(path.join(sourceDir, "skills"), path.join(cursorTargetDir, "skills"));
|
|
178
|
-
|
|
179
|
-
const options = await getLibraryOptions();
|
|
180
|
-
const selectedLibraries = await resolveSelectedLibraries(options);
|
|
181
|
-
for (const library of selectedLibraries) {
|
|
182
|
-
await copyWithDryRun(library.docsSourcePath, path.join(docsTargetRootDir, library.docsDirName));
|
|
183
|
-
if (await fs.pathExists(library.ruleSourcePath)) {
|
|
184
|
-
await copyWithDryRun(library.ruleSourcePath, path.join(cursorTargetDir, "rules", library.ruleFileName));
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
main();
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
|
-
## 四、本地联调
|
|
195
|
-
|
|
196
|
-
在包仓库目录执行:
|
|
27
|
+
每次想同步最新规则时,直接重复执行:
|
|
197
28
|
|
|
198
29
|
```bash
|
|
199
|
-
pnpm
|
|
200
|
-
node ./bin/cli.js --dry-run
|
|
201
|
-
node ./bin/cli.js
|
|
30
|
+
pnpm dlx @fredwsw/frontend-rules@latest
|
|
202
31
|
```
|
|
203
32
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
- `.cursor/rules`
|
|
207
|
-
- `.cursor/skills`
|
|
208
|
-
- `docs/<组件库>-docs`
|
|
33
|
+
这样无需全局安装,始终使用最新版本。
|
|
209
34
|
|
|
210
|
-
|
|
35
|
+
## 常见问题
|
|
211
36
|
|
|
212
|
-
|
|
37
|
+
### 1) 提示 `pnpm` 命令不存在
|
|
213
38
|
|
|
214
|
-
|
|
39
|
+
请先安装 pnpm:
|
|
215
40
|
|
|
216
41
|
```bash
|
|
217
|
-
npm
|
|
42
|
+
npm i -g pnpm
|
|
218
43
|
```
|
|
219
44
|
|
|
220
|
-
2
|
|
221
|
-
|
|
222
|
-
```bash
|
|
223
|
-
npm publish --access public
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
3. 后续更新流程
|
|
227
|
-
|
|
228
|
-
```bash
|
|
229
|
-
# 修改 template/.cursor 内容后
|
|
230
|
-
npm version patch
|
|
231
|
-
npm publish --access public
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
---
|
|
235
|
-
|
|
236
|
-
## 六、在其他项目中使用
|
|
237
|
-
|
|
238
|
-
进入目标项目根目录,执行:
|
|
239
|
-
|
|
240
|
-
```bash
|
|
241
|
-
pnpm dlx @sanshui/frontend-rules@latest
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
常用变体:
|
|
245
|
-
|
|
246
|
-
```bash
|
|
247
|
-
# 只预览,不落盘(若你实现了 --dry-run 参数)
|
|
248
|
-
pnpm dlx @sanshui/frontend-rules@latest --dry-run
|
|
249
|
-
|
|
250
|
-
# 不交互,直接选择全部组件库
|
|
251
|
-
pnpm dlx @sanshui/frontend-rules@latest --all-libs
|
|
252
|
-
|
|
253
|
-
# 不交互,指定组件库(可多选,逗号分隔)
|
|
254
|
-
pnpm dlx @sanshui/frontend-rules@latest --libs=uv-ui,first-ui
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
---
|
|
258
|
-
|
|
259
|
-
## 七、推荐约定(团队协作)
|
|
260
|
-
|
|
261
|
-
- 把 `.cursor` 规则维护统一放在 `@sanshui/frontend-rules`,业务仓库不各自散改。
|
|
262
|
-
- 每次规则更新都发版,业务仓库按需执行 `pnpm dlx ...@latest` 同步。
|
|
263
|
-
- 若业务仓库存在少量本地特化,可约定放在单独文件并在同步后手动合并。
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
## 八、常见问题
|
|
268
|
-
|
|
269
|
-
### 1)`pnpm dlx` 没拿到最新版本?
|
|
270
|
-
|
|
271
|
-
- 显式指定 `@latest`:
|
|
272
|
-
|
|
273
|
-
```bash
|
|
274
|
-
pnpm dlx @sanshui/frontend-rules@latest
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
- 确认刚发布版本已在 npm 可见。
|
|
278
|
-
|
|
279
|
-
### 2)会不会覆盖本地 `.cursor`?
|
|
280
|
-
|
|
281
|
-
- 当前示例是覆盖策略(`overwrite: true`)。
|
|
282
|
-
- 如果你想保守一点,可扩展参数:
|
|
283
|
-
- `--force` 才覆盖
|
|
284
|
-
- 默认仅拷贝不存在文件
|
|
285
|
-
- 或执行前先备份到 `.cursor.bak-时间戳`
|
|
286
|
-
|
|
287
|
-
### 3)如何控制复制哪些组件库文档?
|
|
288
|
-
|
|
289
|
-
- 交互模式下支持方向键上下移动、空格勾选、回车确认(基于 `@inquirer/prompts`)。
|
|
290
|
-
- 非交互可用 `--libs=uv-ui,first-ui`。
|
|
291
|
-
- 全量组件库可用 `--all-libs`。
|
|
292
|
-
|
|
293
|
-
---
|
|
45
|
+
### 2) 提示 Node 版本过低
|
|
294
46
|
|
|
295
|
-
|
|
47
|
+
请升级 Node.js 到 `18` 或更高版本后重试。
|
|
296
48
|
|
|
297
|
-
|
|
298
|
-
2. 写好 `bin/cli.js` 复制逻辑。
|
|
299
|
-
3. 配置 `package.json` 的 `name`、`bin`、`files`。
|
|
300
|
-
4. `npm publish --access public`。
|
|
301
|
-
5. 在业务项目运行:`pnpm dlx @sanshui/frontend-rules@latest`,按提示多选组件库。
|
|
49
|
+
### 3) 我应该在什么目录执行命令?
|
|
302
50
|
|
|
51
|
+
在你要应用规则的项目根目录执行命令。
|
|
52
|
+
例如你的业务项目目录,而不是这个工具包源码目录。
|