@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.
@@ -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
- function moveCursorIndex(currentIndex, delta, size) {
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 renderFrame(lines, previousLineCount) {
68
- if (previousLineCount > 0) {
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 isConfirmKey(key) {
78
- return key.name === "return" || key.name === "enter";
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 isSelectAllKey(key) {
81
- return key.name === "a" && !key.ctrl && !key.meta;
54
+ async function getPrompts() {
55
+ if (!promptsModulePromise) {
56
+ promptsModulePromise = import("@clack/prompts");
57
+ }
58
+ return promptsModulePromise;
82
59
  }
83
- function ensureRawMode() {
84
- emitKeypressEvents(input);
85
- input.setRawMode(true);
86
- input.resume();
87
- output.write("\x1b[?25l");
88
- return () => {
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 buildSingleSelectLines(title, choices, highlightedIndex) {
94
- return [
95
- title,
96
- ...choices.map((choice, index) => {
97
- const cursor = index === highlightedIndex ? ">" : " ";
98
- return `${cursor} ${choice.label}`;
99
- }),
100
- "Use up/down arrows to move, Enter to confirm.",
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
- console.log("\n1) Package manager");
253
- const pm = await promptSingleChoice("Select package manager:", PACKAGE_MANAGER_CHOICES, defaults.pm);
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
- console.log("\n3) Test runner");
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("\n3) Test runner skipped (test feature not selected)");
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
- console.log("\n4) Formatter");
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("\n4) Formatter skipped (format feature not selected)");
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
- console.log("\n5) AI tools");
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("\n5) AI tools skipped (AI skill guidance not selected)");
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.20",
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": {