@tyyyho/treg 0.1.19 → 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/README.zh-hant.md
CHANGED
|
@@ -29,12 +29,12 @@ npx @tyyyho/treg init
|
|
|
29
29
|
執行 `init` 後,`treg` 會依序詢問:
|
|
30
30
|
|
|
31
31
|
1. 套件管理器(`pnpm|npm|yarn|bun`)
|
|
32
|
-
2.
|
|
32
|
+
2. 要加入的功能(可複選,預設全勾)
|
|
33
33
|
3. 測試工具(僅在選到 `test` 時詢問,支援 `skip`)
|
|
34
34
|
4. Formatter(僅在選到 `format` 時詢問)
|
|
35
35
|
5. AI 工具(`Claude|Codex|Gemini` 可複選,僅在選到 AI skill guidance 時詢問)
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
預設勾選功能:
|
|
38
38
|
|
|
39
39
|
- lint
|
|
40
40
|
- format
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { stdin as input, stdout as output } from "node:process";
|
|
2
|
-
import { createInterface } from "node:readline/promises";
|
|
3
2
|
const DEFAULT_AI_TOOLS = ["claude", "codex", "gemini"];
|
|
4
3
|
const PACKAGE_MANAGER_CHOICES = [
|
|
5
4
|
{ value: "pnpm", label: "pnpm" },
|
|
@@ -22,10 +21,6 @@ const AI_TOOL_CHOICES = [
|
|
|
22
21
|
{ value: "gemini", label: "Gemini" },
|
|
23
22
|
];
|
|
24
23
|
const FEATURE_CHOICES = [
|
|
25
|
-
{
|
|
26
|
-
value: "all",
|
|
27
|
-
label: "all (lint, format, TypeScript, test, husky, AI skill guidance)",
|
|
28
|
-
},
|
|
29
24
|
{ value: "lint", label: "lint" },
|
|
30
25
|
{ value: "format", label: "format" },
|
|
31
26
|
{ value: "typescript", label: "TypeScript" },
|
|
@@ -33,85 +28,8 @@ const FEATURE_CHOICES = [
|
|
|
33
28
|
{ value: "husky", label: "husky" },
|
|
34
29
|
{ value: "skills", label: "AI skill guidance" },
|
|
35
30
|
];
|
|
36
|
-
|
|
37
|
-
return choices
|
|
38
|
-
.map((choice, index) => ` ${index + 1}. ${choice.label}`)
|
|
39
|
-
.join("\n");
|
|
40
|
-
}
|
|
41
|
-
function parseSingleChoice(rawInput, choices, defaultValue) {
|
|
42
|
-
const normalized = rawInput.trim().toLowerCase();
|
|
43
|
-
if (!normalized) {
|
|
44
|
-
return { ok: true, value: defaultValue };
|
|
45
|
-
}
|
|
46
|
-
const byIndex = Number.parseInt(normalized, 10);
|
|
47
|
-
if (!Number.isNaN(byIndex) && String(byIndex) === normalized) {
|
|
48
|
-
const selected = choices[byIndex - 1];
|
|
49
|
-
if (selected) {
|
|
50
|
-
return { ok: true, value: selected.value };
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
const byValue = choices.find(choice => choice.value === normalized);
|
|
54
|
-
if (byValue) {
|
|
55
|
-
return { ok: true, value: byValue.value };
|
|
56
|
-
}
|
|
57
|
-
return {
|
|
58
|
-
ok: false,
|
|
59
|
-
error: `Invalid input: ${rawInput}. Please enter a listed number or option name.`,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
function parseMultiChoice(rawInput, choices, defaultValues) {
|
|
63
|
-
const normalized = rawInput.trim().toLowerCase();
|
|
64
|
-
if (!normalized) {
|
|
65
|
-
return { ok: true, value: [...defaultValues] };
|
|
66
|
-
}
|
|
67
|
-
if (normalized === "skip" || normalized === "none") {
|
|
68
|
-
return { ok: true, value: [] };
|
|
69
|
-
}
|
|
70
|
-
const tokens = normalized
|
|
71
|
-
.split(",")
|
|
72
|
-
.map(item => item.trim())
|
|
73
|
-
.filter(Boolean);
|
|
74
|
-
if (tokens.length === 0) {
|
|
75
|
-
return { ok: false, error: "Please enter at least one option." };
|
|
76
|
-
}
|
|
77
|
-
const selected = new Set();
|
|
78
|
-
for (const token of tokens) {
|
|
79
|
-
const byIndex = Number.parseInt(token, 10);
|
|
80
|
-
if (!Number.isNaN(byIndex) && String(byIndex) === token) {
|
|
81
|
-
const item = choices[byIndex - 1];
|
|
82
|
-
if (!item) {
|
|
83
|
-
return {
|
|
84
|
-
ok: false,
|
|
85
|
-
error: `Invalid index: ${token}. Please choose from listed options.`,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
selected.add(item.value);
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
const byValue = choices.find(choice => choice.value === token);
|
|
92
|
-
if (!byValue) {
|
|
93
|
-
return {
|
|
94
|
-
ok: false,
|
|
95
|
-
error: `Invalid option: ${token}. Please choose from listed options.`,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
selected.add(byValue.value);
|
|
99
|
-
}
|
|
100
|
-
return { ok: true, value: [...selected] };
|
|
101
|
-
}
|
|
31
|
+
let promptsModulePromise = null;
|
|
102
32
|
function toFeatureSelection(selected) {
|
|
103
|
-
if (selected.includes("all")) {
|
|
104
|
-
return {
|
|
105
|
-
enabledFeatures: {
|
|
106
|
-
lint: true,
|
|
107
|
-
format: true,
|
|
108
|
-
typescript: true,
|
|
109
|
-
test: true,
|
|
110
|
-
husky: true,
|
|
111
|
-
},
|
|
112
|
-
skills: true,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
33
|
return {
|
|
116
34
|
enabledFeatures: {
|
|
117
35
|
lint: selected.includes("lint"),
|
|
@@ -123,15 +41,41 @@ function toFeatureSelection(selected) {
|
|
|
123
41
|
skills: selected.includes("skills"),
|
|
124
42
|
};
|
|
125
43
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
44
|
+
function mapChoiceOptions(choices) {
|
|
45
|
+
return choices.map(choice => ({ value: choice, label: choice.label }));
|
|
46
|
+
}
|
|
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;
|
|
53
|
+
}
|
|
54
|
+
async function getPrompts() {
|
|
55
|
+
if (!promptsModulePromise) {
|
|
56
|
+
promptsModulePromise = import("@clack/prompts");
|
|
134
57
|
}
|
|
58
|
+
return promptsModulePromise;
|
|
59
|
+
}
|
|
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),
|
|
66
|
+
};
|
|
67
|
+
const result = await prompts.select(defaultChoice ? { ...options, initialValue: defaultChoice } : options);
|
|
68
|
+
return unwrapPromptResult(result, prompts).value;
|
|
69
|
+
}
|
|
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);
|
|
135
79
|
}
|
|
136
80
|
export async function collectInitPrompts(defaults) {
|
|
137
81
|
if (!input.isTTY || !output.isTTY) {
|
|
@@ -151,71 +95,52 @@ export async function collectInitPrompts(defaults) {
|
|
|
151
95
|
aiTools: [...DEFAULT_AI_TOOLS],
|
|
152
96
|
};
|
|
153
97
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const enabledFeatures = { ...featureSelection.enabledFeatures };
|
|
166
|
-
if (featureSelection.enabledFeatures.test) {
|
|
167
|
-
console.log("\n3) Test runner");
|
|
168
|
-
console.log(formatChoices(TEST_RUNNER_CHOICES));
|
|
169
|
-
const selectedTestRunner = await askUntilValid(rl.question.bind(rl), `Select test runner [default: ${defaults.testRunner}, or skip]: `, answer => parseSingleChoice(answer, TEST_RUNNER_CHOICES, defaults.testRunner));
|
|
170
|
-
if (selectedTestRunner === "skip") {
|
|
171
|
-
enabledFeatures.test = false;
|
|
172
|
-
console.log("Test feature disabled by selection: skip");
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
testRunner = selectedTestRunner;
|
|
176
|
-
}
|
|
98
|
+
console.log("\nInit setup");
|
|
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));
|
|
101
|
+
const featureSelection = toFeatureSelection(featureAnswers);
|
|
102
|
+
let testRunner = defaults.testRunner;
|
|
103
|
+
const enabledFeatures = { ...featureSelection.enabledFeatures };
|
|
104
|
+
if (featureSelection.enabledFeatures.test) {
|
|
105
|
+
const selectedTestRunner = await promptSingleChoice("3) Test runner", TEST_RUNNER_CHOICES, defaults.testRunner);
|
|
106
|
+
if (selectedTestRunner === "skip") {
|
|
107
|
+
enabledFeatures.test = false;
|
|
108
|
+
console.log("Test feature disabled by selection: skip");
|
|
177
109
|
}
|
|
178
110
|
else {
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
let formatter = defaults.formatter;
|
|
182
|
-
if (featureSelection.enabledFeatures.format) {
|
|
183
|
-
console.log("\n4) Formatter");
|
|
184
|
-
console.log(formatChoices(FORMATTER_CHOICES));
|
|
185
|
-
formatter = await askUntilValid(rl.question.bind(rl), `Select formatter [default: ${defaults.formatter}]: `, answer => parseSingleChoice(answer, FORMATTER_CHOICES, defaults.formatter));
|
|
111
|
+
testRunner = selectedTestRunner;
|
|
186
112
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
console.log("3) Test runner skipped (test feature not selected)");
|
|
116
|
+
}
|
|
117
|
+
let formatter = defaults.formatter;
|
|
118
|
+
if (featureSelection.enabledFeatures.format) {
|
|
119
|
+
formatter = await promptSingleChoice("4) Formatter", FORMATTER_CHOICES, defaults.formatter);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
console.log("4) Formatter skipped (format feature not selected)");
|
|
123
|
+
}
|
|
124
|
+
let aiTools = [];
|
|
125
|
+
let skills = featureSelection.skills;
|
|
126
|
+
if (skills) {
|
|
127
|
+
aiTools = await promptMultiChoice("5) AI tools", AI_TOOL_CHOICES, DEFAULT_AI_TOOLS);
|
|
128
|
+
if (aiTools.length === 0) {
|
|
129
|
+
skills = false;
|
|
203
130
|
}
|
|
204
|
-
return {
|
|
205
|
-
pm,
|
|
206
|
-
formatter,
|
|
207
|
-
testRunner,
|
|
208
|
-
enabledFeatures,
|
|
209
|
-
skills,
|
|
210
|
-
aiTools,
|
|
211
|
-
};
|
|
212
131
|
}
|
|
213
|
-
|
|
214
|
-
|
|
132
|
+
else {
|
|
133
|
+
console.log("5) AI tools skipped (AI skill guidance not selected)");
|
|
215
134
|
}
|
|
135
|
+
return {
|
|
136
|
+
pm,
|
|
137
|
+
formatter,
|
|
138
|
+
testRunner,
|
|
139
|
+
enabledFeatures,
|
|
140
|
+
skills,
|
|
141
|
+
aiTools,
|
|
142
|
+
};
|
|
216
143
|
}
|
|
217
144
|
export const __testables__ = {
|
|
218
|
-
parseSingleChoice,
|
|
219
|
-
parseMultiChoice,
|
|
220
145
|
toFeatureSelection,
|
|
221
146
|
};
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { promises as fs } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
const START_MARKER = "<!-- treg:skills:start -->";
|
|
5
|
-
const END_MARKER = "<!-- treg:skills:end -->";
|
|
6
4
|
const SKILL_SECTION_HEADING = "## treg AI Skills";
|
|
7
5
|
const SKILLS_BASE_DIR = "skills";
|
|
8
6
|
const AI_TOOL_DOCS = {
|
|
@@ -155,12 +153,6 @@ function upsertSkillSection(content, nextSection) {
|
|
|
155
153
|
const rebuilt = `${before}\n\n${nextSection.trim()}\n`;
|
|
156
154
|
return after ? `${rebuilt}\n${after}\n` : `${rebuilt}`;
|
|
157
155
|
};
|
|
158
|
-
const start = content.indexOf(START_MARKER);
|
|
159
|
-
const end = content.indexOf(END_MARKER);
|
|
160
|
-
if (start !== -1 && end !== -1 && end > start) {
|
|
161
|
-
const suffixStart = end + END_MARKER.length;
|
|
162
|
-
return replaceSection(start, suffixStart);
|
|
163
|
-
}
|
|
164
156
|
const headingStart = content.indexOf(SKILL_SECTION_HEADING);
|
|
165
157
|
if (headingStart !== -1) {
|
|
166
158
|
const nextHeading = content.indexOf("\n## ", headingStart + 1);
|
|
@@ -172,10 +164,6 @@ function upsertSkillSection(content, nextSection) {
|
|
|
172
164
|
}
|
|
173
165
|
return `${content.trimEnd()}\n\n${nextSection.trim()}\n`;
|
|
174
166
|
}
|
|
175
|
-
function buildInitialAiDocContent(fileName) {
|
|
176
|
-
const baseName = path.basename(fileName, path.extname(fileName));
|
|
177
|
-
return `# ${baseName}\n`;
|
|
178
|
-
}
|
|
179
167
|
export async function runAiSkillsRule(context) {
|
|
180
168
|
const { projectDir, dryRun, aiTools } = context;
|
|
181
169
|
const targetFiles = resolveSkillsDocs(projectDir, aiTools);
|
|
@@ -189,9 +177,7 @@ export async function runAiSkillsRule(context) {
|
|
|
189
177
|
continue;
|
|
190
178
|
}
|
|
191
179
|
const exists = existsSync(targetFile);
|
|
192
|
-
const current = exists
|
|
193
|
-
? await fs.readFile(targetFile, "utf8")
|
|
194
|
-
: buildInitialAiDocContent(targetFile);
|
|
180
|
+
const current = exists ? await fs.readFile(targetFile, "utf8") : "";
|
|
195
181
|
const updated = upsertSkillSection(current, section);
|
|
196
182
|
if (updated !== current) {
|
|
197
183
|
await fs.mkdir(path.dirname(targetFile), { recursive: true });
|
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": {
|