@tyyyho/treg 0.1.20 → 0.1.21
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/dist/init-project/init-prompts.js +39 -207
- package/package.json +2 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { stdin as input, stdout as output } from "node:process";
|
|
2
|
-
import { clearScreenDown, cursorTo, emitKeypressEvents, moveCursor as moveTerminalCursor, } from "node:readline";
|
|
3
2
|
const DEFAULT_AI_TOOLS = ["claude", "codex", "gemini"];
|
|
4
3
|
const PACKAGE_MANAGER_CHOICES = [
|
|
5
4
|
{ value: "pnpm", label: "pnpm" },
|
|
@@ -29,29 +28,7 @@ const FEATURE_CHOICES = [
|
|
|
29
28
|
{ value: "husky", label: "husky" },
|
|
30
29
|
{ value: "skills", label: "AI skill guidance" },
|
|
31
30
|
];
|
|
32
|
-
|
|
33
|
-
if (size <= 0) {
|
|
34
|
-
return 0;
|
|
35
|
-
}
|
|
36
|
-
return (currentIndex + delta + size) % size;
|
|
37
|
-
}
|
|
38
|
-
function toggleSelectedValue(selectedValues, value) {
|
|
39
|
-
const next = new Set(selectedValues);
|
|
40
|
-
if (next.has(value)) {
|
|
41
|
-
next.delete(value);
|
|
42
|
-
return next;
|
|
43
|
-
}
|
|
44
|
-
next.add(value);
|
|
45
|
-
return next;
|
|
46
|
-
}
|
|
47
|
-
function selectAllValues(choices) {
|
|
48
|
-
return new Set(choices.map(choice => choice.value));
|
|
49
|
-
}
|
|
50
|
-
function getSelectedValuesInOrder(choices, selectedValues) {
|
|
51
|
-
return choices
|
|
52
|
-
.filter(choice => selectedValues.has(choice.value))
|
|
53
|
-
.map(choice => choice.value);
|
|
54
|
-
}
|
|
31
|
+
let promptsModulePromise = null;
|
|
55
32
|
function toFeatureSelection(selected) {
|
|
56
33
|
return {
|
|
57
34
|
enabledFeatures: {
|
|
@@ -64,171 +41,41 @@ function toFeatureSelection(selected) {
|
|
|
64
41
|
skills: selected.includes("skills"),
|
|
65
42
|
};
|
|
66
43
|
}
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
moveTerminalCursor(output, 0, -previousLineCount);
|
|
70
|
-
cursorTo(output, 0);
|
|
71
|
-
clearScreenDown(output);
|
|
72
|
-
}
|
|
73
|
-
output.write(lines.join("\n"));
|
|
74
|
-
output.write("\n");
|
|
75
|
-
return lines.length;
|
|
44
|
+
function mapChoiceOptions(choices) {
|
|
45
|
+
return choices.map(choice => ({ value: choice, label: choice.label }));
|
|
76
46
|
}
|
|
77
|
-
function
|
|
78
|
-
|
|
47
|
+
function unwrapPromptResult(value, prompts) {
|
|
48
|
+
if (prompts.isCancel(value)) {
|
|
49
|
+
prompts.cancel("Prompt cancelled by user");
|
|
50
|
+
throw new Error("Prompt cancelled by user");
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
79
53
|
}
|
|
80
|
-
function
|
|
81
|
-
|
|
54
|
+
async function getPrompts() {
|
|
55
|
+
if (!promptsModulePromise) {
|
|
56
|
+
promptsModulePromise = import("@clack/prompts");
|
|
57
|
+
}
|
|
58
|
+
return promptsModulePromise;
|
|
82
59
|
}
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
input.setRawMode(false);
|
|
90
|
-
output.write("\x1b[?25h");
|
|
60
|
+
async function promptSingleChoice(message, choices, defaultValue) {
|
|
61
|
+
const prompts = await getPrompts();
|
|
62
|
+
const defaultChoice = choices.find(choice => choice.value === defaultValue);
|
|
63
|
+
const options = {
|
|
64
|
+
message,
|
|
65
|
+
options: mapChoiceOptions(choices),
|
|
91
66
|
};
|
|
67
|
+
const result = await prompts.select(defaultChoice ? { ...options, initialValue: defaultChoice } : options);
|
|
68
|
+
return unwrapPromptResult(result, prompts).value;
|
|
92
69
|
}
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
function buildMultiSelectLines(title, choices, selectedValues, highlightedIndex, allowSelectAll) {
|
|
104
|
-
const controls = allowSelectAll
|
|
105
|
-
? "Use up/down arrows to move, Space to toggle, A to select all, Enter to confirm."
|
|
106
|
-
: "Use up/down arrows to move, Space to toggle, Enter to confirm.";
|
|
107
|
-
return [
|
|
108
|
-
title,
|
|
109
|
-
...choices.map((choice, index) => {
|
|
110
|
-
const cursor = index === highlightedIndex ? ">" : " ";
|
|
111
|
-
const mark = selectedValues.has(choice.value) ? "x" : " ";
|
|
112
|
-
return `${cursor} [${mark}] ${choice.label}`;
|
|
113
|
-
}),
|
|
114
|
-
controls,
|
|
115
|
-
];
|
|
116
|
-
}
|
|
117
|
-
async function promptSingleChoice(title, choices, defaultValue) {
|
|
118
|
-
const defaultIndex = choices.findIndex(choice => choice.value === defaultValue);
|
|
119
|
-
let highlightedIndex = defaultIndex >= 0 ? defaultIndex : 0;
|
|
120
|
-
let renderedLineCount = 0;
|
|
121
|
-
const restore = ensureRawMode();
|
|
122
|
-
try {
|
|
123
|
-
renderedLineCount = renderFrame(buildSingleSelectLines(title, choices, highlightedIndex), renderedLineCount);
|
|
124
|
-
return await new Promise((resolve, reject) => {
|
|
125
|
-
let settled = false;
|
|
126
|
-
const onKeypress = (_, key) => {
|
|
127
|
-
if (key.ctrl && key.name === "c") {
|
|
128
|
-
if (settled) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
settled = true;
|
|
132
|
-
cleanup();
|
|
133
|
-
reject(new Error("Prompt cancelled by user"));
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
if (key.name === "up") {
|
|
137
|
-
highlightedIndex = moveCursorIndex(highlightedIndex, -1, choices.length);
|
|
138
|
-
renderedLineCount = renderFrame(buildSingleSelectLines(title, choices, highlightedIndex), renderedLineCount);
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
if (key.name === "down") {
|
|
142
|
-
highlightedIndex = moveCursorIndex(highlightedIndex, 1, choices.length);
|
|
143
|
-
renderedLineCount = renderFrame(buildSingleSelectLines(title, choices, highlightedIndex), renderedLineCount);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
if (isConfirmKey(key)) {
|
|
147
|
-
const selected = choices[highlightedIndex];
|
|
148
|
-
if (selected) {
|
|
149
|
-
if (settled) {
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
settled = true;
|
|
153
|
-
cleanup();
|
|
154
|
-
resolve(selected.value);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
const cleanup = () => {
|
|
159
|
-
input.off("keypress", onKeypress);
|
|
160
|
-
};
|
|
161
|
-
input.on("keypress", onKeypress);
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
finally {
|
|
165
|
-
restore();
|
|
166
|
-
output.write("\n");
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
async function promptMultiChoice(title, choices, options) {
|
|
170
|
-
let highlightedIndex = 0;
|
|
171
|
-
let selectedValues = new Set(options.defaultValues);
|
|
172
|
-
let renderedLineCount = 0;
|
|
173
|
-
const allowSelectAll = options.allowSelectAll ?? false;
|
|
174
|
-
const restore = ensureRawMode();
|
|
175
|
-
try {
|
|
176
|
-
renderedLineCount = renderFrame(buildMultiSelectLines(title, choices, selectedValues, highlightedIndex, allowSelectAll), renderedLineCount);
|
|
177
|
-
return await new Promise((resolve, reject) => {
|
|
178
|
-
let settled = false;
|
|
179
|
-
const onKeypress = (_, key) => {
|
|
180
|
-
if (key.ctrl && key.name === "c") {
|
|
181
|
-
if (settled) {
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
settled = true;
|
|
185
|
-
cleanup();
|
|
186
|
-
reject(new Error("Prompt cancelled by user"));
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
if (key.name === "up") {
|
|
190
|
-
highlightedIndex = moveCursorIndex(highlightedIndex, -1, choices.length);
|
|
191
|
-
renderedLineCount = renderFrame(buildMultiSelectLines(title, choices, selectedValues, highlightedIndex, allowSelectAll), renderedLineCount);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
if (key.name === "down") {
|
|
195
|
-
highlightedIndex = moveCursorIndex(highlightedIndex, 1, choices.length);
|
|
196
|
-
renderedLineCount = renderFrame(buildMultiSelectLines(title, choices, selectedValues, highlightedIndex, allowSelectAll), renderedLineCount);
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
if (key.name === "space") {
|
|
200
|
-
const highlighted = choices[highlightedIndex];
|
|
201
|
-
if (!highlighted) {
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
selectedValues = toggleSelectedValue(selectedValues, highlighted.value);
|
|
205
|
-
renderedLineCount = renderFrame(buildMultiSelectLines(title, choices, selectedValues, highlightedIndex, allowSelectAll), renderedLineCount);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (allowSelectAll && isSelectAllKey(key)) {
|
|
209
|
-
selectedValues = selectAllValues(choices);
|
|
210
|
-
renderedLineCount = renderFrame(buildMultiSelectLines(title, choices, selectedValues, highlightedIndex, allowSelectAll), renderedLineCount);
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
if (isConfirmKey(key)) {
|
|
214
|
-
if (settled) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
settled = true;
|
|
218
|
-
cleanup();
|
|
219
|
-
resolve(getSelectedValuesInOrder(choices, selectedValues));
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
|
-
const cleanup = () => {
|
|
223
|
-
input.off("keypress", onKeypress);
|
|
224
|
-
};
|
|
225
|
-
input.on("keypress", onKeypress);
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
finally {
|
|
229
|
-
restore();
|
|
230
|
-
output.write("\n");
|
|
231
|
-
}
|
|
70
|
+
async function promptMultiChoice(message, choices, defaultValues) {
|
|
71
|
+
const prompts = await getPrompts();
|
|
72
|
+
const result = await prompts.multiselect({
|
|
73
|
+
message,
|
|
74
|
+
options: mapChoiceOptions(choices),
|
|
75
|
+
initialValues: choices.filter(choice => defaultValues.includes(choice.value)),
|
|
76
|
+
required: false,
|
|
77
|
+
});
|
|
78
|
+
return unwrapPromptResult(result, prompts).map(choice => choice.value);
|
|
232
79
|
}
|
|
233
80
|
export async function collectInitPrompts(defaults) {
|
|
234
81
|
if (!input.isTTY || !output.isTTY) {
|
|
@@ -249,19 +96,13 @@ export async function collectInitPrompts(defaults) {
|
|
|
249
96
|
};
|
|
250
97
|
}
|
|
251
98
|
console.log("\nInit setup");
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
console.log("\n2) Features");
|
|
255
|
-
const featureAnswers = await promptMultiChoice("Select features:", FEATURE_CHOICES, {
|
|
256
|
-
defaultValues: FEATURE_CHOICES.map(choice => choice.value),
|
|
257
|
-
allowSelectAll: true,
|
|
258
|
-
});
|
|
99
|
+
const pm = await promptSingleChoice("1) Package manager", PACKAGE_MANAGER_CHOICES, defaults.pm);
|
|
100
|
+
const featureAnswers = await promptMultiChoice("2) Features", FEATURE_CHOICES, FEATURE_CHOICES.map(choice => choice.value));
|
|
259
101
|
const featureSelection = toFeatureSelection(featureAnswers);
|
|
260
102
|
let testRunner = defaults.testRunner;
|
|
261
103
|
const enabledFeatures = { ...featureSelection.enabledFeatures };
|
|
262
104
|
if (featureSelection.enabledFeatures.test) {
|
|
263
|
-
|
|
264
|
-
const selectedTestRunner = await promptSingleChoice("Select test runner:", TEST_RUNNER_CHOICES, defaults.testRunner);
|
|
105
|
+
const selectedTestRunner = await promptSingleChoice("3) Test runner", TEST_RUNNER_CHOICES, defaults.testRunner);
|
|
265
106
|
if (selectedTestRunner === "skip") {
|
|
266
107
|
enabledFeatures.test = false;
|
|
267
108
|
console.log("Test feature disabled by selection: skip");
|
|
@@ -271,30 +112,25 @@ export async function collectInitPrompts(defaults) {
|
|
|
271
112
|
}
|
|
272
113
|
}
|
|
273
114
|
else {
|
|
274
|
-
console.log("
|
|
115
|
+
console.log("3) Test runner skipped (test feature not selected)");
|
|
275
116
|
}
|
|
276
117
|
let formatter = defaults.formatter;
|
|
277
118
|
if (featureSelection.enabledFeatures.format) {
|
|
278
|
-
|
|
279
|
-
formatter = await promptSingleChoice("Select formatter:", FORMATTER_CHOICES, defaults.formatter);
|
|
119
|
+
formatter = await promptSingleChoice("4) Formatter", FORMATTER_CHOICES, defaults.formatter);
|
|
280
120
|
}
|
|
281
121
|
else {
|
|
282
|
-
console.log("
|
|
122
|
+
console.log("4) Formatter skipped (format feature not selected)");
|
|
283
123
|
}
|
|
284
124
|
let aiTools = [];
|
|
285
125
|
let skills = featureSelection.skills;
|
|
286
126
|
if (skills) {
|
|
287
|
-
|
|
288
|
-
aiTools = await promptMultiChoice("Select AI tools:", AI_TOOL_CHOICES, {
|
|
289
|
-
defaultValues: DEFAULT_AI_TOOLS,
|
|
290
|
-
allowSelectAll: true,
|
|
291
|
-
});
|
|
127
|
+
aiTools = await promptMultiChoice("5) AI tools", AI_TOOL_CHOICES, DEFAULT_AI_TOOLS);
|
|
292
128
|
if (aiTools.length === 0) {
|
|
293
129
|
skills = false;
|
|
294
130
|
}
|
|
295
131
|
}
|
|
296
132
|
else {
|
|
297
|
-
console.log("
|
|
133
|
+
console.log("5) AI tools skipped (AI skill guidance not selected)");
|
|
298
134
|
}
|
|
299
135
|
return {
|
|
300
136
|
pm,
|
|
@@ -306,9 +142,5 @@ export async function collectInitPrompts(defaults) {
|
|
|
306
142
|
};
|
|
307
143
|
}
|
|
308
144
|
export const __testables__ = {
|
|
309
|
-
moveCursorIndex,
|
|
310
|
-
toggleSelectedValue,
|
|
311
|
-
selectAllValues,
|
|
312
|
-
getSelectedValuesInOrder,
|
|
313
145
|
toFeatureSelection,
|
|
314
146
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tyyyho/treg",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "CLI tool for initializing development conventions in existing projects.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"prepublishOnly": "pnpm format:check && pnpm lint:check && pnpm type:check && pnpm test && pnpm build"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
+
"@clack/prompts": "^1.1.0",
|
|
37
38
|
"mrm-core": "^7.1.22"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|