hyouji 0.0.15 → 0.0.17
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.md +28 -15
- package/dist/index.js +226 -51
- package/package.json +8 -9
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Hyouji(表示) GitHub Label Manager
|
|
2
2
|
|
|
3
|
+
<img width="2816" height="1536" alt="hyouji_generated_image" src="https://github.com/user-attachments/assets/636382d1-a718-4289-81d7-2943f1962ce8" />
|
|
4
|
+
|
|
5
|
+
|
|
3
6
|
### article
|
|
4
7
|
|
|
5
8
|
https://levelup.gitconnected.com/create-github-labels-from-terminal-158d4868fab
|
|
@@ -94,6 +97,21 @@ These credentials will be securely saved and reused for future sessions.
|
|
|
94
97
|
8. **Display your settings** - View your saved configuration
|
|
95
98
|
9. **Exit**
|
|
96
99
|
|
|
100
|
+
When you choose a create/delete/import action, you’ll be asked if you want to run in **dry-run mode**. Selecting “yes” shows what would happen without any API calls. Example:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
=== Create preset labels summary ===
|
|
104
|
+
Mode: dry run (no API calls executed)
|
|
105
|
+
Created: 0 Failed: 0 Deleted: 0 Skipped: 24
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Quick dry-run example (generate sample JSON then import it without touching GitHub):
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
hyouji # choose “Generate sample JSON”
|
|
112
|
+
hyouji # choose “Import labels from JSON or YAML” -> pick hyouji.json -> select dry-run = yes
|
|
113
|
+
```
|
|
114
|
+
|
|
97
115
|
### Settings Management
|
|
98
116
|
|
|
99
117
|
The tool now includes persistent configuration storage with enhanced security:
|
|
@@ -105,6 +123,12 @@ The tool now includes persistent configuration storage with enhanced security:
|
|
|
105
123
|
- **Automatic migration**: Existing plain text configurations are automatically upgraded to encrypted format
|
|
106
124
|
- **Token security**: Your personal token is never displayed in plain text, only an obfuscated preview is shown
|
|
107
125
|
|
|
126
|
+
### Dry-run Mode & Progress
|
|
127
|
+
|
|
128
|
+
- Select **yes** when prompted for dry-run to avoid any API calls; actions are listed as “Would create/delete…”.
|
|
129
|
+
- Each API call shows short status lines (e.g., “Creating label…”, “Deleted …”).
|
|
130
|
+
- A final summary reports created/deleted/skipped/failed counts and hints for next steps.
|
|
131
|
+
|
|
108
132
|
### Security Features
|
|
109
133
|
|
|
110
134
|
**Token Encryption**:
|
|
@@ -132,22 +156,11 @@ If you want to create/delete a single label, you need to type the followings.
|
|
|
132
156
|
|
|
133
157
|
- label name
|
|
134
158
|
|
|
135
|
-
|
|
136
|
-
If you want to put your own labels, you will need to modify `label.js` file.
|
|
159
|
+
For multiple labels, use the import flow:
|
|
137
160
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
name: "Type: Bug Fix",
|
|
142
|
-
color: "FF8A65",
|
|
143
|
-
description: "Fix features that are not working",
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
name: "Type: Enhancement",
|
|
147
|
-
color: "64B5F7",
|
|
148
|
-
description: "Add new features",
|
|
149
|
-
},
|
|
150
|
-
```
|
|
161
|
+
- Prepare a JSON or YAML file (examples live in `examples/labels.{json,yaml}`).
|
|
162
|
+
- Or generate fresh templates from the menu: **Generate sample JSON/YAML** will create `hyouji.json` or `hyouji.yaml` in your CWD.
|
|
163
|
+
- Run **Import labels** and provide the file path. You can choose **dry-run** first to see what would be created without hitting the API.
|
|
151
164
|
|
|
152
165
|
## Quick Start
|
|
153
166
|
|
package/dist/index.js
CHANGED
|
@@ -56,6 +56,14 @@ const labelFilePath = {
|
|
|
56
56
|
name: "filePath",
|
|
57
57
|
message: "Please type the path to your JSON or YAML file"
|
|
58
58
|
};
|
|
59
|
+
const dryRunToggle = {
|
|
60
|
+
type: "toggle",
|
|
61
|
+
name: "dryRun",
|
|
62
|
+
message: "Run in dry-run mode? (no API calls will be made)",
|
|
63
|
+
active: "yes",
|
|
64
|
+
inactive: "no",
|
|
65
|
+
initial: false
|
|
66
|
+
};
|
|
59
67
|
const actionSelector = {
|
|
60
68
|
type: "multiselect",
|
|
61
69
|
name: "action",
|
|
@@ -270,42 +278,67 @@ const extraGuideText = `If you don't see action selector, please hit space key.`
|
|
|
270
278
|
const linkToPersonalToken = "https://github.com/settings/tokens";
|
|
271
279
|
const log$4 = console.log;
|
|
272
280
|
const createLabel = async (configs2, label) => {
|
|
273
|
-
|
|
274
|
-
"
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
+
try {
|
|
282
|
+
log$4(chalk.cyan(`⏳ Creating label "${label.name}"...`));
|
|
283
|
+
const resp = await configs2.octokit.request(
|
|
284
|
+
"POST /repos/{owner}/{repo}/labels",
|
|
285
|
+
{
|
|
286
|
+
owner: configs2.owner,
|
|
287
|
+
repo: configs2.repo,
|
|
288
|
+
name: label.name,
|
|
289
|
+
color: label.color,
|
|
290
|
+
description: label.description
|
|
291
|
+
}
|
|
292
|
+
);
|
|
293
|
+
const status = resp.status;
|
|
294
|
+
switch (status) {
|
|
295
|
+
case 201:
|
|
296
|
+
log$4(chalk.green(`✓ ${resp.status}: Created ${label.name}`));
|
|
297
|
+
return true;
|
|
298
|
+
case 404:
|
|
299
|
+
log$4(chalk.red(`${resp.status}: Resource not found`));
|
|
300
|
+
return false;
|
|
301
|
+
case 422:
|
|
302
|
+
log$4(chalk.red(`${resp.status}: Validation failed`));
|
|
303
|
+
return false;
|
|
304
|
+
default:
|
|
305
|
+
log$4(chalk.yellow(`${resp.status}: Something wrong`));
|
|
306
|
+
return false;
|
|
281
307
|
}
|
|
282
|
-
)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
log$4(chalk.red(`${resp.status}: Resource not found`));
|
|
290
|
-
break;
|
|
291
|
-
case 422:
|
|
292
|
-
log$4(chalk.red(`${resp.status}: Validation failed`));
|
|
293
|
-
break;
|
|
294
|
-
default:
|
|
295
|
-
log$4(chalk.yellow(`${resp.status}: Something wrong`));
|
|
296
|
-
break;
|
|
308
|
+
} catch (error) {
|
|
309
|
+
log$4(
|
|
310
|
+
chalk.red(
|
|
311
|
+
`Error creating label "${label.name}": ${error instanceof Error ? error.message : "Unknown error"}`
|
|
312
|
+
)
|
|
313
|
+
);
|
|
314
|
+
return false;
|
|
297
315
|
}
|
|
298
316
|
};
|
|
299
317
|
const createLabels = async (configs2) => {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
318
|
+
let created = 0;
|
|
319
|
+
let failed = 0;
|
|
320
|
+
for (const label of labels) {
|
|
321
|
+
const ok = await createLabel(configs2, label);
|
|
322
|
+
if (ok) {
|
|
323
|
+
created++;
|
|
324
|
+
} else {
|
|
325
|
+
failed++;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (failed === 0) {
|
|
329
|
+
log$4(chalk.green("✓ Created all labels successfully"));
|
|
330
|
+
} else {
|
|
331
|
+
log$4(chalk.yellow(`Finished processing labels: ${created} created, ${failed} failed`));
|
|
332
|
+
}
|
|
304
333
|
log$4(chalk.bgBlueBright(extraGuideText));
|
|
334
|
+
return { created, failed };
|
|
305
335
|
};
|
|
306
336
|
const deleteLabel = async (configs2, labelNames) => {
|
|
337
|
+
let deleted = 0;
|
|
338
|
+
let failed = 0;
|
|
307
339
|
for (const labelName of labelNames) {
|
|
308
340
|
try {
|
|
341
|
+
log$4(chalk.cyan(`⏳ Deleting label "${labelName}"...`));
|
|
309
342
|
const resp = await configs2.octokit.request(
|
|
310
343
|
"DELETE /repos/{owner}/{repo}/labels/{name}",
|
|
311
344
|
{
|
|
@@ -315,11 +348,14 @@ const deleteLabel = async (configs2, labelNames) => {
|
|
|
315
348
|
}
|
|
316
349
|
);
|
|
317
350
|
if (resp.status === 204) {
|
|
351
|
+
deleted++;
|
|
318
352
|
log$4(chalk.green(`${resp.status}: Deleted ${labelName}`));
|
|
319
353
|
} else {
|
|
354
|
+
failed++;
|
|
320
355
|
log$4(chalk.yellow(`${resp.status}: Something wrong with ${labelName}`));
|
|
321
356
|
}
|
|
322
357
|
} catch (error) {
|
|
358
|
+
failed++;
|
|
323
359
|
if (error && typeof error === "object" && "status" in error && error.status === 404) {
|
|
324
360
|
log$4(chalk.red(`404: Label "${labelName}" not found`));
|
|
325
361
|
} else {
|
|
@@ -331,6 +367,7 @@ const deleteLabel = async (configs2, labelNames) => {
|
|
|
331
367
|
}
|
|
332
368
|
}
|
|
333
369
|
}
|
|
370
|
+
return { deleted, failed };
|
|
334
371
|
};
|
|
335
372
|
const getLabels = async (configs2) => {
|
|
336
373
|
const resp = await configs2.octokit.request(
|
|
@@ -352,9 +389,11 @@ const deleteLabels = async (configs2) => {
|
|
|
352
389
|
const names = await getLabels(configs2);
|
|
353
390
|
if (names.length === 0) {
|
|
354
391
|
log$4(chalk.yellow("No labels found to delete"));
|
|
355
|
-
return;
|
|
392
|
+
return { deleted: 0, failed: 0 };
|
|
356
393
|
}
|
|
357
394
|
log$4(chalk.blue(`Deleting ${names.length} labels...`));
|
|
395
|
+
let deleted = 0;
|
|
396
|
+
let failed = 0;
|
|
358
397
|
for (const name of names) {
|
|
359
398
|
try {
|
|
360
399
|
const resp = await configs2.octokit.request(
|
|
@@ -366,11 +405,14 @@ const deleteLabels = async (configs2) => {
|
|
|
366
405
|
}
|
|
367
406
|
);
|
|
368
407
|
if (resp.status === 204) {
|
|
408
|
+
deleted++;
|
|
369
409
|
log$4(chalk.green(`${resp.status}: Deleted ${name}`));
|
|
370
410
|
} else {
|
|
411
|
+
failed++;
|
|
371
412
|
log$4(chalk.yellow(`${resp.status}: Something wrong with ${name}`));
|
|
372
413
|
}
|
|
373
414
|
} catch (error) {
|
|
415
|
+
failed++;
|
|
374
416
|
if (error && typeof error === "object" && "status" in error && error.status === 404) {
|
|
375
417
|
log$4(chalk.red(`404: Label "${name}" not found`));
|
|
376
418
|
} else {
|
|
@@ -384,8 +426,15 @@ const deleteLabels = async (configs2) => {
|
|
|
384
426
|
}
|
|
385
427
|
log$4(chalk.blue("Finished deleting labels"));
|
|
386
428
|
log$4(chalk.bgBlueBright(extraGuideText));
|
|
429
|
+
return { deleted, failed };
|
|
387
430
|
};
|
|
388
|
-
|
|
431
|
+
class CryptoUtils {
|
|
432
|
+
static {
|
|
433
|
+
this.ALGORITHM = "aes-256-cbc";
|
|
434
|
+
}
|
|
435
|
+
static {
|
|
436
|
+
this.ENCODING = "hex";
|
|
437
|
+
}
|
|
389
438
|
/**
|
|
390
439
|
* Generate a machine-specific key based on system information
|
|
391
440
|
* This provides basic obfuscation without requiring user passwords
|
|
@@ -464,10 +513,7 @@ const _CryptoUtils = class _CryptoUtils {
|
|
|
464
513
|
const middle = "*".repeat(Math.min(token.length - 8, 20));
|
|
465
514
|
return `${start}${middle}${end}`;
|
|
466
515
|
}
|
|
467
|
-
}
|
|
468
|
-
_CryptoUtils.ALGORITHM = "aes-256-cbc";
|
|
469
|
-
_CryptoUtils.ENCODING = "hex";
|
|
470
|
-
let CryptoUtils = _CryptoUtils;
|
|
516
|
+
}
|
|
471
517
|
class ConfigError extends Error {
|
|
472
518
|
constructor(type, message, originalError) {
|
|
473
519
|
super(message);
|
|
@@ -977,6 +1023,10 @@ const getConfirmation = async () => {
|
|
|
977
1023
|
const response = await prompts(holdToken);
|
|
978
1024
|
return response.value;
|
|
979
1025
|
};
|
|
1026
|
+
const getDryRunChoice = async () => {
|
|
1027
|
+
const response = await prompts(dryRunToggle);
|
|
1028
|
+
return Boolean(response.dryRun);
|
|
1029
|
+
};
|
|
980
1030
|
const log$3 = console.log;
|
|
981
1031
|
const generateSampleJson = async () => {
|
|
982
1032
|
try {
|
|
@@ -1103,11 +1153,18 @@ const formatSupportedExtensions = () => {
|
|
|
1103
1153
|
return getSupportedExtensions().join(", ");
|
|
1104
1154
|
};
|
|
1105
1155
|
const log$1 = console.log;
|
|
1106
|
-
const importLabelsFromFile = async (configs2, filePath) => {
|
|
1156
|
+
const importLabelsFromFile = async (configs2, filePath, dryRun = false) => {
|
|
1157
|
+
const summary = {
|
|
1158
|
+
attempted: 0,
|
|
1159
|
+
succeeded: 0,
|
|
1160
|
+
failed: 0,
|
|
1161
|
+
skipped: 0
|
|
1162
|
+
};
|
|
1107
1163
|
try {
|
|
1108
1164
|
if (!fs.existsSync(filePath)) {
|
|
1109
1165
|
log$1(chalk.red(`Error: File not found at path: ${filePath}`));
|
|
1110
|
-
|
|
1166
|
+
summary.failed += 1;
|
|
1167
|
+
return summary;
|
|
1111
1168
|
}
|
|
1112
1169
|
const format = detectFileFormat(filePath);
|
|
1113
1170
|
if (!format) {
|
|
@@ -1116,7 +1173,8 @@ const importLabelsFromFile = async (configs2, filePath) => {
|
|
|
1116
1173
|
`Error: Unsupported file format. Supported formats: ${formatSupportedExtensions()}`
|
|
1117
1174
|
)
|
|
1118
1175
|
);
|
|
1119
|
-
|
|
1176
|
+
summary.failed += 1;
|
|
1177
|
+
return summary;
|
|
1120
1178
|
}
|
|
1121
1179
|
const fileContent = fs.readFileSync(filePath, "utf8");
|
|
1122
1180
|
let parsedData;
|
|
@@ -1134,11 +1192,13 @@ const importLabelsFromFile = async (configs2, filePath) => {
|
|
|
1134
1192
|
`Parse error: ${parseError instanceof Error ? parseError.message : "Unknown error"}`
|
|
1135
1193
|
)
|
|
1136
1194
|
);
|
|
1137
|
-
|
|
1195
|
+
summary.failed += 1;
|
|
1196
|
+
return summary;
|
|
1138
1197
|
}
|
|
1139
1198
|
if (!Array.isArray(parsedData)) {
|
|
1140
1199
|
log$1(chalk.red("Error: File must contain an array of label objects"));
|
|
1141
|
-
|
|
1200
|
+
summary.failed += 1;
|
|
1201
|
+
return summary;
|
|
1142
1202
|
}
|
|
1143
1203
|
const validLabels = [];
|
|
1144
1204
|
for (let i = 0; i < parsedData.length; i++) {
|
|
@@ -1224,7 +1284,21 @@ const importLabelsFromFile = async (configs2, filePath) => {
|
|
|
1224
1284
|
}
|
|
1225
1285
|
if (validLabels.length === 0) {
|
|
1226
1286
|
log$1(chalk.red("Error: No valid labels found in file"));
|
|
1227
|
-
|
|
1287
|
+
summary.failed += 1;
|
|
1288
|
+
return summary;
|
|
1289
|
+
}
|
|
1290
|
+
summary.attempted = validLabels.length;
|
|
1291
|
+
if (dryRun) {
|
|
1292
|
+
validLabels.forEach((label) => {
|
|
1293
|
+
summary.skipped += 1;
|
|
1294
|
+
log$1(chalk.yellow(`[dry-run] Would create label "${label.name}"`));
|
|
1295
|
+
});
|
|
1296
|
+
log$1(
|
|
1297
|
+
chalk.blue(
|
|
1298
|
+
`Dry run summary: Would create ${validLabels.length} labels.`
|
|
1299
|
+
)
|
|
1300
|
+
);
|
|
1301
|
+
return summary;
|
|
1228
1302
|
}
|
|
1229
1303
|
log$1(chalk.blue(`Starting import of ${validLabels.length} labels...`));
|
|
1230
1304
|
log$1("");
|
|
@@ -1253,11 +1327,14 @@ const importLabelsFromFile = async (configs2, filePath) => {
|
|
|
1253
1327
|
`✅ Import completed successfully! Created ${successCount} labels.`
|
|
1254
1328
|
)
|
|
1255
1329
|
);
|
|
1330
|
+
summary.succeeded = successCount;
|
|
1256
1331
|
} else {
|
|
1257
1332
|
log$1(chalk.yellow(`⚠️ Import completed with some errors:`));
|
|
1258
1333
|
log$1(chalk.green(` • Successfully created: ${successCount} labels`));
|
|
1259
1334
|
log$1(chalk.red(` • Failed to create: ${errorCount} labels`));
|
|
1260
1335
|
log$1(chalk.blue(` • Total processed: ${validLabels.length} labels`));
|
|
1336
|
+
summary.succeeded = successCount;
|
|
1337
|
+
summary.failed += errorCount;
|
|
1261
1338
|
}
|
|
1262
1339
|
} catch (error) {
|
|
1263
1340
|
log$1(
|
|
@@ -1265,14 +1342,19 @@ const importLabelsFromFile = async (configs2, filePath) => {
|
|
|
1265
1342
|
`Error reading file: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1266
1343
|
)
|
|
1267
1344
|
);
|
|
1345
|
+
summary.failed += 1;
|
|
1268
1346
|
}
|
|
1347
|
+
return summary;
|
|
1269
1348
|
};
|
|
1270
1349
|
const getTargetLabel = async () => {
|
|
1271
1350
|
const response = await prompts(deleteLabel$1);
|
|
1272
1351
|
return [response.name];
|
|
1273
1352
|
};
|
|
1274
1353
|
const GIT_COMMAND_TIMEOUT_MS = 5e3;
|
|
1275
|
-
|
|
1354
|
+
class GitRepositoryDetector {
|
|
1355
|
+
static {
|
|
1356
|
+
this.execAsyncInternal = promisify(exec);
|
|
1357
|
+
}
|
|
1276
1358
|
/**
|
|
1277
1359
|
* Overrides the internal execAsync function for testing purposes.
|
|
1278
1360
|
* @param mock - The mock function to use for execAsync.
|
|
@@ -1468,11 +1550,8 @@ const _GitRepositoryDetector = class _GitRepositoryDetector {
|
|
|
1468
1550
|
return { remotes: [] };
|
|
1469
1551
|
}
|
|
1470
1552
|
}
|
|
1471
|
-
}
|
|
1472
|
-
_GitRepositoryDetector.execAsyncInternal = promisify(exec);
|
|
1473
|
-
let GitRepositoryDetector = _GitRepositoryDetector;
|
|
1553
|
+
}
|
|
1474
1554
|
const getGitHubConfigs = async () => {
|
|
1475
|
-
var _a, _b;
|
|
1476
1555
|
const configManager2 = new ConfigManager();
|
|
1477
1556
|
let validationResult = {
|
|
1478
1557
|
config: null,
|
|
@@ -1556,7 +1635,7 @@ const getGitHubConfigs = async () => {
|
|
|
1556
1635
|
};
|
|
1557
1636
|
}
|
|
1558
1637
|
const promptConfig = [...githubConfigs];
|
|
1559
|
-
if (
|
|
1638
|
+
if (validationResult.preservedData?.owner) {
|
|
1560
1639
|
const ownerPromptIndex = promptConfig.findIndex(
|
|
1561
1640
|
(prompt) => prompt.name === "owner"
|
|
1562
1641
|
);
|
|
@@ -1576,7 +1655,7 @@ const getGitHubConfigs = async () => {
|
|
|
1576
1655
|
owner: response.owner,
|
|
1577
1656
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1578
1657
|
});
|
|
1579
|
-
if (
|
|
1658
|
+
if (validationResult.preservedData?.owner && validationResult.preservedData.owner !== response.owner) {
|
|
1580
1659
|
console.log("✓ Configuration updated with new credentials");
|
|
1581
1660
|
} else {
|
|
1582
1661
|
console.log("✓ Configuration saved successfully");
|
|
@@ -1753,6 +1832,32 @@ const initializeConfigs = async () => {
|
|
|
1753
1832
|
return null;
|
|
1754
1833
|
}
|
|
1755
1834
|
};
|
|
1835
|
+
const makeSummary = () => ({
|
|
1836
|
+
created: 0,
|
|
1837
|
+
deleted: 0,
|
|
1838
|
+
skipped: 0,
|
|
1839
|
+
failed: 0,
|
|
1840
|
+
notes: []
|
|
1841
|
+
});
|
|
1842
|
+
const printSummary = (action, summary, dryRun) => {
|
|
1843
|
+
log(chalk.cyan(`
|
|
1844
|
+
=== ${action} summary ===`));
|
|
1845
|
+
if (dryRun) {
|
|
1846
|
+
log(chalk.yellow("Mode: dry run (no API calls executed)"));
|
|
1847
|
+
}
|
|
1848
|
+
log(
|
|
1849
|
+
chalk.green(`Created: ${summary.created}`) + chalk.red(` Failed: ${summary.failed}`) + chalk.blue(` Deleted: ${summary.deleted}`) + chalk.yellow(` Skipped: ${summary.skipped}`)
|
|
1850
|
+
);
|
|
1851
|
+
summary.notes.forEach((note) => log(chalk.gray(`- ${note}`)));
|
|
1852
|
+
if (summary.failed > 0 && !dryRun) {
|
|
1853
|
+
log(
|
|
1854
|
+
chalk.yellow(
|
|
1855
|
+
"Some operations failed. Re-run the command or check your credentials/permissions."
|
|
1856
|
+
)
|
|
1857
|
+
);
|
|
1858
|
+
}
|
|
1859
|
+
log(chalk.cyan("========================\n"));
|
|
1860
|
+
};
|
|
1756
1861
|
const main = async () => {
|
|
1757
1862
|
if (firstStart) {
|
|
1758
1863
|
configs = await initializeConfigs();
|
|
@@ -1764,36 +1869,104 @@ const main = async () => {
|
|
|
1764
1869
|
while (selectedIndex == 99) {
|
|
1765
1870
|
selectedIndex = await selectAction();
|
|
1766
1871
|
}
|
|
1872
|
+
if (selectedIndex === 8) {
|
|
1873
|
+
console.log("exit");
|
|
1874
|
+
process.exit(0);
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
const dryRun = selectedIndex >= 0 && selectedIndex <= 4 ? await getDryRunChoice() : false;
|
|
1767
1878
|
switch (selectedIndex) {
|
|
1768
1879
|
case 0: {
|
|
1880
|
+
const summary = makeSummary();
|
|
1769
1881
|
const newLabel2 = await getNewLabel();
|
|
1770
|
-
|
|
1882
|
+
if (dryRun) {
|
|
1883
|
+
log(
|
|
1884
|
+
chalk.yellow(
|
|
1885
|
+
`[dry-run] Would create label "${newLabel2.name}" with color "${newLabel2.color ?? "N/A"}"`
|
|
1886
|
+
)
|
|
1887
|
+
);
|
|
1888
|
+
summary.skipped += 1;
|
|
1889
|
+
} else {
|
|
1890
|
+
const ok = await createLabel(configs, newLabel2);
|
|
1891
|
+
if (ok) {
|
|
1892
|
+
summary.created += 1;
|
|
1893
|
+
} else {
|
|
1894
|
+
summary.failed += 1;
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
printSummary("Create a label", summary, dryRun);
|
|
1771
1898
|
firstStart = firstStart && false;
|
|
1772
1899
|
break;
|
|
1773
1900
|
}
|
|
1774
1901
|
case 1: {
|
|
1775
|
-
|
|
1902
|
+
const summary = makeSummary();
|
|
1903
|
+
if (dryRun) {
|
|
1904
|
+
log(
|
|
1905
|
+
chalk.yellow(
|
|
1906
|
+
`[dry-run] Would create ${labels.length} preset labels (no API calls)`
|
|
1907
|
+
)
|
|
1908
|
+
);
|
|
1909
|
+
summary.skipped += labels.length;
|
|
1910
|
+
} else {
|
|
1911
|
+
const result = await createLabels(configs);
|
|
1912
|
+
summary.created = result.created;
|
|
1913
|
+
summary.failed = result.failed;
|
|
1914
|
+
}
|
|
1915
|
+
printSummary("Create preset labels", summary, dryRun);
|
|
1776
1916
|
firstStart = firstStart && false;
|
|
1777
1917
|
break;
|
|
1778
1918
|
}
|
|
1779
1919
|
case 2: {
|
|
1920
|
+
const summary = makeSummary();
|
|
1780
1921
|
const targetLabel = await getTargetLabel();
|
|
1781
|
-
|
|
1922
|
+
if (dryRun) {
|
|
1923
|
+
summary.skipped += targetLabel.length;
|
|
1924
|
+
targetLabel.forEach(
|
|
1925
|
+
(name) => log(chalk.yellow(`[dry-run] Would delete label "${name}"`))
|
|
1926
|
+
);
|
|
1927
|
+
} else {
|
|
1928
|
+
const result = await deleteLabel(configs, targetLabel);
|
|
1929
|
+
summary.deleted = result.deleted;
|
|
1930
|
+
summary.failed = result.failed;
|
|
1931
|
+
}
|
|
1932
|
+
printSummary("Delete a label", summary, dryRun);
|
|
1782
1933
|
firstStart = firstStart && false;
|
|
1783
1934
|
break;
|
|
1784
1935
|
}
|
|
1785
1936
|
case 3: {
|
|
1786
|
-
|
|
1937
|
+
const summary = makeSummary();
|
|
1938
|
+
if (dryRun) {
|
|
1939
|
+
log(
|
|
1940
|
+
chalk.yellow(
|
|
1941
|
+
"[dry-run] Would delete all labels in the configured repository"
|
|
1942
|
+
)
|
|
1943
|
+
);
|
|
1944
|
+
summary.skipped += 1;
|
|
1945
|
+
} else {
|
|
1946
|
+
const result = await deleteLabels(configs);
|
|
1947
|
+
summary.deleted = result.deleted;
|
|
1948
|
+
summary.failed = result.failed;
|
|
1949
|
+
summary.notes.push("All labels processed");
|
|
1950
|
+
}
|
|
1951
|
+
printSummary("Delete all labels", summary, dryRun);
|
|
1787
1952
|
firstStart = firstStart && false;
|
|
1788
1953
|
break;
|
|
1789
1954
|
}
|
|
1790
1955
|
case 4: {
|
|
1956
|
+
const summary = makeSummary();
|
|
1791
1957
|
try {
|
|
1792
1958
|
const filePath = await getLabelFilePath();
|
|
1793
1959
|
if (filePath) {
|
|
1794
|
-
await importLabelsFromFile(configs, filePath);
|
|
1960
|
+
const result = await importLabelsFromFile(configs, filePath, dryRun);
|
|
1961
|
+
summary.created = result.succeeded;
|
|
1962
|
+
summary.failed = result.failed;
|
|
1963
|
+
summary.skipped = result.skipped;
|
|
1964
|
+
summary.notes.push(
|
|
1965
|
+
`Processed ${result.attempted} label entries from file`
|
|
1966
|
+
);
|
|
1795
1967
|
} else {
|
|
1796
1968
|
log(chalk.yellow("No file path provided. Returning to main menu."));
|
|
1969
|
+
summary.skipped += 1;
|
|
1797
1970
|
}
|
|
1798
1971
|
} catch (error) {
|
|
1799
1972
|
log(
|
|
@@ -1801,7 +1974,9 @@ const main = async () => {
|
|
|
1801
1974
|
`Error during label import: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1802
1975
|
)
|
|
1803
1976
|
);
|
|
1977
|
+
summary.failed += 1;
|
|
1804
1978
|
}
|
|
1979
|
+
printSummary("Import labels", summary, dryRun);
|
|
1805
1980
|
firstStart = firstStart && false;
|
|
1806
1981
|
break;
|
|
1807
1982
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hyouji",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
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>",
|
|
@@ -24,8 +24,7 @@
|
|
|
24
24
|
"表示",
|
|
25
25
|
"Hyouji(表示)",
|
|
26
26
|
"JSON",
|
|
27
|
-
"yaml"
|
|
28
|
-
"yml"
|
|
27
|
+
"yaml"
|
|
29
28
|
],
|
|
30
29
|
"scripts": {
|
|
31
30
|
"start": "node dist/index.js",
|
|
@@ -53,7 +52,7 @@
|
|
|
53
52
|
"prepare-release": "run-s reset-hard test cov:check doc:html version doc:publish"
|
|
54
53
|
},
|
|
55
54
|
"engines": {
|
|
56
|
-
"node": ">=
|
|
55
|
+
"node": ">=22.22.0"
|
|
57
56
|
},
|
|
58
57
|
"dependencies": {
|
|
59
58
|
"@octokit/core": "^7.0.6",
|
|
@@ -63,14 +62,14 @@
|
|
|
63
62
|
"yaml": "^2.8.1"
|
|
64
63
|
},
|
|
65
64
|
"devDependencies": {
|
|
66
|
-
"@biomejs/biome": "2.3.
|
|
65
|
+
"@biomejs/biome": "2.3.11",
|
|
67
66
|
"@types/node": "^24.10.0",
|
|
68
|
-
"@vitest/coverage-v8": "^4.0.
|
|
69
|
-
"@vitest/ui": "^4.0.
|
|
67
|
+
"@vitest/coverage-v8": "^4.0.17",
|
|
68
|
+
"@vitest/ui": "^4.0.17",
|
|
70
69
|
"standard-version": "^9.5.0",
|
|
71
70
|
"typescript": "^5.9.3",
|
|
72
|
-
"vite": "^7.
|
|
73
|
-
"vitest": "^4.0.
|
|
71
|
+
"vite": "^7.3.1",
|
|
72
|
+
"vitest": "^4.0.17"
|
|
74
73
|
},
|
|
75
74
|
"files": [
|
|
76
75
|
"dist/**/*",
|