create-dwy 0.18.3 → 0.18.4
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 +8 -0
- package/package.json +1 -1
- package/src/searchable-select.js +1 -9
- package/src/sync-scene.js +112 -95
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/src/searchable-select.js
CHANGED
|
@@ -263,15 +263,7 @@ export function searchableMultiselect(opts) {
|
|
|
263
263
|
? styleText('green', S_CHECKBOX_SELECTED)
|
|
264
264
|
: styleText('dim', S_CHECKBOX_INACTIVE)
|
|
265
265
|
if (opt.disabled) {
|
|
266
|
-
|
|
267
|
-
const packOn = opt.packId && this.selectedValues.includes(opt.packId)
|
|
268
|
-
if (packOn) {
|
|
269
|
-
const previewBox = styleText('green', S_CHECKBOX_SELECTED)
|
|
270
|
-
return active
|
|
271
|
-
? `${previewBox} ${label}`
|
|
272
|
-
: `${previewBox} ${styleText('dim', label)}`
|
|
273
|
-
}
|
|
274
|
-
return `${styleText('dim', S_CHECKBOX_INACTIVE)} ${styleText('dim', label)}`
|
|
266
|
+
return `${styleText('dim', S_CHECKBOX_INACTIVE)} ${styleText(['strikethrough', 'dim'], label)}`
|
|
275
267
|
}
|
|
276
268
|
// 聚焦也不拼 hint,描述只在底部公共区
|
|
277
269
|
if (active) {
|
package/src/sync-scene.js
CHANGED
|
@@ -1,100 +1,118 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 按包勾选交互:技术栈包 →
|
|
2
|
+
* 按包勾选交互:技术栈包 → 场景包。
|
|
3
|
+
* 用 @clack/prompts groupMultiselect:勾父级=全选子项,子项可单独取消。
|
|
3
4
|
* 由 dwy / dwy sync 在「按场景和技术栈」模式下调用。
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import { isCancel } from '@clack/prompts'
|
|
7
|
-
import { SEARCH_PLACEHOLDER,
|
|
7
|
+
import { groupMultiselect, isCancel } from '@clack/prompts'
|
|
8
|
+
import { SEARCH_PLACEHOLDER, searchableSelect } from './searchable-select.js'
|
|
8
9
|
import {
|
|
9
10
|
SCENE_PACKS,
|
|
10
11
|
STACK_PACKS,
|
|
11
|
-
expandPackSelection,
|
|
12
12
|
listPackItems,
|
|
13
13
|
normalizePacks,
|
|
14
|
-
packLabelForItem,
|
|
15
14
|
} from './sync-packs.js'
|
|
16
15
|
import { chalk } from './utils.js'
|
|
17
16
|
|
|
17
|
+
/** 子 value 三段分隔:packId / type / name。packId 不含冒号。 */
|
|
18
|
+
const PACK_CHILD_RE = /^([^:]+):(skills|rules|hooks):(.+)$/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 子条目在 groupMultiselect 里的稳定 value。
|
|
22
|
+
* 带 packId:同一 skill 可同时出现在 Vue 与 Python,取消一边不能误伤另一边。
|
|
23
|
+
*
|
|
24
|
+
* @param {string} packId
|
|
25
|
+
* @param {'skills' | 'rules' | 'hooks'} type
|
|
26
|
+
* @param {string} name
|
|
27
|
+
*/
|
|
28
|
+
export function packChildKey(packId, type, name) {
|
|
29
|
+
return `${packId}:${type}:${name}`
|
|
30
|
+
}
|
|
31
|
+
|
|
18
32
|
/**
|
|
19
|
-
*
|
|
33
|
+
* 解析 packChildKey。对不上的丢掉,避免脏 initialValues 撑进结果。
|
|
34
|
+
*
|
|
35
|
+
* @param {unknown} key
|
|
36
|
+
* @returns {{ packId: string, type: 'skills' | 'rules' | 'hooks', name: string } | null}
|
|
37
|
+
*/
|
|
38
|
+
export function parsePackChildKey(key) {
|
|
39
|
+
const match = String(key).match(PACK_CHILD_RE)
|
|
40
|
+
if (!match) return null
|
|
41
|
+
return { packId: match[1], type: match[2], name: match[3] }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* groupMultiselect 的 options:key 是分组标题(包名),value 是可单独勾的子项。
|
|
46
|
+
* 空包跳过——clack 空组 `every()` 恒 true,会显示成已全选。
|
|
20
47
|
*
|
|
21
48
|
* @param {object[]} packs
|
|
22
49
|
* @param {object} scans
|
|
23
50
|
* @param {{ skillsOnly?: boolean }} [opts]
|
|
51
|
+
* @returns {Record<string, { value: string, label: string, hint?: string }[]>}
|
|
24
52
|
*/
|
|
25
|
-
export function
|
|
26
|
-
const
|
|
53
|
+
export function packGroupOptions(packs, scans, { skillsOnly = false } = {}) {
|
|
54
|
+
const groups = {}
|
|
27
55
|
for (const pack of packs) {
|
|
28
56
|
const items = listPackItems(pack, scans, { skillsOnly })
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
value: pack.id,
|
|
32
|
-
label:
|
|
33
|
-
|
|
34
|
-
})
|
|
35
|
-
for (const item of items) {
|
|
36
|
-
rows.push({
|
|
37
|
-
value: `hint:${pack.id}:${item.type}:${item.name}`,
|
|
38
|
-
label: ` ${item.name}`,
|
|
39
|
-
description: item.description || pack.label,
|
|
40
|
-
disabled: true,
|
|
41
|
-
packId: pack.id,
|
|
42
|
-
})
|
|
43
|
-
}
|
|
57
|
+
if (items.length === 0) continue
|
|
58
|
+
groups[`${pack.label}(${items.length})`] = items.map(item => ({
|
|
59
|
+
value: packChildKey(pack.id, item.type, item.name),
|
|
60
|
+
label: item.name,
|
|
61
|
+
hint: item.description || undefined,
|
|
62
|
+
}))
|
|
44
63
|
}
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* 子条目在多选里的稳定 key。类型前缀避免 skill/rule 重名撞车。
|
|
50
|
-
*
|
|
51
|
-
* @param {'skills' | 'rules' | 'hooks'} type
|
|
52
|
-
* @param {string} name
|
|
53
|
-
*/
|
|
54
|
-
export function packItemKey(type, name) {
|
|
55
|
-
return `${type}:${name}`
|
|
64
|
+
return groups
|
|
56
65
|
}
|
|
57
66
|
|
|
58
67
|
/**
|
|
59
|
-
*
|
|
68
|
+
* 已选包 → 其下全部子 value,给 groupMultiselect initialValues。
|
|
69
|
+
* 父级勾选态由 clack 根据「子是否全在 value 里」推导,不要传包名。
|
|
60
70
|
*
|
|
61
|
-
* @param {
|
|
62
|
-
* @param {
|
|
71
|
+
* @param {object[]} packs
|
|
72
|
+
* @param {object} scans
|
|
73
|
+
* @param {string[]} selectedPackIds
|
|
74
|
+
* @param {{ skillsOnly?: boolean }} [opts]
|
|
63
75
|
*/
|
|
64
|
-
export function
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
label: `${packLabel} / ${item.name}`,
|
|
72
|
-
description: item.description,
|
|
73
|
-
})
|
|
76
|
+
export function initialChildValues(packs, scans, selectedPackIds, { skillsOnly = false } = {}) {
|
|
77
|
+
const selected = new Set(selectedPackIds)
|
|
78
|
+
const values = []
|
|
79
|
+
for (const pack of packs) {
|
|
80
|
+
if (!selected.has(pack.id)) continue
|
|
81
|
+
for (const item of listPackItems(pack, scans, { skillsOnly })) {
|
|
82
|
+
values.push(packChildKey(pack.id, item.type, item.name))
|
|
74
83
|
}
|
|
75
84
|
}
|
|
76
|
-
|
|
77
|
-
return rows
|
|
85
|
+
return values
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
/**
|
|
81
|
-
*
|
|
89
|
+
* 把勾上的子 value 还原成条目 + 所属包。
|
|
90
|
+
* 只认扫描结果里真实存在的名字,防止 key 伪造。
|
|
82
91
|
*
|
|
83
|
-
* @param {{ skills: object[], rules: object[], hooks: object[] }} expanded
|
|
84
92
|
* @param {string[]} keys
|
|
93
|
+
* @param {object} scans
|
|
94
|
+
* @param {{ skillsOnly?: boolean }} [opts]
|
|
85
95
|
*/
|
|
86
|
-
export function
|
|
87
|
-
const
|
|
96
|
+
export function itemsFromChildKeys(keys, scans, { skillsOnly = false } = {}) {
|
|
97
|
+
const parsed = keys.map(parsePackChildKey).filter(Boolean)
|
|
98
|
+
const packIds = [...new Set(parsed.map(item => item.packId))]
|
|
99
|
+
const names = { skills: new Set(), rules: new Set(), hooks: new Set() }
|
|
100
|
+
for (const item of parsed) names[item.type].add(item.name)
|
|
101
|
+
const pick = (type) => (scans[type] || []).filter(item => names[type].has(item.name))
|
|
102
|
+
if (skillsOnly) {
|
|
103
|
+
return { packIds, skills: pick('skills'), rules: [], commands: [], hooks: [] }
|
|
104
|
+
}
|
|
88
105
|
return {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
106
|
+
packIds,
|
|
107
|
+
skills: pick('skills'),
|
|
108
|
+
rules: pick('rules'),
|
|
92
109
|
commands: [],
|
|
110
|
+
hooks: pick('hooks'),
|
|
93
111
|
}
|
|
94
112
|
}
|
|
95
113
|
|
|
96
114
|
/**
|
|
97
|
-
*
|
|
115
|
+
* 打印本次按包勾选的包名与条数。
|
|
98
116
|
*
|
|
99
117
|
* @param {string[]} stacks
|
|
100
118
|
* @param {string[]} scenes
|
|
@@ -110,7 +128,7 @@ function printPackSummary(stacks, scenes, selected) {
|
|
|
110
128
|
}
|
|
111
129
|
|
|
112
130
|
/**
|
|
113
|
-
*
|
|
131
|
+
* 按包选择条目。Space 勾父级=该包全部子项;光标在子项上可单独取消。
|
|
114
132
|
* 返回 { skills, rules, commands, hooks, packs } 或 null(取消)。
|
|
115
133
|
*
|
|
116
134
|
* @param {object} scans
|
|
@@ -119,24 +137,31 @@ function printPackSummary(stacks, scenes, selected) {
|
|
|
119
137
|
*/
|
|
120
138
|
export async function interactiveSelectByPacks(scans, syncState, { skillsOnly = false } = {}) {
|
|
121
139
|
const previous = normalizePacks(syncState?.packs)
|
|
122
|
-
|
|
140
|
+
const packOpts = { skillsOnly }
|
|
141
|
+
const defaultStacks = previous.stacks.length > 0 ? previous.stacks : ['common']
|
|
142
|
+
let stacks = defaultStacks
|
|
123
143
|
let scenes = previous.scenes
|
|
144
|
+
// 返回重选时保留已取消的子项,不要用包 id 重新全勾
|
|
145
|
+
let stackKeys = initialChildValues(STACK_PACKS, scans, defaultStacks, packOpts)
|
|
146
|
+
let sceneKeys = initialChildValues(SCENE_PACKS, scans, scenes, packOpts)
|
|
124
147
|
let step = 'stacks'
|
|
125
|
-
const packOpts = { skillsOnly }
|
|
126
148
|
|
|
127
149
|
while (true) {
|
|
128
150
|
if (step === 'stacks') {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
151
|
+
const options = packGroupOptions(STACK_PACKS, scans, packOpts)
|
|
152
|
+
const result = await groupMultiselect({
|
|
153
|
+
message: '选择技术栈(Space 勾整包,也可单独取消某个子项)',
|
|
154
|
+
options,
|
|
155
|
+
initialValues: stackKeys,
|
|
133
156
|
required: true,
|
|
134
157
|
maxItems: 15,
|
|
135
|
-
|
|
136
|
-
descLines: 8,
|
|
158
|
+
selectableGroups: true,
|
|
137
159
|
})
|
|
138
160
|
if (isCancel(result)) return null
|
|
139
|
-
|
|
161
|
+
stackKeys = result
|
|
162
|
+
stacks = STACK_PACKS.map(pack => pack.id).filter(id =>
|
|
163
|
+
stackKeys.some(key => parsePackChildKey(key)?.packId === id),
|
|
164
|
+
)
|
|
140
165
|
|
|
141
166
|
const nav = await searchableSelect({
|
|
142
167
|
message: `已选 ${stacks.length} 个技术栈。下一步:`,
|
|
@@ -153,41 +178,33 @@ export async function interactiveSelectByPacks(scans, syncState, { skillsOnly =
|
|
|
153
178
|
continue
|
|
154
179
|
}
|
|
155
180
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
181
|
+
const sceneOptions = packGroupOptions(SCENE_PACKS, scans, packOpts)
|
|
182
|
+
if (Object.keys(sceneOptions).length > 0) {
|
|
183
|
+
const result = await groupMultiselect({
|
|
184
|
+
message: '选择场景包(可全不选;Space 勾整包,也可单独取消某个子项)',
|
|
185
|
+
options: sceneOptions,
|
|
186
|
+
initialValues: sceneKeys,
|
|
161
187
|
required: false,
|
|
162
188
|
maxItems: 15,
|
|
163
|
-
|
|
164
|
-
descLines: 8,
|
|
189
|
+
selectableGroups: true,
|
|
165
190
|
})
|
|
166
191
|
if (isCancel(result)) return null
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const packIds = [...stacks, ...scenes]
|
|
175
|
-
const options = buildPackItemOptions(expanded, packIds)
|
|
176
|
-
if (options.length === 0) {
|
|
177
|
-
return { skills: [], rules: [], commands: [], hooks: [], packs: { stacks, scenes } }
|
|
192
|
+
sceneKeys = result
|
|
193
|
+
scenes = SCENE_PACKS.map(pack => pack.id).filter(id =>
|
|
194
|
+
sceneKeys.some(key => parsePackChildKey(key)?.packId === id),
|
|
195
|
+
)
|
|
196
|
+
} else {
|
|
197
|
+
sceneKeys = []
|
|
198
|
+
scenes = []
|
|
178
199
|
}
|
|
179
200
|
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
options,
|
|
183
|
-
initialValues: options.map(option => option.value),
|
|
184
|
-
required: false,
|
|
185
|
-
maxItems: 15,
|
|
186
|
-
placeholder: SEARCH_PLACEHOLDER,
|
|
187
|
-
})
|
|
188
|
-
if (isCancel(keys)) return null
|
|
201
|
+
const selected = itemsFromChildKeys([...stackKeys, ...sceneKeys], scans, packOpts)
|
|
202
|
+
printPackSummary(stacks, scenes, selected)
|
|
189
203
|
return {
|
|
190
|
-
|
|
204
|
+
skills: selected.skills,
|
|
205
|
+
rules: selected.rules,
|
|
206
|
+
commands: selected.commands,
|
|
207
|
+
hooks: selected.hooks,
|
|
191
208
|
packs: { stacks, scenes },
|
|
192
209
|
}
|
|
193
210
|
}
|