create-dwy 0.18.1 → 0.18.3
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 +14 -5
- package/src/sync-packs.js +32 -1
- package/src/sync-scene.js +55 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# create-dwy
|
|
2
2
|
|
|
3
|
+
## 0.18.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 技术栈/场景包下的子条目随父包显示为已勾选,不再画成删除线空框。逐条取消仍在下一步预勾清单里。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 0.18.2
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 技术栈包与场景包都会在包名下列出子条目;选包后统一进入预勾清单,可逐条取消。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
3
19
|
## 0.18.1
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/package.json
CHANGED
package/src/searchable-select.js
CHANGED
|
@@ -121,14 +121,13 @@ function wrapHard(text, width) {
|
|
|
121
121
|
* @param {number} contentWidth 可写字符宽
|
|
122
122
|
* @returns {string[]}
|
|
123
123
|
*/
|
|
124
|
-
function buildDescPanel(description, barPrefix, contentWidth) {
|
|
124
|
+
function buildDescPanel(description, barPrefix, contentWidth, lineCount = DESC_PANEL_LINES) {
|
|
125
125
|
const raw = (description && description.trim()) ? description.trim() : '(无说明)'
|
|
126
126
|
const wrapped = wrapHard(raw, contentWidth)
|
|
127
|
-
// 固定 DESC_PANEL_LINES 行:超出截断末行加省略,不足补空
|
|
128
127
|
const lines = []
|
|
129
|
-
for (let i = 0; i <
|
|
128
|
+
for (let i = 0; i < lineCount; i++) {
|
|
130
129
|
let part = wrapped[i] ?? ''
|
|
131
|
-
if (i ===
|
|
130
|
+
if (i === lineCount - 1 && wrapped.length > lineCount) {
|
|
132
131
|
const ellipsis = '…'
|
|
133
132
|
part = (wrapped[i] ?? '').slice(0, Math.max(contentWidth - ellipsis.length, 0)) + ellipsis
|
|
134
133
|
}
|
|
@@ -162,6 +161,7 @@ function resolveDescription(options, focusedValue) {
|
|
|
162
161
|
* @param {unknown[]} [opts.initialValues]
|
|
163
162
|
* @param {boolean} [opts.required]
|
|
164
163
|
* @param {number} [opts.maxItems]
|
|
164
|
+
* @param {number} [opts.descLines] 底部说明区行数,包内子条目列表可加高
|
|
165
165
|
* @param {string} [opts.placeholder]
|
|
166
166
|
* @param {(search: string, opt: object) => boolean} [opts.filter]
|
|
167
167
|
* @returns {Promise<unknown[] | symbol>}
|
|
@@ -233,6 +233,7 @@ export function searchableMultiselect(opts) {
|
|
|
233
233
|
resolveDescription(this.filteredOptions, this.focusedValue),
|
|
234
234
|
bar,
|
|
235
235
|
contentWidth,
|
|
236
|
+
opts.descLines ?? DESC_PANEL_LINES,
|
|
236
237
|
)
|
|
237
238
|
const instructions = [
|
|
238
239
|
`${styleText('dim', '↑/↓')} to navigate`,
|
|
@@ -262,7 +263,15 @@ export function searchableMultiselect(opts) {
|
|
|
262
263
|
? styleText('green', S_CHECKBOX_SELECTED)
|
|
263
264
|
: styleText('dim', S_CHECKBOX_INACTIVE)
|
|
264
265
|
if (opt.disabled) {
|
|
265
|
-
|
|
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
275
|
}
|
|
267
276
|
// 聚焦也不拼 hint,描述只在底部公共区
|
|
268
277
|
if (active) {
|
package/src/sync-packs.js
CHANGED
|
@@ -131,7 +131,7 @@ export function normalizePacks(raw) {
|
|
|
131
131
|
* @param {object} pack
|
|
132
132
|
* @param {'skills' | 'rules' | 'hooks'} type
|
|
133
133
|
*/
|
|
134
|
-
function itemInPack(item, pack, type) {
|
|
134
|
+
export function itemInPack(item, pack, type) {
|
|
135
135
|
if (type === 'skills') {
|
|
136
136
|
if ((pack.skillNames || []).includes(item.name)) return true
|
|
137
137
|
return (pack.skillCategories || []).includes(item.category)
|
|
@@ -163,6 +163,37 @@ function uniqueByName(items) {
|
|
|
163
163
|
* @param {{ skills: object[], rules: object[], commands?: object[], hooks: object[] }} scans
|
|
164
164
|
* @param {{ stacks?: string[], scenes?: string[], skillsOnly?: boolean }} selection
|
|
165
165
|
*/
|
|
166
|
+
/**
|
|
167
|
+
* 列出某包在当前扫描结果里的子条目,带 type,供界面展示「包下面有哪些」。
|
|
168
|
+
*
|
|
169
|
+
* @param {object} pack
|
|
170
|
+
* @param {{ skills?: object[], rules?: object[], hooks?: object[] }} scans
|
|
171
|
+
* @param {{ skillsOnly?: boolean }} [opts]
|
|
172
|
+
*/
|
|
173
|
+
export function listPackItems(pack, scans, { skillsOnly = false } = {}) {
|
|
174
|
+
const types = skillsOnly ? ['skills'] : ['skills', 'rules', 'hooks']
|
|
175
|
+
const items = []
|
|
176
|
+
for (const type of types) {
|
|
177
|
+
for (const item of scans[type] || []) {
|
|
178
|
+
if (itemInPack(item, pack, type)) items.push({ ...item, type })
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return items
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 条目属于已选包时,用包的中文名做分组前缀。
|
|
186
|
+
*
|
|
187
|
+
* @param {{ name: string, category?: string }} item
|
|
188
|
+
* @param {'skills' | 'rules' | 'hooks'} type
|
|
189
|
+
* @param {string[]} packIds
|
|
190
|
+
*/
|
|
191
|
+
export function packLabelForItem(item, type, packIds) {
|
|
192
|
+
const packs = [...STACK_PACKS, ...SCENE_PACKS]
|
|
193
|
+
const hit = packs.find(pack => packIds.includes(pack.id) && itemInPack(item, pack, type))
|
|
194
|
+
return hit?.label || item.category || '其他'
|
|
195
|
+
}
|
|
196
|
+
|
|
166
197
|
export function expandPackSelection(scans, { stacks = [], scenes = [], skillsOnly = false } = {}) {
|
|
167
198
|
const packById = new Map([...STACK_PACKS, ...SCENE_PACKS].map(pack => [pack.id, pack]))
|
|
168
199
|
const selectedPacks = [...stacks, ...scenes]
|
package/src/sync-scene.js
CHANGED
|
@@ -1,30 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 按包勾选交互:技术栈包 →
|
|
2
|
+
* 按包勾选交互:技术栈包 → 场景包(包下挂子条目)→ 预勾子条目可取消。
|
|
3
3
|
* 由 dwy / dwy sync 在「按场景和技术栈」模式下调用。
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { isCancel } from '@clack/prompts'
|
|
7
7
|
import { SEARCH_PLACEHOLDER, searchableMultiselect, searchableSelect } from './searchable-select.js'
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
SCENE_PACKS,
|
|
10
|
+
STACK_PACKS,
|
|
11
|
+
expandPackSelection,
|
|
12
|
+
listPackItems,
|
|
13
|
+
normalizePacks,
|
|
14
|
+
packLabelForItem,
|
|
15
|
+
} from './sync-packs.js'
|
|
9
16
|
import { chalk } from './utils.js'
|
|
10
17
|
|
|
11
|
-
const TYPE_LABELS = {
|
|
12
|
-
skills: 'Skills',
|
|
13
|
-
rules: 'Rules',
|
|
14
|
-
hooks: 'Hooks',
|
|
15
|
-
}
|
|
16
|
-
|
|
17
18
|
/**
|
|
18
|
-
*
|
|
19
|
+
* 包名 + 其下子条目。子条目 disabled,只展示不参与勾选;勾的是整包。
|
|
19
20
|
*
|
|
20
|
-
* @param {
|
|
21
|
+
* @param {object[]} packs
|
|
22
|
+
* @param {object} scans
|
|
23
|
+
* @param {{ skillsOnly?: boolean }} [opts]
|
|
21
24
|
*/
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
export function packOptionsWithChildren(packs, scans, { skillsOnly = false } = {}) {
|
|
26
|
+
const rows = []
|
|
27
|
+
for (const pack of packs) {
|
|
28
|
+
const items = listPackItems(pack, scans, { skillsOnly })
|
|
29
|
+
const names = items.map(item => item.name)
|
|
30
|
+
rows.push({
|
|
31
|
+
value: pack.id,
|
|
32
|
+
label: `${pack.label}(${items.length})`,
|
|
33
|
+
description: names.length > 0 ? names.join('、') : pack.description,
|
|
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
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return rows
|
|
28
46
|
}
|
|
29
47
|
|
|
30
48
|
/**
|
|
@@ -38,21 +56,24 @@ export function packItemKey(type, name) {
|
|
|
38
56
|
}
|
|
39
57
|
|
|
40
58
|
/**
|
|
41
|
-
*
|
|
59
|
+
* 把包展开结果打成一条可搜索列表,按包名分组,便于对照取消。
|
|
42
60
|
*
|
|
43
61
|
* @param {{ skills: object[], rules: object[], hooks: object[] }} selected
|
|
62
|
+
* @param {string[]} packIds
|
|
44
63
|
*/
|
|
45
|
-
export function buildPackItemOptions(selected) {
|
|
64
|
+
export function buildPackItemOptions(selected, packIds = []) {
|
|
46
65
|
const rows = []
|
|
47
66
|
for (const type of ['skills', 'rules', 'hooks']) {
|
|
48
67
|
for (const item of selected[type] || []) {
|
|
68
|
+
const packLabel = packLabelForItem(item, type, packIds)
|
|
49
69
|
rows.push({
|
|
50
70
|
value: packItemKey(type, item.name),
|
|
51
|
-
label: `${
|
|
71
|
+
label: `${packLabel} / ${item.name}`,
|
|
52
72
|
description: item.description,
|
|
53
73
|
})
|
|
54
74
|
}
|
|
55
75
|
}
|
|
76
|
+
rows.sort((a, b) => a.label.localeCompare(b.label, 'zh'))
|
|
56
77
|
return rows
|
|
57
78
|
}
|
|
58
79
|
|
|
@@ -89,7 +110,7 @@ function printPackSummary(stacks, scenes, selected) {
|
|
|
89
110
|
}
|
|
90
111
|
|
|
91
112
|
/**
|
|
92
|
-
*
|
|
113
|
+
* 按包选择条目。选包时列出包下子条目;确认后默认全勾,用户可取消。
|
|
93
114
|
* 返回 { skills, rules, commands, hooks, packs } 或 null(取消)。
|
|
94
115
|
*
|
|
95
116
|
* @param {object} scans
|
|
@@ -101,19 +122,21 @@ export async function interactiveSelectByPacks(scans, syncState, { skillsOnly =
|
|
|
101
122
|
let stacks = previous.stacks.length > 0 ? previous.stacks : ['common']
|
|
102
123
|
let scenes = previous.scenes
|
|
103
124
|
let step = 'stacks'
|
|
125
|
+
const packOpts = { skillsOnly }
|
|
104
126
|
|
|
105
127
|
while (true) {
|
|
106
128
|
if (step === 'stacks') {
|
|
107
129
|
const result = await searchableMultiselect({
|
|
108
|
-
message: '
|
|
109
|
-
options:
|
|
130
|
+
message: '选择技术栈(通用默认勾选;包下列出子条目,下一步可逐条取消)',
|
|
131
|
+
options: packOptionsWithChildren(STACK_PACKS, scans, packOpts),
|
|
110
132
|
initialValues: stacks,
|
|
111
133
|
required: true,
|
|
112
|
-
maxItems:
|
|
134
|
+
maxItems: 15,
|
|
113
135
|
placeholder: SEARCH_PLACEHOLDER,
|
|
136
|
+
descLines: 8,
|
|
114
137
|
})
|
|
115
138
|
if (isCancel(result)) return null
|
|
116
|
-
stacks = result
|
|
139
|
+
stacks = result.filter(id => !String(id).startsWith('hint:'))
|
|
117
140
|
|
|
118
141
|
const nav = await searchableSelect({
|
|
119
142
|
message: `已选 ${stacks.length} 个技术栈。下一步:`,
|
|
@@ -132,28 +155,30 @@ export async function interactiveSelectByPacks(scans, syncState, { skillsOnly =
|
|
|
132
155
|
|
|
133
156
|
if (step === 'scenes') {
|
|
134
157
|
const result = await searchableMultiselect({
|
|
135
|
-
message: '
|
|
136
|
-
options:
|
|
158
|
+
message: '选择场景包(可全不选;包下列出子条目,下一步可逐条取消)',
|
|
159
|
+
options: packOptionsWithChildren(SCENE_PACKS, scans, packOpts),
|
|
137
160
|
initialValues: scenes,
|
|
138
161
|
required: false,
|
|
139
|
-
maxItems:
|
|
162
|
+
maxItems: 15,
|
|
140
163
|
placeholder: SEARCH_PLACEHOLDER,
|
|
164
|
+
descLines: 8,
|
|
141
165
|
})
|
|
142
166
|
if (isCancel(result)) return null
|
|
143
|
-
scenes = result
|
|
167
|
+
scenes = result.filter(id => !String(id).startsWith('hint:'))
|
|
144
168
|
step = 'items'
|
|
145
169
|
continue
|
|
146
170
|
}
|
|
147
171
|
|
|
148
172
|
const expanded = expandPackSelection(scans, { stacks, scenes, skillsOnly })
|
|
149
173
|
printPackSummary(stacks, scenes, expanded)
|
|
150
|
-
const
|
|
174
|
+
const packIds = [...stacks, ...scenes]
|
|
175
|
+
const options = buildPackItemOptions(expanded, packIds)
|
|
151
176
|
if (options.length === 0) {
|
|
152
177
|
return { skills: [], rules: [], commands: [], hooks: [], packs: { stacks, scenes } }
|
|
153
178
|
}
|
|
154
179
|
|
|
155
180
|
const keys = await searchableMultiselect({
|
|
156
|
-
message: '
|
|
181
|
+
message: '已按包预勾子条目,取消不需要的即可',
|
|
157
182
|
options,
|
|
158
183
|
initialValues: options.map(option => option.value),
|
|
159
184
|
required: false,
|