create-dwy 0.16.0 → 0.17.0
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/CHANGELOG.md +17 -0
- package/bin/index.js +1 -1
- package/package.json +3 -2
- package/src/searchable-select.js +419 -0
- package/src/sync-all.js +313 -65
- package/src/sync-global-skills.js +167 -0
- package/src/sync.js +59 -43
- package/templates/ai-tools/CHANGELOG.md +210 -0
- package/templates/ai-tools/VERSION +1 -0
- package/templates/ai-tools/hook-manifests/hooks.json +7 -0
- package/templates/ai-tools/hooks/Git/pre-git-commit-ai-signature-check.sh +57 -0
- package/templates/ai-tools/rules/Android/dwy-android-core.md +280 -5
- package/templates/ai-tools/rules/Docker/dwy-docker.md +52 -1
- package/templates/ai-tools/rules/HarmonyOS/dwy-arkts-ui-build.md +50 -1
- package/templates/ai-tools/rules/HarmonyOS/dwy-arkts-ui-design.md +139 -0
- package/templates/ai-tools/rules/HarmonyOS/dwy-harmony-pitfalls.md +72 -0
- package/templates/ai-tools/rules/Python/dwy-python-core.md +47 -0
- package/templates/ai-tools/rules/Vue/dwy-vue-core.md +55 -0
- package/templates/ai-tools/rules/iOS/dwy-ios-pitfalls.md +7 -0
- package/templates/ai-tools/rules/iOS/dwy-swift-core.md +46 -0
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-git-commit.md +9 -6
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-oss-commercial-license.md +128 -0
- package/templates/ai-tools/skills//345/205/203/345/267/245/345/205/267/dwy-shared/SKILL.md +22 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/SKILL.md +65 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/assets/appstore-submission.yaml +43 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/references/api-workflow.md +17 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/references/in-app-purchase-review.md +16 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/references/screenshot-workflow.md +16 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/scripts/appstore_config.sh +140 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/SKILL.md +11 -8
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/android.md +2 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/backend-deploy.md +2 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/changelog.md +24 -4
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/frontend-deploy.md +2 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/generic-release.md +2 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/git-tag.md +82 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/harmony.md +2 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/ios.md +3 -2
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/monitor-notify.md +2 -2
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-semver/SKILL.md +1 -1
- package/templates/ai-tools/skills//345/237/272/347/241/200/345/272/223/dwy-eapi/references/tasks-integration-guide.md +2 -2
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/SKILL.md +1 -1
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/references/checks-runtime.md +2 -2
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_docker.sh +3 -3
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_secrets.sh +1 -1
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/README.md +57 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/SKILL.md +147 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/references/api.md +43 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/references/config.example.yaml +33 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/scripts/global_config.py +89 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/scripts/setup_config.py +175 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/scripts/synthesize.py +339 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/SKILL.md +127 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/assets/cover-style-master-v1.png +0 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/references/platform-copy-examples.md +37 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/references/platform-posting-times.md +193 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0005-ideation/SKILL.md +42 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0005-ideation/templates/topic-backlog.template.md +50 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0005-ideation/templates//351/200/211/351/242/230/346/261/240.template.md +16 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0010-product/SKILL.md +106 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0010-product/templates/channel-profile.template.md +52 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0010-product/templates//344/272/247/345/223/201/345/215/241.template.md +132 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0030-facts/SKILL.md +60 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0030-facts/scripts/build_facts_html.py +430 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0030-facts/scripts/generate_html.py +1287 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0030-facts/scripts/generate_report.py +189 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0030-facts/templates//344/272/213/345/256/236/350/265/204/346/226/231/345/272/223.template.md +55 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/SKILL.md +36 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/templates/README.md +10 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/templates//344/272/213/345/256/236.template.md +82 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/templates//345/215/225/351/233/206/346/226/207/346/241/243.template.md +175 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/templates//350/204/232/346/234/254.template.md +69 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/templates//351/252/250/346/236/266-v1.1.md +174 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0050-design/SKILL.md +55 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0060-assets/SKILL.md +29 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package/SKILL.md +42 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package/scripts/validate_shots.py +207 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package/shots.schema.json +186 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package//344/272/272/345/256/241/345/214/205.template.md +1 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package//344/272/272/345/256/241/350/257/264/346/230/216.md +15 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package//346/210/220/347/211/207/345/214/205.template.md +49 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0080-factory/SKILL.md +58 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-comic-kit/SKILL.md +424 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-comic-kit/evals.md +41 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-flow/CONTRACT.md +123 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-flow/SKILL.md +81 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-flow//346/211/247/350/241/214/346/270/205/345/215/225.template.md +60 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/SKILL.md +78 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/agents/openai.yaml +4 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/assets/knowledge-town-paper-film-v1.json +80 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/assets/remotion/AudioTracks.tsx +20 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/assets/remotion/CaptionLayer.tsx +18 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/assets/remotion/CueTimeline.ts +24 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/assets/remotion/KnowledgeTownComposition.tsx +21 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/references/cinematic-direction.md +73 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/references/sticker-asset-production.md +30 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/references/style-guide.md +43 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/scripts/build_cue_timeline.py +75 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/scripts/validate_cue_timeline.py +68 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/scripts/validate_style_preset.py +22 -0
- package/templates/ai-tools/skills//350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-first/SKILL.md +176 -0
- package/templates/ai-tools/skills//350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-first/references/chapter-01-mirrors.md +112 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# create-dwy
|
|
2
2
|
|
|
3
|
+
## 0.17.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- `dwy` 交互同步开头增加「同步范围」:完整同步(默认)或仅 Skills。仅 Skills 不写、不删 Rules / Commands / Hooks。
|
|
8
|
+
- 最后增加「Skill 同步位置」:默认仅项目目录;可另选 Claude / Grok / Codex / Cursor / OpenCode 全局目录,并勾选要额外拷贝的 skill。只覆盖点名 skill,不删用户 home 里其它个人 skill。选择写入 `/.dwy/sync-state.json` 的 `skillScope` / `syncMode`。
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 0.16.1
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 交互同步每一步选项支持关键字搜索(Skills / Rules / Commands / Hooks、平台选择、陈旧项删除、导航与汇总确认)。
|
|
17
|
+
- 选项说明改为列表下方固定「说明」公共区展示当前聚焦项完整描述,不再跟在选项行尾导致截断错乱。
|
|
18
|
+
- 新增 `src/searchable-select.js`(基于 `@clack/core` AutocompletePrompt 自定义 render);`@clack/prompts` 下限抬至 `^1.7.0`,显式依赖 `@clack/core`。
|
|
19
|
+
|
|
3
20
|
## 0.16.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
package/bin/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-dwy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "项目脚手架与多平台 AI 配置同步工具",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"author": "dwydev",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@clack/
|
|
31
|
+
"@clack/core": "^1.4.3",
|
|
32
|
+
"@clack/prompts": "^1.7.0",
|
|
32
33
|
"handlebars": "^4.7.0",
|
|
33
34
|
"fs-extra": "^11.0.0",
|
|
34
35
|
"chalk": "^5.0.0"
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 可搜索选择器:列表不展示 hint,底部固定「说明」公共区展示当前聚焦项描述。
|
|
3
|
+
*
|
|
4
|
+
* 基于 @clack/core AutocompletePrompt 自定义 render,绕过 prompts 默认「hint 跟在聚焦行后」行为。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AutocompletePrompt, getColumns, settings } from '@clack/core'
|
|
8
|
+
import { limitOptions } from '@clack/prompts'
|
|
9
|
+
import { styleText } from 'node:util'
|
|
10
|
+
import process from 'node:process'
|
|
11
|
+
|
|
12
|
+
/** 是否支持 unicode 符号(与 clack prompts 判定一致) */
|
|
13
|
+
function isUnicodeSupported() {
|
|
14
|
+
if (process.platform !== 'win32') {
|
|
15
|
+
return process.env.TERM !== 'linux'
|
|
16
|
+
}
|
|
17
|
+
return Boolean(process.env.CI)
|
|
18
|
+
|| Boolean(process.env.WT_SESSION)
|
|
19
|
+
|| Boolean(process.env.TERMINUS_SUBLIME)
|
|
20
|
+
|| process.env.ConEmuTask === '{cmd::Cmder}'
|
|
21
|
+
|| process.env.TERM_PROGRAM === 'Terminus-Sublime'
|
|
22
|
+
|| process.env.TERM_PROGRAM === 'vscode'
|
|
23
|
+
|| process.env.TERM === 'xterm-256color'
|
|
24
|
+
|| process.env.TERM === 'alacritty'
|
|
25
|
+
|| process.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const unicode = isUnicodeSupported()
|
|
29
|
+
const unicodeOr = (c, fallback) => (unicode ? c : fallback)
|
|
30
|
+
|
|
31
|
+
const S_STEP_ACTIVE = unicodeOr('◆', '*')
|
|
32
|
+
const S_STEP_CANCEL = unicodeOr('■', 'x')
|
|
33
|
+
const S_STEP_ERROR = unicodeOr('▲', 'x')
|
|
34
|
+
const S_STEP_SUBMIT = unicodeOr('◇', 'o')
|
|
35
|
+
const S_BAR = unicodeOr('│', '|')
|
|
36
|
+
const S_BAR_END = unicodeOr('└', '—')
|
|
37
|
+
const S_RADIO_ACTIVE = unicodeOr('●', '>')
|
|
38
|
+
const S_RADIO_INACTIVE = unicodeOr('○', ' ')
|
|
39
|
+
const S_CHECKBOX_SELECTED = unicodeOr('◼', '[+]')
|
|
40
|
+
const S_CHECKBOX_INACTIVE = unicodeOr('◻', '[ ]')
|
|
41
|
+
|
|
42
|
+
/** 底部说明区固定行数,避免列表高度随描述长短跳动 */
|
|
43
|
+
const DESC_PANEL_LINES = 3
|
|
44
|
+
|
|
45
|
+
/** 搜索框默认 placeholder */
|
|
46
|
+
export const SEARCH_PLACEHOLDER = '输入关键字筛选…'
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 按状态返回步骤符号。
|
|
50
|
+
* @param {string} state
|
|
51
|
+
*/
|
|
52
|
+
function symbol(state) {
|
|
53
|
+
switch (state) {
|
|
54
|
+
case 'cancel':
|
|
55
|
+
return styleText('red', S_STEP_CANCEL)
|
|
56
|
+
case 'error':
|
|
57
|
+
return styleText('yellow', S_STEP_ERROR)
|
|
58
|
+
case 'submit':
|
|
59
|
+
return styleText('green', S_STEP_SUBMIT)
|
|
60
|
+
default:
|
|
61
|
+
return styleText('cyan', S_STEP_ACTIVE)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 默认过滤:匹配 label / value / description。
|
|
67
|
+
* @param {string} search
|
|
68
|
+
* @param {{ value: unknown, label?: string, description?: string, hint?: string }} opt
|
|
69
|
+
*/
|
|
70
|
+
function defaultFilter(search, opt) {
|
|
71
|
+
if (!search) return true
|
|
72
|
+
const q = search.toLowerCase()
|
|
73
|
+
const label = (opt.label ?? String(opt.value ?? '')).toLowerCase()
|
|
74
|
+
const desc = (opt.description ?? opt.hint ?? '').toLowerCase()
|
|
75
|
+
const value = String(opt.value ?? '').toLowerCase()
|
|
76
|
+
return label.includes(q) || desc.includes(q) || value.includes(q)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 取选项展示文案。
|
|
81
|
+
* @param {{ label?: string, value?: unknown }} opt
|
|
82
|
+
*/
|
|
83
|
+
function optionLabel(opt) {
|
|
84
|
+
return opt.label ?? String(opt.value ?? '')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 按终端宽度硬换行(中日文按字符计,足够 CLI 展示)。
|
|
89
|
+
* @param {string} text
|
|
90
|
+
* @param {number} width
|
|
91
|
+
* @returns {string[]}
|
|
92
|
+
*/
|
|
93
|
+
function wrapHard(text, width) {
|
|
94
|
+
if (!text) return []
|
|
95
|
+
const w = Math.max(width, 8)
|
|
96
|
+
const lines = []
|
|
97
|
+
let row = ''
|
|
98
|
+
for (const ch of text) {
|
|
99
|
+
if (ch === '\n') {
|
|
100
|
+
lines.push(row)
|
|
101
|
+
row = ''
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
if (row.length >= w) {
|
|
105
|
+
lines.push(row)
|
|
106
|
+
row = ch
|
|
107
|
+
} else {
|
|
108
|
+
row += ch
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (row) lines.push(row)
|
|
112
|
+
return lines
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 构建底部固定高度说明区。
|
|
117
|
+
* 无描述时用占位,保证行数恒定。
|
|
118
|
+
*
|
|
119
|
+
* @param {string | undefined} description 当前聚焦项完整描述
|
|
120
|
+
* @param {string} barPrefix 左侧竖线前缀(含颜色)
|
|
121
|
+
* @param {number} contentWidth 可写字符宽
|
|
122
|
+
* @returns {string[]}
|
|
123
|
+
*/
|
|
124
|
+
function buildDescPanel(description, barPrefix, contentWidth) {
|
|
125
|
+
const raw = (description && description.trim()) ? description.trim() : '(无说明)'
|
|
126
|
+
const wrapped = wrapHard(raw, contentWidth)
|
|
127
|
+
// 固定 DESC_PANEL_LINES 行:超出截断末行加省略,不足补空
|
|
128
|
+
const lines = []
|
|
129
|
+
for (let i = 0; i < DESC_PANEL_LINES; i++) {
|
|
130
|
+
let part = wrapped[i] ?? ''
|
|
131
|
+
if (i === DESC_PANEL_LINES - 1 && wrapped.length > DESC_PANEL_LINES) {
|
|
132
|
+
const ellipsis = '…'
|
|
133
|
+
part = (wrapped[i] ?? '').slice(0, Math.max(contentWidth - ellipsis.length, 0)) + ellipsis
|
|
134
|
+
}
|
|
135
|
+
if (i === 0) {
|
|
136
|
+
lines.push(`${barPrefix}${styleText('dim', '说明:')}${styleText('dim', part)}`)
|
|
137
|
+
} else {
|
|
138
|
+
// 续行缩进对齐「说明:」四字宽
|
|
139
|
+
lines.push(`${barPrefix}${styleText('dim', ' ')}${styleText('dim', part)}`)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return lines
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 从 options 中取 focusedValue 对应项的 description。
|
|
147
|
+
* @param {Array<{ value: unknown, description?: string, hint?: string }>} options
|
|
148
|
+
* @param {unknown} focusedValue
|
|
149
|
+
*/
|
|
150
|
+
function resolveDescription(options, focusedValue) {
|
|
151
|
+
if (focusedValue === undefined) return undefined
|
|
152
|
+
const hit = options.find(o => o.value === focusedValue)
|
|
153
|
+
return hit?.description ?? hit?.hint
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 可搜索多选:列表无行内 hint,底部公共区展示当前项说明。
|
|
158
|
+
*
|
|
159
|
+
* @param {object} opts
|
|
160
|
+
* @param {string} opts.message
|
|
161
|
+
* @param {Array<{ value: unknown, label?: string, description?: string, disabled?: boolean }>} opts.options
|
|
162
|
+
* @param {unknown[]} [opts.initialValues]
|
|
163
|
+
* @param {boolean} [opts.required]
|
|
164
|
+
* @param {number} [opts.maxItems]
|
|
165
|
+
* @param {string} [opts.placeholder]
|
|
166
|
+
* @param {(search: string, opt: object) => boolean} [opts.filter]
|
|
167
|
+
* @returns {Promise<unknown[] | symbol>}
|
|
168
|
+
*/
|
|
169
|
+
export function searchableMultiselect(opts) {
|
|
170
|
+
const withGuide = opts.withGuide ?? settings.withGuide
|
|
171
|
+
const placeholder = opts.placeholder ?? SEARCH_PLACEHOLDER
|
|
172
|
+
const filter = opts.filter ?? defaultFilter
|
|
173
|
+
|
|
174
|
+
const prompt = new AutocompletePrompt({
|
|
175
|
+
options: opts.options,
|
|
176
|
+
multiple: true,
|
|
177
|
+
placeholder,
|
|
178
|
+
filter,
|
|
179
|
+
initialValue: opts.initialValues,
|
|
180
|
+
signal: opts.signal,
|
|
181
|
+
input: opts.input,
|
|
182
|
+
output: opts.output,
|
|
183
|
+
validate: () => {
|
|
184
|
+
if (opts.required && prompt.selectedValues.length === 0) {
|
|
185
|
+
return '请至少选择一项'
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
render() {
|
|
189
|
+
const msgLine = `${withGuide ? `${styleText('gray', S_BAR)}\n` : ''}${symbol(this.state)} ${opts.message}\n`
|
|
190
|
+
const userInput = this.userInput
|
|
191
|
+
const isPlaceholder = userInput === '' && placeholder !== undefined
|
|
192
|
+
const searchDisplay = this.isNavigating || isPlaceholder
|
|
193
|
+
? styleText('dim', isPlaceholder ? placeholder : userInput)
|
|
194
|
+
: this.userInputWithCursor
|
|
195
|
+
const matchHint = this.filteredOptions.length !== this.options.length
|
|
196
|
+
? styleText('dim', ` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? '' : 'es'})`)
|
|
197
|
+
: ''
|
|
198
|
+
|
|
199
|
+
if (this.state === 'submit') {
|
|
200
|
+
return `${msgLine}${withGuide ? `${styleText('gray', S_BAR)} ` : ''}${styleText(
|
|
201
|
+
'dim',
|
|
202
|
+
`已选 ${this.selectedValues.length} 项`,
|
|
203
|
+
)}`
|
|
204
|
+
}
|
|
205
|
+
if (this.state === 'cancel') {
|
|
206
|
+
return `${msgLine}${withGuide ? `${styleText('gray', S_BAR)} ` : ''}${styleText(
|
|
207
|
+
['strikethrough', 'dim'],
|
|
208
|
+
userInput,
|
|
209
|
+
)}`
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const barColor = this.state === 'error' ? 'yellow' : 'cyan'
|
|
213
|
+
const bar = withGuide ? `${styleText(barColor, S_BAR)} ` : ''
|
|
214
|
+
const barEnd = withGuide ? styleText(barColor, S_BAR_END) : ''
|
|
215
|
+
const noMatch = this.filteredOptions.length === 0 && userInput
|
|
216
|
+
? [`${bar}${styleText('yellow', '无匹配项')}`]
|
|
217
|
+
: []
|
|
218
|
+
const errLines = this.state === 'error'
|
|
219
|
+
? [`${bar}${styleText('yellow', this.error)}`]
|
|
220
|
+
: []
|
|
221
|
+
|
|
222
|
+
const header = [
|
|
223
|
+
...`${msgLine}${withGuide ? styleText(barColor, S_BAR) : ''}`.split('\n'),
|
|
224
|
+
`${bar}${styleText('dim', 'Search:')} ${searchDisplay}${matchHint}`,
|
|
225
|
+
...noMatch,
|
|
226
|
+
...errLines,
|
|
227
|
+
]
|
|
228
|
+
|
|
229
|
+
// 底部:说明区 + 快捷键(说明区行数固定,计入 rowPadding)
|
|
230
|
+
const cols = getColumns(opts.output ?? process.stdout)
|
|
231
|
+
const contentWidth = Math.max(cols - (withGuide ? 10 : 6), 20)
|
|
232
|
+
const descPanel = buildDescPanel(
|
|
233
|
+
resolveDescription(this.filteredOptions, this.focusedValue),
|
|
234
|
+
bar,
|
|
235
|
+
contentWidth,
|
|
236
|
+
)
|
|
237
|
+
const instructions = [
|
|
238
|
+
`${styleText('dim', '↑/↓')} to navigate`,
|
|
239
|
+
`${styleText('dim', this.isNavigating ? 'Space/Tab:' : 'Tab:')} select`,
|
|
240
|
+
`${styleText('dim', 'Enter:')} confirm`,
|
|
241
|
+
`${styleText('dim', 'Type:')} to search`,
|
|
242
|
+
]
|
|
243
|
+
const footer = [
|
|
244
|
+
`${bar}${styleText('dim', S_BAR_H())}`,
|
|
245
|
+
...descPanel,
|
|
246
|
+
`${bar}${instructions.join(' · ')}`,
|
|
247
|
+
barEnd,
|
|
248
|
+
].filter(Boolean)
|
|
249
|
+
|
|
250
|
+
const listLines = this.filteredOptions.length === 0
|
|
251
|
+
? []
|
|
252
|
+
: limitOptions({
|
|
253
|
+
cursor: this.cursor,
|
|
254
|
+
options: this.filteredOptions,
|
|
255
|
+
maxItems: opts.maxItems,
|
|
256
|
+
output: opts.output,
|
|
257
|
+
rowPadding: header.length + footer.length,
|
|
258
|
+
style: (opt, active) => {
|
|
259
|
+
const selected = this.selectedValues.includes(opt.value)
|
|
260
|
+
const label = optionLabel(opt)
|
|
261
|
+
const box = selected
|
|
262
|
+
? styleText('green', S_CHECKBOX_SELECTED)
|
|
263
|
+
: styleText('dim', S_CHECKBOX_INACTIVE)
|
|
264
|
+
if (opt.disabled) {
|
|
265
|
+
return `${styleText('gray', S_CHECKBOX_INACTIVE)} ${styleText(['strikethrough', 'gray'], label)}`
|
|
266
|
+
}
|
|
267
|
+
// 聚焦也不拼 hint,描述只在底部公共区
|
|
268
|
+
if (active) {
|
|
269
|
+
return `${box} ${label}`
|
|
270
|
+
}
|
|
271
|
+
return `${box} ${styleText('dim', label)}`
|
|
272
|
+
},
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
return [
|
|
276
|
+
...header,
|
|
277
|
+
...listLines.map(line => `${bar}${line}`),
|
|
278
|
+
...footer,
|
|
279
|
+
].join('\n')
|
|
280
|
+
},
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
return prompt.prompt()
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** 横线字符(与 clack 一致) */
|
|
287
|
+
function S_BAR_H() {
|
|
288
|
+
return unicodeOr('─', '-')
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* 可搜索单选:列表无行内 hint,底部公共区展示当前项说明。
|
|
293
|
+
*
|
|
294
|
+
* @param {object} opts
|
|
295
|
+
* @param {string} opts.message
|
|
296
|
+
* @param {Array<{ value: unknown, label?: string, description?: string, disabled?: boolean }>} opts.options
|
|
297
|
+
* @param {unknown} [opts.initialValue]
|
|
298
|
+
* @param {number} [opts.maxItems]
|
|
299
|
+
* @param {string} [opts.placeholder]
|
|
300
|
+
* @param {(search: string, opt: object) => boolean} [opts.filter]
|
|
301
|
+
* @returns {Promise<unknown | symbol>}
|
|
302
|
+
*/
|
|
303
|
+
export function searchableSelect(opts) {
|
|
304
|
+
const withGuide = opts.withGuide ?? settings.withGuide
|
|
305
|
+
const placeholder = opts.placeholder ?? SEARCH_PLACEHOLDER
|
|
306
|
+
const filter = opts.filter ?? defaultFilter
|
|
307
|
+
|
|
308
|
+
const prompt = new AutocompletePrompt({
|
|
309
|
+
options: opts.options,
|
|
310
|
+
multiple: false,
|
|
311
|
+
placeholder,
|
|
312
|
+
filter,
|
|
313
|
+
initialValue: opts.initialValue !== undefined ? [opts.initialValue] : undefined,
|
|
314
|
+
signal: opts.signal,
|
|
315
|
+
input: opts.input,
|
|
316
|
+
output: opts.output,
|
|
317
|
+
validate: opts.validate,
|
|
318
|
+
render() {
|
|
319
|
+
const headerBase = withGuide
|
|
320
|
+
? [`${styleText('gray', S_BAR)}`, `${symbol(this.state)} ${opts.message}`]
|
|
321
|
+
: [`${symbol(this.state)} ${opts.message}`]
|
|
322
|
+
const userInput = this.userInput
|
|
323
|
+
const isPlaceholder = userInput === '' && placeholder !== undefined
|
|
324
|
+
let searchDisplay = ''
|
|
325
|
+
if (this.isNavigating || isPlaceholder) {
|
|
326
|
+
const d = isPlaceholder ? placeholder : userInput
|
|
327
|
+
searchDisplay = d !== '' ? ` ${styleText('dim', d)}` : ''
|
|
328
|
+
} else {
|
|
329
|
+
searchDisplay = ` ${this.userInputWithCursor}`
|
|
330
|
+
}
|
|
331
|
+
const matchHint = this.filteredOptions.length !== this.options.length
|
|
332
|
+
? styleText(
|
|
333
|
+
'dim',
|
|
334
|
+
` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? '' : 'es'})`,
|
|
335
|
+
)
|
|
336
|
+
: ''
|
|
337
|
+
|
|
338
|
+
if (this.state === 'submit') {
|
|
339
|
+
const selected = this.filteredOptions.find(o => o.value === this.focusedValue)
|
|
340
|
+
?? opts.options.find(o => o.value === this.focusedValue)
|
|
341
|
+
const text = selected ? optionLabel(selected) : ''
|
|
342
|
+
const body = text ? ` ${styleText('dim', text)}` : ''
|
|
343
|
+
const bar = withGuide ? styleText('gray', S_BAR) : ''
|
|
344
|
+
return `${headerBase.join('\n')}\n${bar}${body}`
|
|
345
|
+
}
|
|
346
|
+
if (this.state === 'cancel') {
|
|
347
|
+
const body = userInput ? ` ${styleText(['strikethrough', 'dim'], userInput)}` : ''
|
|
348
|
+
const bar = withGuide ? styleText('gray', S_BAR) : ''
|
|
349
|
+
return `${headerBase.join('\n')}\n${bar}${body}`
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const barColor = this.state === 'error' ? 'yellow' : 'cyan'
|
|
353
|
+
const bar = withGuide ? `${styleText(barColor, S_BAR)} ` : ''
|
|
354
|
+
const barEnd = withGuide ? styleText(barColor, S_BAR_END) : ''
|
|
355
|
+
const noMatch = this.filteredOptions.length === 0 && userInput
|
|
356
|
+
? [`${bar}${styleText('yellow', '无匹配项')}`]
|
|
357
|
+
: []
|
|
358
|
+
const errLines = this.state === 'error'
|
|
359
|
+
? [`${bar}${styleText('yellow', this.error)}`]
|
|
360
|
+
: []
|
|
361
|
+
|
|
362
|
+
const header = [...headerBase]
|
|
363
|
+
if (withGuide) header.push(`${bar.trimEnd()}`)
|
|
364
|
+
header.push(
|
|
365
|
+
`${bar}${styleText('dim', 'Search:')}${searchDisplay}${matchHint}`,
|
|
366
|
+
...noMatch,
|
|
367
|
+
...errLines,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
const cols = getColumns(opts.output ?? process.stdout)
|
|
371
|
+
const contentWidth = Math.max(cols - (withGuide ? 10 : 6), 20)
|
|
372
|
+
const descPanel = buildDescPanel(
|
|
373
|
+
resolveDescription(this.filteredOptions, this.focusedValue),
|
|
374
|
+
bar,
|
|
375
|
+
contentWidth,
|
|
376
|
+
)
|
|
377
|
+
const instructions = [
|
|
378
|
+
`${styleText('dim', '↑/↓')} to select`,
|
|
379
|
+
`${styleText('dim', 'Enter:')} confirm`,
|
|
380
|
+
`${styleText('dim', 'Type:')} to search`,
|
|
381
|
+
]
|
|
382
|
+
const footer = [
|
|
383
|
+
`${bar}${styleText('dim', S_BAR_H())}`,
|
|
384
|
+
...descPanel,
|
|
385
|
+
`${bar}${instructions.join(' · ')}`,
|
|
386
|
+
barEnd,
|
|
387
|
+
].filter(Boolean)
|
|
388
|
+
|
|
389
|
+
const listLines = this.filteredOptions.length === 0
|
|
390
|
+
? []
|
|
391
|
+
: limitOptions({
|
|
392
|
+
cursor: this.cursor,
|
|
393
|
+
options: this.filteredOptions,
|
|
394
|
+
maxItems: opts.maxItems,
|
|
395
|
+
output: opts.output,
|
|
396
|
+
columnPadding: withGuide ? 3 : 0,
|
|
397
|
+
rowPadding: header.length + footer.length,
|
|
398
|
+
style: (opt, active) => {
|
|
399
|
+
const label = optionLabel(opt)
|
|
400
|
+
if (opt.disabled) {
|
|
401
|
+
return `${styleText('gray', S_RADIO_INACTIVE)} ${styleText(['strikethrough', 'gray'], label)}`
|
|
402
|
+
}
|
|
403
|
+
if (active) {
|
|
404
|
+
return `${styleText('green', S_RADIO_ACTIVE)} ${label}`
|
|
405
|
+
}
|
|
406
|
+
return `${styleText('dim', S_RADIO_INACTIVE)} ${styleText('dim', label)}`
|
|
407
|
+
},
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
return [
|
|
411
|
+
...header,
|
|
412
|
+
...listLines.map(line => `${bar}${line}`),
|
|
413
|
+
...footer,
|
|
414
|
+
].join('\n')
|
|
415
|
+
},
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
return prompt.prompt()
|
|
419
|
+
}
|