create-dwy 0.18.2 → 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 +16 -0
- package/package.json +1 -1
- package/src/searchable-select.js +1 -1
- package/src/sync-scene.js +112 -94
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# create-dwy
|
|
2
2
|
|
|
3
|
+
## 0.18.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 按包勾选改用 `@clack/prompts` 的 `groupMultiselect`:Space 勾父级=全选子项,子项可单独取消。去掉「下一步再取消子项」那一屏。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 0.18.3
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 技术栈/场景包下的子条目随父包显示为已勾选,不再画成删除线空框。逐条取消仍在下一步预勾清单里。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
3
19
|
## 0.18.2
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/package.json
CHANGED
package/src/searchable-select.js
CHANGED
|
@@ -263,7 +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
|
-
return `${styleText('
|
|
266
|
+
return `${styleText('dim', S_CHECKBOX_INACTIVE)} ${styleText(['strikethrough', 'dim'], label)}`
|
|
267
267
|
}
|
|
268
268
|
// 聚焦也不拼 hint,描述只在底部公共区
|
|
269
269
|
if (active) {
|
package/src/sync-scene.js
CHANGED
|
@@ -1,99 +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
|
-
})
|
|
42
|
-
}
|
|
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
|
+
}))
|
|
43
63
|
}
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* 子条目在多选里的稳定 key。类型前缀避免 skill/rule 重名撞车。
|
|
49
|
-
*
|
|
50
|
-
* @param {'skills' | 'rules' | 'hooks'} type
|
|
51
|
-
* @param {string} name
|
|
52
|
-
*/
|
|
53
|
-
export function packItemKey(type, name) {
|
|
54
|
-
return `${type}:${name}`
|
|
64
|
+
return groups
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
/**
|
|
58
|
-
*
|
|
68
|
+
* 已选包 → 其下全部子 value,给 groupMultiselect initialValues。
|
|
69
|
+
* 父级勾选态由 clack 根据「子是否全在 value 里」推导,不要传包名。
|
|
59
70
|
*
|
|
60
|
-
* @param {
|
|
61
|
-
* @param {
|
|
71
|
+
* @param {object[]} packs
|
|
72
|
+
* @param {object} scans
|
|
73
|
+
* @param {string[]} selectedPackIds
|
|
74
|
+
* @param {{ skillsOnly?: boolean }} [opts]
|
|
62
75
|
*/
|
|
63
|
-
export function
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
label: `${packLabel} / ${item.name}`,
|
|
71
|
-
description: item.description,
|
|
72
|
-
})
|
|
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))
|
|
73
83
|
}
|
|
74
84
|
}
|
|
75
|
-
|
|
76
|
-
return rows
|
|
85
|
+
return values
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
/**
|
|
80
|
-
*
|
|
89
|
+
* 把勾上的子 value 还原成条目 + 所属包。
|
|
90
|
+
* 只认扫描结果里真实存在的名字,防止 key 伪造。
|
|
81
91
|
*
|
|
82
|
-
* @param {{ skills: object[], rules: object[], hooks: object[] }} expanded
|
|
83
92
|
* @param {string[]} keys
|
|
93
|
+
* @param {object} scans
|
|
94
|
+
* @param {{ skillsOnly?: boolean }} [opts]
|
|
84
95
|
*/
|
|
85
|
-
export function
|
|
86
|
-
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
|
+
}
|
|
87
105
|
return {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
106
|
+
packIds,
|
|
107
|
+
skills: pick('skills'),
|
|
108
|
+
rules: pick('rules'),
|
|
91
109
|
commands: [],
|
|
110
|
+
hooks: pick('hooks'),
|
|
92
111
|
}
|
|
93
112
|
}
|
|
94
113
|
|
|
95
114
|
/**
|
|
96
|
-
*
|
|
115
|
+
* 打印本次按包勾选的包名与条数。
|
|
97
116
|
*
|
|
98
117
|
* @param {string[]} stacks
|
|
99
118
|
* @param {string[]} scenes
|
|
@@ -109,7 +128,7 @@ function printPackSummary(stacks, scenes, selected) {
|
|
|
109
128
|
}
|
|
110
129
|
|
|
111
130
|
/**
|
|
112
|
-
*
|
|
131
|
+
* 按包选择条目。Space 勾父级=该包全部子项;光标在子项上可单独取消。
|
|
113
132
|
* 返回 { skills, rules, commands, hooks, packs } 或 null(取消)。
|
|
114
133
|
*
|
|
115
134
|
* @param {object} scans
|
|
@@ -118,24 +137,31 @@ function printPackSummary(stacks, scenes, selected) {
|
|
|
118
137
|
*/
|
|
119
138
|
export async function interactiveSelectByPacks(scans, syncState, { skillsOnly = false } = {}) {
|
|
120
139
|
const previous = normalizePacks(syncState?.packs)
|
|
121
|
-
|
|
140
|
+
const packOpts = { skillsOnly }
|
|
141
|
+
const defaultStacks = previous.stacks.length > 0 ? previous.stacks : ['common']
|
|
142
|
+
let stacks = defaultStacks
|
|
122
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)
|
|
123
147
|
let step = 'stacks'
|
|
124
|
-
const packOpts = { skillsOnly }
|
|
125
148
|
|
|
126
149
|
while (true) {
|
|
127
150
|
if (step === 'stacks') {
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
151
|
+
const options = packGroupOptions(STACK_PACKS, scans, packOpts)
|
|
152
|
+
const result = await groupMultiselect({
|
|
153
|
+
message: '选择技术栈(Space 勾整包,也可单独取消某个子项)',
|
|
154
|
+
options,
|
|
155
|
+
initialValues: stackKeys,
|
|
132
156
|
required: true,
|
|
133
157
|
maxItems: 15,
|
|
134
|
-
|
|
135
|
-
descLines: 8,
|
|
158
|
+
selectableGroups: true,
|
|
136
159
|
})
|
|
137
160
|
if (isCancel(result)) return null
|
|
138
|
-
|
|
161
|
+
stackKeys = result
|
|
162
|
+
stacks = STACK_PACKS.map(pack => pack.id).filter(id =>
|
|
163
|
+
stackKeys.some(key => parsePackChildKey(key)?.packId === id),
|
|
164
|
+
)
|
|
139
165
|
|
|
140
166
|
const nav = await searchableSelect({
|
|
141
167
|
message: `已选 ${stacks.length} 个技术栈。下一步:`,
|
|
@@ -152,41 +178,33 @@ export async function interactiveSelectByPacks(scans, syncState, { skillsOnly =
|
|
|
152
178
|
continue
|
|
153
179
|
}
|
|
154
180
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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,
|
|
160
187
|
required: false,
|
|
161
188
|
maxItems: 15,
|
|
162
|
-
|
|
163
|
-
descLines: 8,
|
|
189
|
+
selectableGroups: true,
|
|
164
190
|
})
|
|
165
191
|
if (isCancel(result)) return null
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const packIds = [...stacks, ...scenes]
|
|
174
|
-
const options = buildPackItemOptions(expanded, packIds)
|
|
175
|
-
if (options.length === 0) {
|
|
176
|
-
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 = []
|
|
177
199
|
}
|
|
178
200
|
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
options,
|
|
182
|
-
initialValues: options.map(option => option.value),
|
|
183
|
-
required: false,
|
|
184
|
-
maxItems: 15,
|
|
185
|
-
placeholder: SEARCH_PLACEHOLDER,
|
|
186
|
-
})
|
|
187
|
-
if (isCancel(keys)) return null
|
|
201
|
+
const selected = itemsFromChildKeys([...stackKeys, ...sceneKeys], scans, packOpts)
|
|
202
|
+
printPackSummary(stacks, scenes, selected)
|
|
188
203
|
return {
|
|
189
|
-
|
|
204
|
+
skills: selected.skills,
|
|
205
|
+
rules: selected.rules,
|
|
206
|
+
commands: selected.commands,
|
|
207
|
+
hooks: selected.hooks,
|
|
190
208
|
packs: { stacks, scenes },
|
|
191
209
|
}
|
|
192
210
|
}
|