hyouji 0.0.17 → 0.1.0
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/index.js +466 -78
- package/package.json +5 -7
package/dist/index.js
CHANGED
|
@@ -7,7 +7,9 @@ import { homedir } from "os";
|
|
|
7
7
|
import * as path from "path";
|
|
8
8
|
import { join, dirname } from "path";
|
|
9
9
|
import { createHash, randomBytes, createCipheriv, createDecipheriv } from "crypto";
|
|
10
|
-
import
|
|
10
|
+
import { stdout, stdin } from "node:process";
|
|
11
|
+
import { emitKeypressEvents } from "node:readline";
|
|
12
|
+
import { createInterface } from "node:readline/promises";
|
|
11
13
|
import YAML from "yaml";
|
|
12
14
|
import { Octokit } from "@octokit/core";
|
|
13
15
|
import { exec } from "child_process";
|
|
@@ -47,26 +49,16 @@ const newLabel = [
|
|
|
47
49
|
}
|
|
48
50
|
];
|
|
49
51
|
const deleteLabel$1 = {
|
|
50
|
-
type: "text",
|
|
51
|
-
name: "name",
|
|
52
52
|
message: "Please type label name you want to delete"
|
|
53
53
|
};
|
|
54
54
|
const labelFilePath = {
|
|
55
|
-
type: "text",
|
|
56
|
-
name: "filePath",
|
|
57
55
|
message: "Please type the path to your JSON or YAML file"
|
|
58
56
|
};
|
|
59
57
|
const dryRunToggle = {
|
|
60
|
-
type: "toggle",
|
|
61
|
-
name: "dryRun",
|
|
62
58
|
message: "Run in dry-run mode? (no API calls will be made)",
|
|
63
|
-
active: "yes",
|
|
64
|
-
inactive: "no",
|
|
65
59
|
initial: false
|
|
66
60
|
};
|
|
67
61
|
const actionSelector = {
|
|
68
|
-
type: "multiselect",
|
|
69
|
-
name: "action",
|
|
70
62
|
message: "Please select an action",
|
|
71
63
|
choices: [
|
|
72
64
|
{ title: "create a label", value: 0 },
|
|
@@ -81,8 +73,6 @@ const actionSelector = {
|
|
|
81
73
|
]
|
|
82
74
|
};
|
|
83
75
|
const holdToken = {
|
|
84
|
-
type: "confirm",
|
|
85
|
-
name: "value",
|
|
86
76
|
message: "Do you have a personal token?",
|
|
87
77
|
initial: true
|
|
88
78
|
};
|
|
@@ -1019,13 +1009,374 @@ class ConfigManager {
|
|
|
1019
1009
|
].includes(error.type);
|
|
1020
1010
|
}
|
|
1021
1011
|
}
|
|
1012
|
+
const OPEN_TUI_INPUT_TIMEOUT_MS = 3e4;
|
|
1013
|
+
const ESCAPE_SELECTION_VALUE = 99;
|
|
1014
|
+
let opentuiLoadAttempted = false;
|
|
1015
|
+
let opentuiModule = null;
|
|
1016
|
+
const loadOpenTui = async () => {
|
|
1017
|
+
if (opentuiLoadAttempted) {
|
|
1018
|
+
return opentuiModule;
|
|
1019
|
+
}
|
|
1020
|
+
opentuiLoadAttempted = true;
|
|
1021
|
+
try {
|
|
1022
|
+
const loaded = await import("@opentui/core");
|
|
1023
|
+
opentuiModule = loaded;
|
|
1024
|
+
return loaded;
|
|
1025
|
+
} catch {
|
|
1026
|
+
opentuiModule = null;
|
|
1027
|
+
return null;
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
const question = async (message) => {
|
|
1031
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
1032
|
+
try {
|
|
1033
|
+
const answer = await rl.question(`${message}: `);
|
|
1034
|
+
return answer.trim();
|
|
1035
|
+
} finally {
|
|
1036
|
+
rl.close();
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
const askText = async (message, options = {}) => {
|
|
1040
|
+
const core = await loadOpenTui();
|
|
1041
|
+
if (core) {
|
|
1042
|
+
const result = await askTextWithOpenTui(core, message, options);
|
|
1043
|
+
if (result !== null) {
|
|
1044
|
+
return result;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
const answer = await question(message);
|
|
1048
|
+
if (answer.length === 0 && options.initial !== void 0) {
|
|
1049
|
+
return options.initial;
|
|
1050
|
+
}
|
|
1051
|
+
return answer;
|
|
1052
|
+
};
|
|
1053
|
+
const askPassword = async (message) => {
|
|
1054
|
+
const masked = await askPasswordWithRawInput(message);
|
|
1055
|
+
if (masked !== null) {
|
|
1056
|
+
return masked;
|
|
1057
|
+
}
|
|
1058
|
+
return askText(message);
|
|
1059
|
+
};
|
|
1060
|
+
const askConfirm = async (message, initial = true) => {
|
|
1061
|
+
const value = await askSelect(message, [
|
|
1062
|
+
{ title: initial ? "yes (default)" : "yes", value: 1 },
|
|
1063
|
+
{ title: initial ? "no" : "no (default)", value: 0 }
|
|
1064
|
+
]);
|
|
1065
|
+
if (value === 1) {
|
|
1066
|
+
return true;
|
|
1067
|
+
}
|
|
1068
|
+
if (value === 0) {
|
|
1069
|
+
return false;
|
|
1070
|
+
}
|
|
1071
|
+
if (value === ESCAPE_SELECTION_VALUE) {
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
return initial;
|
|
1075
|
+
};
|
|
1076
|
+
const askSelect = async (message, choices) => {
|
|
1077
|
+
const core = await loadOpenTui();
|
|
1078
|
+
if (core) {
|
|
1079
|
+
const selected = await askSelectWithOpenTui(core, message, choices);
|
|
1080
|
+
if (selected !== null) {
|
|
1081
|
+
return selected;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
stdout.write(`${message}
|
|
1085
|
+
`);
|
|
1086
|
+
choices.forEach((choice, index) => {
|
|
1087
|
+
stdout.write(` ${index + 1}. ${choice.title}
|
|
1088
|
+
`);
|
|
1089
|
+
});
|
|
1090
|
+
const rawSelected = await askSelectWithRawInput(choices);
|
|
1091
|
+
if (rawSelected !== null) {
|
|
1092
|
+
return rawSelected;
|
|
1093
|
+
}
|
|
1094
|
+
while (true) {
|
|
1095
|
+
const answer = await question("Select number");
|
|
1096
|
+
const normalized = answer.toLowerCase();
|
|
1097
|
+
if (normalized === "esc" || normalized === "escape") {
|
|
1098
|
+
return choices.find((choice2) => choice2.title.toLowerCase() === "exit")?.value ?? 99;
|
|
1099
|
+
}
|
|
1100
|
+
const parsed = Number.parseInt(answer, 10);
|
|
1101
|
+
if (Number.isNaN(parsed)) {
|
|
1102
|
+
stdout.write("Please enter a valid number.\n");
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
const choice = choices[parsed - 1];
|
|
1106
|
+
if (!choice) {
|
|
1107
|
+
stdout.write("Out of range. Try again.\n");
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1110
|
+
return choice.value;
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
const askSelectWithOpenTui = async (core, message, choices) => {
|
|
1114
|
+
const renderer = await core.createCliRenderer({ exitOnCtrlC: true });
|
|
1115
|
+
let destroyed = false;
|
|
1116
|
+
const destroyRenderer = async () => {
|
|
1117
|
+
if (destroyed) {
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
destroyed = true;
|
|
1121
|
+
await renderer.destroy();
|
|
1122
|
+
};
|
|
1123
|
+
try {
|
|
1124
|
+
const root = new core.BoxRenderable(renderer, {
|
|
1125
|
+
id: "prompt-root",
|
|
1126
|
+
flexDirection: "column",
|
|
1127
|
+
border: true,
|
|
1128
|
+
padding: 1
|
|
1129
|
+
});
|
|
1130
|
+
const title = new core.TextRenderable(renderer, {
|
|
1131
|
+
id: "prompt-title",
|
|
1132
|
+
content: message
|
|
1133
|
+
});
|
|
1134
|
+
const select = new core.SelectRenderable(renderer, {
|
|
1135
|
+
id: "prompt-select",
|
|
1136
|
+
options: choices.map((choice) => ({
|
|
1137
|
+
name: choice.title,
|
|
1138
|
+
value: choice.value
|
|
1139
|
+
}))
|
|
1140
|
+
});
|
|
1141
|
+
root.add(title);
|
|
1142
|
+
root.add(select);
|
|
1143
|
+
renderer.root.add(root);
|
|
1144
|
+
select.focus?.();
|
|
1145
|
+
if (!select.on) {
|
|
1146
|
+
await destroyRenderer();
|
|
1147
|
+
return null;
|
|
1148
|
+
}
|
|
1149
|
+
const escapeValue = choices.find((choice) => choice.title.toLowerCase() === "exit")?.value ?? 99;
|
|
1150
|
+
return await new Promise((resolve) => {
|
|
1151
|
+
let resolved = false;
|
|
1152
|
+
const resolveOnce = async (value) => {
|
|
1153
|
+
if (resolved) {
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
resolved = true;
|
|
1157
|
+
await destroyRenderer();
|
|
1158
|
+
resolve(value);
|
|
1159
|
+
};
|
|
1160
|
+
select.on(
|
|
1161
|
+
core.SelectRenderableEvents.ITEM_SELECTED,
|
|
1162
|
+
async (_index, option) => {
|
|
1163
|
+
if (typeof option === "object" && option !== null && "value" in option && typeof option.value === "number") {
|
|
1164
|
+
await resolveOnce(option.value);
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
await resolveOnce(99);
|
|
1168
|
+
}
|
|
1169
|
+
);
|
|
1170
|
+
renderer.keyInput?.on?.("keypress", async (key) => {
|
|
1171
|
+
if (typeof key === "object" && key !== null && "name" in key && (key.name === "escape" || key.name === "esc")) {
|
|
1172
|
+
await resolveOnce(escapeValue);
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
1175
|
+
});
|
|
1176
|
+
} catch {
|
|
1177
|
+
await destroyRenderer();
|
|
1178
|
+
return null;
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
const askSelectWithRawInput = async (choices) => {
|
|
1182
|
+
if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
const escapeValue = choices.find((choice) => choice.title.toLowerCase() === "exit")?.value ?? 99;
|
|
1186
|
+
stdout.write("Select number (or Esc to exit): ");
|
|
1187
|
+
return await new Promise((resolve) => {
|
|
1188
|
+
let digits = "";
|
|
1189
|
+
const wasRaw = stdin.isRaw;
|
|
1190
|
+
const wasPaused = stdin.isPaused();
|
|
1191
|
+
const cleanup = () => {
|
|
1192
|
+
stdin.removeListener("keypress", onKeypress);
|
|
1193
|
+
if (!wasRaw) {
|
|
1194
|
+
stdin.setRawMode(false);
|
|
1195
|
+
}
|
|
1196
|
+
if (wasPaused) {
|
|
1197
|
+
stdin.pause();
|
|
1198
|
+
}
|
|
1199
|
+
stdout.write("\n");
|
|
1200
|
+
};
|
|
1201
|
+
const resolveValue = (value) => {
|
|
1202
|
+
cleanup();
|
|
1203
|
+
resolve(value);
|
|
1204
|
+
};
|
|
1205
|
+
const onKeypress = (str, key) => {
|
|
1206
|
+
if (!key) {
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
if (key.name === "escape" || key.name === "esc") {
|
|
1210
|
+
resolveValue(escapeValue);
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
if (key.name === "return" || key.name === "enter") {
|
|
1214
|
+
const parsed = Number.parseInt(digits, 10);
|
|
1215
|
+
if (Number.isNaN(parsed)) {
|
|
1216
|
+
stdout.write("\nPlease enter a valid number.\nSelect number: ");
|
|
1217
|
+
digits = "";
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
const choice = choices[parsed - 1];
|
|
1221
|
+
if (!choice) {
|
|
1222
|
+
stdout.write("\nOut of range. Try again.\nSelect number: ");
|
|
1223
|
+
digits = "";
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
resolveValue(choice.value);
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
if (key.name === "backspace") {
|
|
1230
|
+
if (digits.length > 0) {
|
|
1231
|
+
digits = digits.slice(0, -1);
|
|
1232
|
+
stdout.write("\b \b");
|
|
1233
|
+
}
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
if (/^[0-9]$/.test(str)) {
|
|
1237
|
+
digits += str;
|
|
1238
|
+
stdout.write(str);
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
emitKeypressEvents(stdin);
|
|
1242
|
+
stdin.setRawMode(true);
|
|
1243
|
+
stdin.resume();
|
|
1244
|
+
stdin.on("keypress", onKeypress);
|
|
1245
|
+
});
|
|
1246
|
+
};
|
|
1247
|
+
const askPasswordWithRawInput = async (message) => {
|
|
1248
|
+
if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
|
|
1249
|
+
return null;
|
|
1250
|
+
}
|
|
1251
|
+
stdout.write(`${message}: `);
|
|
1252
|
+
return await new Promise((resolve) => {
|
|
1253
|
+
let value = "";
|
|
1254
|
+
const wasRaw = stdin.isRaw;
|
|
1255
|
+
const wasPaused = stdin.isPaused();
|
|
1256
|
+
const cleanup = () => {
|
|
1257
|
+
stdin.removeListener("keypress", onKeypress);
|
|
1258
|
+
if (!wasRaw) {
|
|
1259
|
+
stdin.setRawMode(false);
|
|
1260
|
+
}
|
|
1261
|
+
if (wasPaused) {
|
|
1262
|
+
stdin.pause();
|
|
1263
|
+
}
|
|
1264
|
+
stdout.write("\n");
|
|
1265
|
+
};
|
|
1266
|
+
const resolveValue = (password) => {
|
|
1267
|
+
cleanup();
|
|
1268
|
+
resolve(password);
|
|
1269
|
+
};
|
|
1270
|
+
const onKeypress = (str, key) => {
|
|
1271
|
+
if (!key) {
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
if (key.name === "return" || key.name === "enter") {
|
|
1275
|
+
resolveValue(value);
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
if (key.name === "backspace") {
|
|
1279
|
+
if (value.length > 0) {
|
|
1280
|
+
value = value.slice(0, -1);
|
|
1281
|
+
stdout.write("\b \b");
|
|
1282
|
+
}
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
if (key.name === "escape" || key.name === "esc") {
|
|
1286
|
+
resolveValue(null);
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
if (str.length === 1 && str >= " ") {
|
|
1290
|
+
value += str;
|
|
1291
|
+
stdout.write("*");
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
emitKeypressEvents(stdin);
|
|
1295
|
+
stdin.setRawMode(true);
|
|
1296
|
+
stdin.resume();
|
|
1297
|
+
stdin.on("keypress", onKeypress);
|
|
1298
|
+
});
|
|
1299
|
+
};
|
|
1300
|
+
const askTextWithOpenTui = async (core, message, options) => {
|
|
1301
|
+
const renderer = await core.createCliRenderer({ exitOnCtrlC: true });
|
|
1302
|
+
let destroyed = false;
|
|
1303
|
+
let currentValue = options.initial ?? "";
|
|
1304
|
+
const destroyRenderer = async () => {
|
|
1305
|
+
if (destroyed) {
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
destroyed = true;
|
|
1309
|
+
await renderer.destroy();
|
|
1310
|
+
};
|
|
1311
|
+
try {
|
|
1312
|
+
const root = new core.BoxRenderable(renderer, {
|
|
1313
|
+
id: "prompt-root",
|
|
1314
|
+
flexDirection: "column",
|
|
1315
|
+
border: true,
|
|
1316
|
+
padding: 1
|
|
1317
|
+
});
|
|
1318
|
+
const title = new core.TextRenderable(renderer, {
|
|
1319
|
+
id: "prompt-title",
|
|
1320
|
+
content: message
|
|
1321
|
+
});
|
|
1322
|
+
const inputField = new core.InputRenderable(renderer, {
|
|
1323
|
+
id: "prompt-input",
|
|
1324
|
+
placeholder: options.initial ?? ""
|
|
1325
|
+
});
|
|
1326
|
+
inputField.setValue?.(currentValue);
|
|
1327
|
+
if (!inputField.on) {
|
|
1328
|
+
await destroyRenderer();
|
|
1329
|
+
return null;
|
|
1330
|
+
}
|
|
1331
|
+
inputField.on(core.InputRenderableEvents.CHANGE, (nextValue) => {
|
|
1332
|
+
if (typeof nextValue === "string") {
|
|
1333
|
+
currentValue = nextValue;
|
|
1334
|
+
}
|
|
1335
|
+
});
|
|
1336
|
+
root.add(title);
|
|
1337
|
+
root.add(inputField);
|
|
1338
|
+
renderer.root.add(root);
|
|
1339
|
+
inputField.focus?.();
|
|
1340
|
+
if (!renderer.keyInput?.on) {
|
|
1341
|
+
await destroyRenderer();
|
|
1342
|
+
return null;
|
|
1343
|
+
}
|
|
1344
|
+
return await new Promise((resolve) => {
|
|
1345
|
+
let settled = false;
|
|
1346
|
+
const timer = setTimeout(async () => {
|
|
1347
|
+
if (settled) {
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
settled = true;
|
|
1351
|
+
await destroyRenderer();
|
|
1352
|
+
resolve(null);
|
|
1353
|
+
}, OPEN_TUI_INPUT_TIMEOUT_MS);
|
|
1354
|
+
renderer.keyInput?.on?.("keypress", async (key) => {
|
|
1355
|
+
if (settled) {
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
if (typeof key === "object" && key !== null && "name" in key && (key.name === "enter" || key.name === "return")) {
|
|
1359
|
+
settled = true;
|
|
1360
|
+
clearTimeout(timer);
|
|
1361
|
+
await destroyRenderer();
|
|
1362
|
+
if (currentValue === "" && options.initial !== void 0) {
|
|
1363
|
+
resolve(options.initial);
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
resolve(currentValue);
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
});
|
|
1370
|
+
} catch {
|
|
1371
|
+
await destroyRenderer();
|
|
1372
|
+
return null;
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1022
1375
|
const getConfirmation = async () => {
|
|
1023
|
-
|
|
1024
|
-
return response.value;
|
|
1376
|
+
return askConfirm(holdToken.message, holdToken.initial);
|
|
1025
1377
|
};
|
|
1026
1378
|
const getDryRunChoice = async () => {
|
|
1027
|
-
|
|
1028
|
-
return Boolean(response.dryRun);
|
|
1379
|
+
return askConfirm(dryRunToggle.message, dryRunToggle.initial);
|
|
1029
1380
|
};
|
|
1030
1381
|
const log$3 = console.log;
|
|
1031
1382
|
const generateSampleJson = async () => {
|
|
@@ -1347,8 +1698,13 @@ const importLabelsFromFile = async (configs2, filePath, dryRun = false) => {
|
|
|
1347
1698
|
return summary;
|
|
1348
1699
|
};
|
|
1349
1700
|
const getTargetLabel = async () => {
|
|
1350
|
-
|
|
1351
|
-
|
|
1701
|
+
while (true) {
|
|
1702
|
+
const name = (await askText(deleteLabel$1.message)).trim();
|
|
1703
|
+
if (name.length > 0) {
|
|
1704
|
+
return [name];
|
|
1705
|
+
}
|
|
1706
|
+
console.log(chalk.yellow("Label name cannot be empty. Please try again."));
|
|
1707
|
+
}
|
|
1352
1708
|
};
|
|
1353
1709
|
const GIT_COMMAND_TIMEOUT_MS = 5e3;
|
|
1354
1710
|
class GitRepositoryDetector {
|
|
@@ -1455,14 +1811,14 @@ class GitRepositoryDetector {
|
|
|
1455
1811
|
*/
|
|
1456
1812
|
static async getRemoteUrl(gitRoot, remoteName) {
|
|
1457
1813
|
try {
|
|
1458
|
-
const { stdout } = await this.execAsyncInternal(
|
|
1814
|
+
const { stdout: stdout2 } = await this.execAsyncInternal(
|
|
1459
1815
|
`git remote get-url ${remoteName}`,
|
|
1460
1816
|
{
|
|
1461
1817
|
cwd: gitRoot,
|
|
1462
1818
|
timeout: GIT_COMMAND_TIMEOUT_MS
|
|
1463
1819
|
}
|
|
1464
1820
|
);
|
|
1465
|
-
return
|
|
1821
|
+
return stdout2.trim() || null;
|
|
1466
1822
|
} catch {
|
|
1467
1823
|
return null;
|
|
1468
1824
|
}
|
|
@@ -1532,12 +1888,12 @@ class GitRepositoryDetector {
|
|
|
1532
1888
|
*/
|
|
1533
1889
|
static async getAllRemotes(gitRoot) {
|
|
1534
1890
|
try {
|
|
1535
|
-
const { stdout } = await this.execAsyncInternal("git remote", {
|
|
1891
|
+
const { stdout: stdout2 } = await this.execAsyncInternal("git remote", {
|
|
1536
1892
|
cwd: gitRoot,
|
|
1537
1893
|
timeout: GIT_COMMAND_TIMEOUT_MS
|
|
1538
1894
|
});
|
|
1539
1895
|
return {
|
|
1540
|
-
remotes:
|
|
1896
|
+
remotes: stdout2.trim().split("\n").filter((remote) => remote.length > 0)
|
|
1541
1897
|
};
|
|
1542
1898
|
} catch (err) {
|
|
1543
1899
|
const error = err;
|
|
@@ -1551,6 +1907,21 @@ class GitRepositoryDetector {
|
|
|
1551
1907
|
}
|
|
1552
1908
|
}
|
|
1553
1909
|
}
|
|
1910
|
+
const askRequiredValue = async (ask, fieldName) => {
|
|
1911
|
+
while (true) {
|
|
1912
|
+
const rawValue = await ask();
|
|
1913
|
+
if (rawValue === null) {
|
|
1914
|
+
throw new Error(`${fieldName} input was canceled by user.`);
|
|
1915
|
+
}
|
|
1916
|
+
const value = rawValue.trim();
|
|
1917
|
+
if (value.length > 0) {
|
|
1918
|
+
return value;
|
|
1919
|
+
}
|
|
1920
|
+
console.log(
|
|
1921
|
+
chalk.yellow(`⚠️ ${fieldName} cannot be empty. Please try again.`)
|
|
1922
|
+
);
|
|
1923
|
+
}
|
|
1924
|
+
};
|
|
1554
1925
|
const getGitHubConfigs = async () => {
|
|
1555
1926
|
const configManager2 = new ConfigManager();
|
|
1556
1927
|
let validationResult = {
|
|
@@ -1615,91 +1986,108 @@ const getGitHubConfigs = async () => {
|
|
|
1615
1986
|
console.log(chalk.gray(` Error: ${error.message}`));
|
|
1616
1987
|
}
|
|
1617
1988
|
}
|
|
1618
|
-
const
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
}
|
|
1624
|
-
]);
|
|
1989
|
+
const repoPrompt2 = githubConfigs.find((prompt) => prompt.name === "repo");
|
|
1990
|
+
const repo2 = await askRequiredValue(
|
|
1991
|
+
() => askText(repoPrompt2?.message ?? "Please type your target repo name"),
|
|
1992
|
+
"Repository name"
|
|
1993
|
+
);
|
|
1625
1994
|
const octokit2 = new Octokit({
|
|
1626
1995
|
auth: validationResult.config.token
|
|
1627
1996
|
});
|
|
1628
1997
|
return {
|
|
1629
1998
|
octokit: octokit2,
|
|
1630
1999
|
owner: validationResult.config.owner,
|
|
1631
|
-
repo:
|
|
2000
|
+
repo: repo2,
|
|
1632
2001
|
fromSavedConfig: true,
|
|
1633
2002
|
autoDetected: false,
|
|
1634
2003
|
detectionMethod: "manual"
|
|
1635
2004
|
};
|
|
1636
2005
|
}
|
|
1637
|
-
const
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
)
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
2006
|
+
const tokenPrompt = githubConfigs.find((prompt) => prompt.name === "octokit");
|
|
2007
|
+
const ownerPrompt = githubConfigs.find((prompt) => prompt.name === "owner");
|
|
2008
|
+
const repoPrompt = githubConfigs.find((prompt) => prompt.name === "repo");
|
|
2009
|
+
const octokitToken = await askRequiredValue(
|
|
2010
|
+
() => askPassword(tokenPrompt?.message ?? "Please type your personal token"),
|
|
2011
|
+
"Personal token"
|
|
2012
|
+
);
|
|
2013
|
+
const owner = await askRequiredValue(
|
|
2014
|
+
() => askText(ownerPrompt?.message ?? "Please type your GitHub account", {
|
|
2015
|
+
initial: validationResult.preservedData?.owner
|
|
2016
|
+
}),
|
|
2017
|
+
"GitHub account"
|
|
2018
|
+
);
|
|
2019
|
+
const repo = await askRequiredValue(
|
|
2020
|
+
() => askText(repoPrompt?.message ?? "Please type your target repo name"),
|
|
2021
|
+
"Repository name"
|
|
2022
|
+
);
|
|
2023
|
+
try {
|
|
2024
|
+
await configManager2.saveConfig({
|
|
2025
|
+
token: octokitToken,
|
|
2026
|
+
owner,
|
|
2027
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
2028
|
+
});
|
|
2029
|
+
if (validationResult.preservedData?.owner && validationResult.preservedData.owner !== owner) {
|
|
2030
|
+
console.log("✓ Configuration updated with new credentials");
|
|
2031
|
+
} else {
|
|
2032
|
+
console.log("✓ Configuration saved successfully");
|
|
1648
2033
|
}
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
owner: response.owner,
|
|
1656
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1657
|
-
});
|
|
1658
|
-
if (validationResult.preservedData?.owner && validationResult.preservedData.owner !== response.owner) {
|
|
1659
|
-
console.log("✓ Configuration updated with new credentials");
|
|
1660
|
-
} else {
|
|
1661
|
-
console.log("✓ Configuration saved successfully");
|
|
1662
|
-
}
|
|
1663
|
-
} catch (error) {
|
|
1664
|
-
if (error instanceof ConfigError) {
|
|
1665
|
-
console.error(`❌ ${ConfigManager.getErrorMessage(error)}`);
|
|
1666
|
-
if (!ConfigManager.isRecoverableError(error)) {
|
|
1667
|
-
console.error(
|
|
1668
|
-
" This may affect future sessions. Please resolve the issue or contact support."
|
|
1669
|
-
);
|
|
1670
|
-
}
|
|
1671
|
-
} else {
|
|
1672
|
-
console.warn(
|
|
1673
|
-
"⚠️ Failed to save configuration:",
|
|
1674
|
-
error instanceof Error ? error.message : "Unknown error"
|
|
2034
|
+
} catch (error) {
|
|
2035
|
+
if (error instanceof ConfigError) {
|
|
2036
|
+
console.error(`❌ ${ConfigManager.getErrorMessage(error)}`);
|
|
2037
|
+
if (!ConfigManager.isRecoverableError(error)) {
|
|
2038
|
+
console.error(
|
|
2039
|
+
" This may affect future sessions. Please resolve the issue or contact support."
|
|
1675
2040
|
);
|
|
1676
2041
|
}
|
|
2042
|
+
} else {
|
|
2043
|
+
console.warn(
|
|
2044
|
+
"⚠️ Failed to save configuration:",
|
|
2045
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
2046
|
+
);
|
|
1677
2047
|
}
|
|
1678
2048
|
}
|
|
1679
2049
|
const octokit = new Octokit({
|
|
1680
|
-
auth:
|
|
2050
|
+
auth: octokitToken
|
|
1681
2051
|
});
|
|
1682
2052
|
return {
|
|
1683
2053
|
octokit,
|
|
1684
|
-
owner
|
|
1685
|
-
repo
|
|
2054
|
+
owner,
|
|
2055
|
+
repo,
|
|
1686
2056
|
fromSavedConfig: false,
|
|
1687
2057
|
autoDetected: false,
|
|
1688
2058
|
detectionMethod: "manual"
|
|
1689
2059
|
};
|
|
1690
2060
|
};
|
|
1691
2061
|
const getLabelFilePath = async () => {
|
|
1692
|
-
|
|
1693
|
-
|
|
2062
|
+
while (true) {
|
|
2063
|
+
const filePath = (await askText(labelFilePath.message)).trim();
|
|
2064
|
+
if (filePath.length > 0) {
|
|
2065
|
+
return filePath;
|
|
2066
|
+
}
|
|
2067
|
+
console.log("File path cannot be empty. Please try again.");
|
|
2068
|
+
}
|
|
2069
|
+
};
|
|
2070
|
+
const getPromptMessage = (field, fallback) => {
|
|
2071
|
+
return newLabel.find((prompt) => prompt.name === field)?.message ?? fallback;
|
|
1694
2072
|
};
|
|
1695
2073
|
const getNewLabel = async () => {
|
|
1696
|
-
const
|
|
1697
|
-
|
|
2074
|
+
const name = await askText(
|
|
2075
|
+
getPromptMessage("name", "Please type new label name")
|
|
2076
|
+
);
|
|
2077
|
+
const color = await askText(
|
|
2078
|
+
getPromptMessage("color", 'Please type label color without "#" ')
|
|
2079
|
+
);
|
|
2080
|
+
const description = await askText(
|
|
2081
|
+
getPromptMessage("description", "Please type label description")
|
|
2082
|
+
);
|
|
2083
|
+
return {
|
|
2084
|
+
name,
|
|
2085
|
+
color,
|
|
2086
|
+
description
|
|
2087
|
+
};
|
|
1698
2088
|
};
|
|
1699
2089
|
const selectAction = async () => {
|
|
1700
|
-
|
|
1701
|
-
const { action } = response;
|
|
1702
|
-
return action[0] !== void 0 ? action[0] : 99;
|
|
2090
|
+
return askSelect(actionSelector.message, actionSelector.choices);
|
|
1703
2091
|
};
|
|
1704
2092
|
const log = console.log;
|
|
1705
2093
|
let firstStart = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hyouji",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Hyouji (表示) — A command-line tool for organizing and displaying GitHub labels with clarity and harmony.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"author": "koji <baxin1919@gmail.com>",
|
|
@@ -42,23 +42,19 @@
|
|
|
42
42
|
"test:auto-detection": "node tests/integration/auto-detection/integration-flow.cjs",
|
|
43
43
|
"test:verification": "node tests/scripts/verification/run-all.cjs",
|
|
44
44
|
"test:all-custom": "npm run test:error-handling && npm run test:config && npm run test:integration && npm run test:verification",
|
|
45
|
-
"check-cli": "run-s test diff-integration-tests check-integration-tests",
|
|
46
|
-
"check-integration-tests": "run-s check-integration-test:*",
|
|
47
45
|
"diff-integration-tests": "mkdir -p diff && rm -rf diff/test && cp -r test diff/test && rm -rf diff/test/test-*/.git && cd diff && git init --quiet && git add -A && git commit --quiet --no-verify --allow-empty -m 'WIP' && echo '\\n\\nCommitted most recent integration test output in the \"diff\" directory. Review the changes with \"cd diff && git diff HEAD\" or your preferred git diff viewer.'",
|
|
48
46
|
"watch:build": "tsc -p tsconfig.json -w",
|
|
49
|
-
"cov:send": "run-s cov:lcov && codecov",
|
|
50
47
|
"version": "standard-version",
|
|
51
|
-
"reset-hard": "git clean -dfx && git reset --hard &&
|
|
52
|
-
"prepare-release": "run-s reset-hard test cov:check doc:html version doc:publish"
|
|
48
|
+
"reset-hard": "git clean -dfx && git reset --hard && bun install"
|
|
53
49
|
},
|
|
54
50
|
"engines": {
|
|
55
51
|
"node": ">=22.22.0"
|
|
56
52
|
},
|
|
57
53
|
"dependencies": {
|
|
58
54
|
"@octokit/core": "^7.0.6",
|
|
55
|
+
"@opentui/core": "0.1.80",
|
|
59
56
|
"chalk": "^5.6.2",
|
|
60
57
|
"oh-my-logo": "^0.3.2",
|
|
61
|
-
"prompts": "^2.4.2",
|
|
62
58
|
"yaml": "^2.8.1"
|
|
63
59
|
},
|
|
64
60
|
"devDependencies": {
|
|
@@ -66,6 +62,8 @@
|
|
|
66
62
|
"@types/node": "^24.10.0",
|
|
67
63
|
"@vitest/coverage-v8": "^4.0.17",
|
|
68
64
|
"@vitest/ui": "^4.0.17",
|
|
65
|
+
"knip": "^5.85.0",
|
|
66
|
+
"lefthook": "^2.1.1",
|
|
69
67
|
"standard-version": "^9.5.0",
|
|
70
68
|
"typescript": "^5.9.3",
|
|
71
69
|
"vite": "^7.3.1",
|