hyouji 0.0.6 → 0.0.8
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 +3 -0
- package/dist/index.js +369 -88
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -77,6 +77,9 @@ hyouji
|
|
|
77
77
|
On your first run, you'll be prompted to enter:
|
|
78
78
|
|
|
79
79
|
- **GitHub Personal Token** - Generate one [here](https://github.com/settings/tokens) with `repo` scope
|
|
80
|
+
<img width="792" height="564" alt="github_token" src="https://github.com/user-attachments/assets/e460738f-833a-4158-a8ba-61752beaad72" />
|
|
81
|
+
|
|
82
|
+
|
|
80
83
|
- **GitHub Username** - Your GitHub account name
|
|
81
84
|
|
|
82
85
|
These credentials will be securely saved and reused for future sessions.
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Octokit } from "@octokit/core";
|
|
3
2
|
import chalk from "chalk";
|
|
4
|
-
import prompts from "prompts";
|
|
5
3
|
import { renderFilled } from "oh-my-logo";
|
|
6
4
|
import * as fs from "fs";
|
|
7
5
|
import { promises, existsSync } from "fs";
|
|
8
6
|
import { homedir } from "os";
|
|
9
|
-
import { join } from "path";
|
|
7
|
+
import { join, dirname } from "path";
|
|
10
8
|
import { createHash, randomBytes, createCipheriv, createDecipheriv } from "crypto";
|
|
9
|
+
import prompts from "prompts";
|
|
10
|
+
import { Octokit } from "@octokit/core";
|
|
11
|
+
import { exec } from "child_process";
|
|
12
|
+
import { promisify } from "util";
|
|
11
13
|
const githubConfigs = [
|
|
12
14
|
{
|
|
13
15
|
type: "password",
|
|
@@ -62,8 +64,9 @@ const actionSelector = {
|
|
|
62
64
|
{ title: "delete a label", value: 2 },
|
|
63
65
|
{ title: "delete all labels", value: 3 },
|
|
64
66
|
{ title: "import JSON", value: 4 },
|
|
65
|
-
{ title: "
|
|
66
|
-
{ title: "
|
|
67
|
+
{ title: "Generate sample JSON", value: 5 },
|
|
68
|
+
{ title: "Display your settings", value: 6 },
|
|
69
|
+
{ title: "exit", value: 7 }
|
|
67
70
|
]
|
|
68
71
|
};
|
|
69
72
|
const holdToken = {
|
|
@@ -72,6 +75,23 @@ const holdToken = {
|
|
|
72
75
|
message: "Do you have a personal token?",
|
|
73
76
|
initial: true
|
|
74
77
|
};
|
|
78
|
+
const sampleData = [
|
|
79
|
+
{
|
|
80
|
+
name: "Type: Bug Fix",
|
|
81
|
+
color: "FF8A65",
|
|
82
|
+
description: "Fix features that are not working"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "Type: Enhancement",
|
|
86
|
+
color: "64B5F7",
|
|
87
|
+
description: "Add new features"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "Type: Improvement",
|
|
91
|
+
color: "4DB6AC",
|
|
92
|
+
description: "Improve existing functionality"
|
|
93
|
+
}
|
|
94
|
+
];
|
|
75
95
|
const labels = (
|
|
76
96
|
// the following labels are based on this post
|
|
77
97
|
// https://qiita.com/willow-micro/items/51eeb3efe5b4192a4abd
|
|
@@ -245,7 +265,7 @@ Thank you!
|
|
|
245
265
|
};
|
|
246
266
|
const extraGuideText = `If you don't see action selector, please hit space key.`;
|
|
247
267
|
const linkToPersonalToken = "https://github.com/settings/tokens";
|
|
248
|
-
const log$
|
|
268
|
+
const log$3 = console.log;
|
|
249
269
|
const createLabel = async (configs2, label) => {
|
|
250
270
|
const resp = await configs2.octokit.request(
|
|
251
271
|
"POST /repos/{owner}/{repo}/labels",
|
|
@@ -260,16 +280,16 @@ const createLabel = async (configs2, label) => {
|
|
|
260
280
|
const status = resp.status;
|
|
261
281
|
switch (status) {
|
|
262
282
|
case 201:
|
|
263
|
-
log$
|
|
283
|
+
log$3(chalk.green(`${resp.status}: Created ${label.name}`));
|
|
264
284
|
break;
|
|
265
285
|
case 404:
|
|
266
|
-
log$
|
|
286
|
+
log$3(chalk.red(`${resp.status}: Resource not found`));
|
|
267
287
|
break;
|
|
268
288
|
case 422:
|
|
269
|
-
log$
|
|
289
|
+
log$3(chalk.red(`${resp.status}: Validation failed`));
|
|
270
290
|
break;
|
|
271
291
|
default:
|
|
272
|
-
log$
|
|
292
|
+
log$3(chalk.yellow(`${resp.status}: Something wrong`));
|
|
273
293
|
break;
|
|
274
294
|
}
|
|
275
295
|
};
|
|
@@ -277,8 +297,8 @@ const createLabels = async (configs2) => {
|
|
|
277
297
|
labels.forEach(async (label) => {
|
|
278
298
|
createLabel(configs2, label);
|
|
279
299
|
});
|
|
280
|
-
log$
|
|
281
|
-
log$
|
|
300
|
+
log$3("Created all labels");
|
|
301
|
+
log$3(chalk.bgBlueBright(extraGuideText));
|
|
282
302
|
};
|
|
283
303
|
const deleteLabel = async (configs2, labelNames) => {
|
|
284
304
|
for (const labelName of labelNames) {
|
|
@@ -292,15 +312,15 @@ const deleteLabel = async (configs2, labelNames) => {
|
|
|
292
312
|
}
|
|
293
313
|
);
|
|
294
314
|
if (resp.status === 204) {
|
|
295
|
-
log$
|
|
315
|
+
log$3(chalk.green(`${resp.status}: Deleted ${labelName}`));
|
|
296
316
|
} else {
|
|
297
|
-
log$
|
|
317
|
+
log$3(chalk.yellow(`${resp.status}: Something wrong with ${labelName}`));
|
|
298
318
|
}
|
|
299
319
|
} catch (error) {
|
|
300
320
|
if (error && typeof error === "object" && "status" in error && error.status === 404) {
|
|
301
|
-
log$
|
|
321
|
+
log$3(chalk.red(`404: Label "${labelName}" not found`));
|
|
302
322
|
} else {
|
|
303
|
-
log$
|
|
323
|
+
log$3(
|
|
304
324
|
chalk.red(
|
|
305
325
|
`Error deleting label "${labelName}": ${error instanceof Error ? error.message : "Unknown error"}`
|
|
306
326
|
)
|
|
@@ -321,17 +341,17 @@ const getLabels = async (configs2) => {
|
|
|
321
341
|
const names = await resp.data.map((label) => label.name);
|
|
322
342
|
return names;
|
|
323
343
|
} else {
|
|
324
|
-
log$
|
|
344
|
+
log$3(chalk.red("something wrong"));
|
|
325
345
|
return [];
|
|
326
346
|
}
|
|
327
347
|
};
|
|
328
348
|
const deleteLabels = async (configs2) => {
|
|
329
349
|
const names = await getLabels(configs2);
|
|
330
350
|
if (names.length === 0) {
|
|
331
|
-
log$
|
|
351
|
+
log$3(chalk.yellow("No labels found to delete"));
|
|
332
352
|
return;
|
|
333
353
|
}
|
|
334
|
-
log$
|
|
354
|
+
log$3(chalk.blue(`Deleting ${names.length} labels...`));
|
|
335
355
|
for (const name of names) {
|
|
336
356
|
try {
|
|
337
357
|
const resp = await configs2.octokit.request(
|
|
@@ -343,15 +363,15 @@ const deleteLabels = async (configs2) => {
|
|
|
343
363
|
}
|
|
344
364
|
);
|
|
345
365
|
if (resp.status === 204) {
|
|
346
|
-
log$
|
|
366
|
+
log$3(chalk.green(`${resp.status}: Deleted ${name}`));
|
|
347
367
|
} else {
|
|
348
|
-
log$
|
|
368
|
+
log$3(chalk.yellow(`${resp.status}: Something wrong with ${name}`));
|
|
349
369
|
}
|
|
350
370
|
} catch (error) {
|
|
351
371
|
if (error && typeof error === "object" && "status" in error && error.status === 404) {
|
|
352
|
-
log$
|
|
372
|
+
log$3(chalk.red(`404: Label "${name}" not found`));
|
|
353
373
|
} else {
|
|
354
|
-
log$
|
|
374
|
+
log$3(
|
|
355
375
|
chalk.red(
|
|
356
376
|
`Error deleting label "${name}": ${error instanceof Error ? error.message : "Unknown error"}`
|
|
357
377
|
)
|
|
@@ -359,8 +379,8 @@ const deleteLabels = async (configs2) => {
|
|
|
359
379
|
}
|
|
360
380
|
}
|
|
361
381
|
}
|
|
362
|
-
log$
|
|
363
|
-
log$
|
|
382
|
+
log$3(chalk.blue("Finished deleting labels"));
|
|
383
|
+
log$3(chalk.bgBlueBright(extraGuideText));
|
|
364
384
|
};
|
|
365
385
|
const _CryptoUtils = class _CryptoUtils {
|
|
366
386
|
/**
|
|
@@ -954,6 +974,53 @@ const getConfirmation = async () => {
|
|
|
954
974
|
const response = await prompts(holdToken);
|
|
955
975
|
return response.value;
|
|
956
976
|
};
|
|
977
|
+
const log$2 = console.log;
|
|
978
|
+
const generateSampleJson = async () => {
|
|
979
|
+
try {
|
|
980
|
+
const outputPath = "./hyouji.json";
|
|
981
|
+
const jsonContent = JSON.stringify(sampleData, null, 2);
|
|
982
|
+
log$2(chalk.blue("Generating sample JSON file..."));
|
|
983
|
+
fs.writeFileSync(outputPath, jsonContent, "utf8");
|
|
984
|
+
log$2(
|
|
985
|
+
chalk.green(
|
|
986
|
+
"✅ Sample JSON file generated successfully at ./hyouji.json"
|
|
987
|
+
)
|
|
988
|
+
);
|
|
989
|
+
} catch (error) {
|
|
990
|
+
if (error instanceof Error) {
|
|
991
|
+
const nodeError = error;
|
|
992
|
+
if (nodeError.code === "EACCES") {
|
|
993
|
+
log$2(
|
|
994
|
+
chalk.red(
|
|
995
|
+
"❌ Error generating sample JSON file: Permission denied. Please check write permissions for the current directory."
|
|
996
|
+
)
|
|
997
|
+
);
|
|
998
|
+
} else if (nodeError.code === "ENOSPC") {
|
|
999
|
+
log$2(
|
|
1000
|
+
chalk.red(
|
|
1001
|
+
"❌ Error generating sample JSON file: Insufficient disk space."
|
|
1002
|
+
)
|
|
1003
|
+
);
|
|
1004
|
+
} else if (nodeError.code === "EROFS") {
|
|
1005
|
+
log$2(
|
|
1006
|
+
chalk.red(
|
|
1007
|
+
"❌ Error generating sample JSON file: Read-only file system."
|
|
1008
|
+
)
|
|
1009
|
+
);
|
|
1010
|
+
} else {
|
|
1011
|
+
log$2(
|
|
1012
|
+
chalk.red(`❌ Error generating sample JSON file: ${error.message}`)
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
} else {
|
|
1016
|
+
log$2(
|
|
1017
|
+
chalk.red(
|
|
1018
|
+
"❌ An unexpected error occurred while generating the sample JSON file"
|
|
1019
|
+
)
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
957
1024
|
const log$1 = console.log;
|
|
958
1025
|
const importLabelsFromJson = async (configs2, filePath) => {
|
|
959
1026
|
try {
|
|
@@ -1109,6 +1176,177 @@ const getTargetLabel = async () => {
|
|
|
1109
1176
|
const response = await prompts(deleteLabel$1);
|
|
1110
1177
|
return [response.name];
|
|
1111
1178
|
};
|
|
1179
|
+
const execAsync = promisify(exec);
|
|
1180
|
+
const GIT_COMMAND_TIMEOUT_MS = 5e3;
|
|
1181
|
+
class GitRepositoryDetector {
|
|
1182
|
+
/**
|
|
1183
|
+
* Detects Git repository information from the current working directory
|
|
1184
|
+
* @param cwd - Current working directory (defaults to process.cwd())
|
|
1185
|
+
* @returns Promise<GitDetectionResult>
|
|
1186
|
+
*/
|
|
1187
|
+
static async detectRepository(cwd) {
|
|
1188
|
+
const workingDir = cwd || process.cwd();
|
|
1189
|
+
try {
|
|
1190
|
+
const gitRoot = await this.findGitRoot(workingDir);
|
|
1191
|
+
if (!gitRoot) {
|
|
1192
|
+
return {
|
|
1193
|
+
isGitRepository: false,
|
|
1194
|
+
error: "Not a Git repository"
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
const remotes = await this.getAllRemotes(gitRoot);
|
|
1198
|
+
if (remotes.length === 0) {
|
|
1199
|
+
return {
|
|
1200
|
+
isGitRepository: true,
|
|
1201
|
+
error: "No remotes configured"
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
let remoteUrl = null;
|
|
1205
|
+
let detectionMethod = "origin";
|
|
1206
|
+
if (remotes.includes("origin")) {
|
|
1207
|
+
remoteUrl = await this.getRemoteUrl(gitRoot, "origin");
|
|
1208
|
+
}
|
|
1209
|
+
if (!remoteUrl && remotes.length > 0) {
|
|
1210
|
+
remoteUrl = await this.getRemoteUrl(gitRoot, remotes[0]);
|
|
1211
|
+
detectionMethod = "first-remote";
|
|
1212
|
+
}
|
|
1213
|
+
if (!remoteUrl) {
|
|
1214
|
+
return {
|
|
1215
|
+
isGitRepository: true,
|
|
1216
|
+
error: "Could not retrieve remote URL"
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
const parsedUrl = this.parseGitUrl(remoteUrl);
|
|
1220
|
+
if (!parsedUrl) {
|
|
1221
|
+
return {
|
|
1222
|
+
isGitRepository: true,
|
|
1223
|
+
error: "Could not parse remote URL"
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
return {
|
|
1227
|
+
isGitRepository: true,
|
|
1228
|
+
repositoryInfo: {
|
|
1229
|
+
owner: parsedUrl.owner,
|
|
1230
|
+
repo: parsedUrl.repo,
|
|
1231
|
+
remoteUrl,
|
|
1232
|
+
detectionMethod
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1235
|
+
} catch (err) {
|
|
1236
|
+
return {
|
|
1237
|
+
isGitRepository: false,
|
|
1238
|
+
error: err instanceof Error ? err.message : "Unknown error occurred"
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Finds the Git root directory by traversing up the directory tree
|
|
1244
|
+
* @param startPath - Starting directory path
|
|
1245
|
+
* @returns Promise<string | null> - Git root path or null if not found
|
|
1246
|
+
*/
|
|
1247
|
+
static async findGitRoot(startPath) {
|
|
1248
|
+
let currentPath = startPath;
|
|
1249
|
+
while (currentPath !== dirname(currentPath)) {
|
|
1250
|
+
const gitPath = join(currentPath, ".git");
|
|
1251
|
+
if (existsSync(gitPath)) {
|
|
1252
|
+
return currentPath;
|
|
1253
|
+
}
|
|
1254
|
+
currentPath = dirname(currentPath);
|
|
1255
|
+
}
|
|
1256
|
+
return null;
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Gets the URL for a specific Git remote
|
|
1260
|
+
* @param gitRoot - Git repository root directory
|
|
1261
|
+
* @param remoteName - Name of the remote (e.g., 'origin')
|
|
1262
|
+
* @returns Promise<string | null> - Remote URL or null if not found
|
|
1263
|
+
*/
|
|
1264
|
+
static async getRemoteUrl(gitRoot, remoteName) {
|
|
1265
|
+
try {
|
|
1266
|
+
const { stdout } = await execAsync(`git remote get-url ${remoteName}`, {
|
|
1267
|
+
cwd: gitRoot,
|
|
1268
|
+
timeout: GIT_COMMAND_TIMEOUT_MS
|
|
1269
|
+
});
|
|
1270
|
+
return stdout.trim() || null;
|
|
1271
|
+
} catch {
|
|
1272
|
+
return null;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Parses a Git URL to extract owner and repository name
|
|
1277
|
+
* @param url - Git remote URL
|
|
1278
|
+
* @returns Object with owner and repo or null if parsing fails
|
|
1279
|
+
*/
|
|
1280
|
+
static parseGitUrl(url) {
|
|
1281
|
+
if (!url || typeof url !== "string" || url.trim().length === 0) {
|
|
1282
|
+
return null;
|
|
1283
|
+
}
|
|
1284
|
+
const trimmedUrl = url.trim();
|
|
1285
|
+
try {
|
|
1286
|
+
const sshMatch = trimmedUrl.match(
|
|
1287
|
+
/^git@github\.com:([^/\s:]+)\/([^/\s:]+?)(?:\.git)?$/
|
|
1288
|
+
);
|
|
1289
|
+
if (sshMatch) {
|
|
1290
|
+
const owner = sshMatch[1];
|
|
1291
|
+
const repo = sshMatch[2];
|
|
1292
|
+
if (this.isValidGitHubIdentifier(owner) && this.isValidGitHubIdentifier(repo)) {
|
|
1293
|
+
return { owner, repo };
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
const httpsMatch = trimmedUrl.match(
|
|
1297
|
+
/^https:\/\/github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?(?:\/)?$/
|
|
1298
|
+
);
|
|
1299
|
+
if (httpsMatch) {
|
|
1300
|
+
const owner = httpsMatch[1];
|
|
1301
|
+
const repo = httpsMatch[2];
|
|
1302
|
+
if (this.isValidGitHubIdentifier(owner) && this.isValidGitHubIdentifier(repo)) {
|
|
1303
|
+
return { owner, repo };
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
const httpMatch = trimmedUrl.match(
|
|
1307
|
+
/^http:\/\/github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?(?:\/)?$/
|
|
1308
|
+
);
|
|
1309
|
+
if (httpMatch) {
|
|
1310
|
+
const owner = httpMatch[1];
|
|
1311
|
+
const repo = httpMatch[2];
|
|
1312
|
+
if (this.isValidGitHubIdentifier(owner) && this.isValidGitHubIdentifier(repo)) {
|
|
1313
|
+
return { owner, repo };
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
} catch {
|
|
1317
|
+
return null;
|
|
1318
|
+
}
|
|
1319
|
+
return null;
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Validates if a string is a valid GitHub identifier (username or repository name)
|
|
1323
|
+
* @param identifier - The identifier to validate
|
|
1324
|
+
* @returns boolean - True if valid, false otherwise
|
|
1325
|
+
*/
|
|
1326
|
+
static isValidGitHubIdentifier(identifier) {
|
|
1327
|
+
if (!identifier || typeof identifier !== "string") {
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
const GITHUB_IDENTIFIER_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
|
|
1331
|
+
return identifier.length >= 1 && identifier.length <= 39 && GITHUB_IDENTIFIER_REGEX.test(identifier) && !identifier.includes("--");
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Gets all configured Git remotes
|
|
1335
|
+
* @param gitRoot - Git repository root directory
|
|
1336
|
+
* @returns Promise<string[]> - Array of remote names
|
|
1337
|
+
*/
|
|
1338
|
+
static async getAllRemotes(gitRoot) {
|
|
1339
|
+
try {
|
|
1340
|
+
const { stdout } = await execAsync("git remote", {
|
|
1341
|
+
cwd: gitRoot,
|
|
1342
|
+
timeout: GIT_COMMAND_TIMEOUT_MS
|
|
1343
|
+
});
|
|
1344
|
+
return stdout.trim().split("\n").filter((remote) => remote.length > 0);
|
|
1345
|
+
} catch {
|
|
1346
|
+
return [];
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1112
1350
|
const getGitHubConfigs = async () => {
|
|
1113
1351
|
var _a, _b;
|
|
1114
1352
|
const configManager2 = new ConfigManager();
|
|
@@ -1130,6 +1368,50 @@ const getGitHubConfigs = async () => {
|
|
|
1130
1368
|
};
|
|
1131
1369
|
}
|
|
1132
1370
|
if (validationResult.config && !validationResult.shouldPromptForCredentials) {
|
|
1371
|
+
try {
|
|
1372
|
+
const detectionResult = await GitRepositoryDetector.detectRepository();
|
|
1373
|
+
if (detectionResult.isGitRepository && detectionResult.repositoryInfo) {
|
|
1374
|
+
console.log(
|
|
1375
|
+
chalk.green(
|
|
1376
|
+
`✓ Detected repository: ${detectionResult.repositoryInfo.owner}/${detectionResult.repositoryInfo.repo}`
|
|
1377
|
+
)
|
|
1378
|
+
);
|
|
1379
|
+
console.log(
|
|
1380
|
+
chalk.gray(
|
|
1381
|
+
` Detection method: ${detectionResult.repositoryInfo.detectionMethod === "origin" ? "origin remote" : "first available remote"}`
|
|
1382
|
+
)
|
|
1383
|
+
);
|
|
1384
|
+
const octokit3 = new Octokit({
|
|
1385
|
+
auth: validationResult.config.token
|
|
1386
|
+
});
|
|
1387
|
+
return {
|
|
1388
|
+
octokit: octokit3,
|
|
1389
|
+
owner: detectionResult.repositoryInfo.owner,
|
|
1390
|
+
repo: detectionResult.repositoryInfo.repo,
|
|
1391
|
+
fromSavedConfig: true,
|
|
1392
|
+
autoDetected: true,
|
|
1393
|
+
detectionMethod: detectionResult.repositoryInfo.detectionMethod
|
|
1394
|
+
};
|
|
1395
|
+
} else {
|
|
1396
|
+
if (detectionResult.error) {
|
|
1397
|
+
console.log(
|
|
1398
|
+
chalk.yellow(
|
|
1399
|
+
`⚠️ Repository auto-detection failed: ${detectionResult.error}`
|
|
1400
|
+
)
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
console.log(chalk.gray(" Falling back to manual input..."));
|
|
1404
|
+
}
|
|
1405
|
+
} catch (error) {
|
|
1406
|
+
console.log(
|
|
1407
|
+
chalk.yellow(
|
|
1408
|
+
"⚠️ Repository auto-detection failed, falling back to manual input"
|
|
1409
|
+
)
|
|
1410
|
+
);
|
|
1411
|
+
if (error instanceof Error) {
|
|
1412
|
+
console.log(chalk.gray(` Error: ${error.message}`));
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1133
1415
|
const repoResponse = await prompts([
|
|
1134
1416
|
{
|
|
1135
1417
|
type: "text",
|
|
@@ -1144,7 +1426,9 @@ const getGitHubConfigs = async () => {
|
|
|
1144
1426
|
octokit: octokit2,
|
|
1145
1427
|
owner: validationResult.config.owner,
|
|
1146
1428
|
repo: repoResponse.repo,
|
|
1147
|
-
fromSavedConfig: true
|
|
1429
|
+
fromSavedConfig: true,
|
|
1430
|
+
autoDetected: false,
|
|
1431
|
+
detectionMethod: "manual"
|
|
1148
1432
|
};
|
|
1149
1433
|
}
|
|
1150
1434
|
const promptConfig = [...githubConfigs];
|
|
@@ -1196,7 +1480,9 @@ const getGitHubConfigs = async () => {
|
|
|
1196
1480
|
octokit,
|
|
1197
1481
|
owner: response.owner,
|
|
1198
1482
|
repo: response.repo,
|
|
1199
|
-
fromSavedConfig: false
|
|
1483
|
+
fromSavedConfig: false,
|
|
1484
|
+
autoDetected: false,
|
|
1485
|
+
detectionMethod: "manual"
|
|
1200
1486
|
};
|
|
1201
1487
|
};
|
|
1202
1488
|
const getJsonFilePath = async () => {
|
|
@@ -1215,33 +1501,6 @@ const selectAction = async () => {
|
|
|
1215
1501
|
const log = console.log;
|
|
1216
1502
|
let firstStart = true;
|
|
1217
1503
|
const configManager = new ConfigManager();
|
|
1218
|
-
const setupConfigs = async () => {
|
|
1219
|
-
console.log(initialText);
|
|
1220
|
-
if (firstStart) {
|
|
1221
|
-
await configManager.migrateToEncrypted();
|
|
1222
|
-
}
|
|
1223
|
-
const config = await getGitHubConfigs();
|
|
1224
|
-
if (!config.octokit || !config.owner || !config.repo) {
|
|
1225
|
-
throw new Error("Invalid configuration: missing required fields");
|
|
1226
|
-
}
|
|
1227
|
-
try {
|
|
1228
|
-
await config.octokit.request("GET /user");
|
|
1229
|
-
} catch (error) {
|
|
1230
|
-
if (config.fromSavedConfig) {
|
|
1231
|
-
console.log(
|
|
1232
|
-
chalk.yellow(
|
|
1233
|
-
"Saved credentials are invalid. Please provide new credentials."
|
|
1234
|
-
)
|
|
1235
|
-
);
|
|
1236
|
-
await configManager.clearConfig();
|
|
1237
|
-
return setupConfigs();
|
|
1238
|
-
}
|
|
1239
|
-
throw new Error(
|
|
1240
|
-
`GitHub API authentication failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1241
|
-
);
|
|
1242
|
-
}
|
|
1243
|
-
return config;
|
|
1244
|
-
};
|
|
1245
1504
|
const displaySettings = async () => {
|
|
1246
1505
|
log(chalk.cyan("\n=== Current Settings ==="));
|
|
1247
1506
|
const configPath = configManager.getConfigPath();
|
|
@@ -1318,47 +1577,56 @@ const initializeConfigs = async () => {
|
|
|
1318
1577
|
console.warn("Failed to display ASCII art, continuing...");
|
|
1319
1578
|
console.error("Error:", error);
|
|
1320
1579
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
message: "Please type your target repo name"
|
|
1330
|
-
}
|
|
1331
|
-
]);
|
|
1332
|
-
const config = {
|
|
1333
|
-
octokit: new Octokit({ auth: existingConfig.config.token }),
|
|
1334
|
-
owner: existingConfig.config.owner,
|
|
1335
|
-
repo: repoResponse.repo,
|
|
1336
|
-
fromSavedConfig: true
|
|
1337
|
-
};
|
|
1338
|
-
log(chalk.green(`Using saved configuration for ${config.owner}`));
|
|
1339
|
-
return config;
|
|
1340
|
-
} else {
|
|
1341
|
-
return await setupConfigs();
|
|
1342
|
-
}
|
|
1343
|
-
} catch (error) {
|
|
1344
|
-
console.error("Error:", error);
|
|
1345
|
-
return await setupConfigs();
|
|
1580
|
+
try {
|
|
1581
|
+
console.log(initialText);
|
|
1582
|
+
if (firstStart) {
|
|
1583
|
+
await configManager.migrateToEncrypted();
|
|
1584
|
+
}
|
|
1585
|
+
const config = await getGitHubConfigs();
|
|
1586
|
+
if (!config.octokit || !config.owner || !config.repo) {
|
|
1587
|
+
throw new Error("Invalid configuration: missing required fields");
|
|
1346
1588
|
}
|
|
1347
|
-
} else {
|
|
1348
1589
|
try {
|
|
1349
|
-
|
|
1590
|
+
await config.octokit.request("GET /user");
|
|
1591
|
+
} catch (error) {
|
|
1350
1592
|
if (config.fromSavedConfig) {
|
|
1351
|
-
log(
|
|
1593
|
+
console.log(
|
|
1594
|
+
chalk.yellow(
|
|
1595
|
+
"Saved credentials are invalid. Please provide new credentials."
|
|
1596
|
+
)
|
|
1597
|
+
);
|
|
1598
|
+
await configManager.clearConfig();
|
|
1599
|
+
return initializeConfigs();
|
|
1352
1600
|
}
|
|
1353
|
-
|
|
1354
|
-
|
|
1601
|
+
throw new Error(
|
|
1602
|
+
`GitHub API authentication failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
if (config.fromSavedConfig) {
|
|
1606
|
+
log(chalk.green(`✓ Using saved configuration for ${config.owner}`));
|
|
1607
|
+
}
|
|
1608
|
+
if (config.autoDetected) {
|
|
1355
1609
|
log(
|
|
1356
|
-
chalk.
|
|
1357
|
-
|
|
1610
|
+
chalk.green(
|
|
1611
|
+
`✓ Repository auto-detected: ${config.owner}/${config.repo}`
|
|
1358
1612
|
)
|
|
1359
1613
|
);
|
|
1360
|
-
|
|
1614
|
+
const detectionMethodText = config.detectionMethod === "origin" ? "origin remote" : config.detectionMethod === "first-remote" ? "first available remote" : "manual input";
|
|
1615
|
+
log(chalk.gray(` Detection method: ${detectionMethodText}`));
|
|
1616
|
+
} else if (config.detectionMethod === "manual") {
|
|
1617
|
+
log(
|
|
1618
|
+
chalk.blue(`✓ Repository configured: ${config.owner}/${config.repo}`)
|
|
1619
|
+
);
|
|
1620
|
+
log(chalk.gray(` Input method: manual`));
|
|
1361
1621
|
}
|
|
1622
|
+
return config;
|
|
1623
|
+
} catch (error) {
|
|
1624
|
+
log(
|
|
1625
|
+
chalk.red(
|
|
1626
|
+
`Configuration error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1627
|
+
)
|
|
1628
|
+
);
|
|
1629
|
+
return null;
|
|
1362
1630
|
}
|
|
1363
1631
|
};
|
|
1364
1632
|
const main = async () => {
|
|
@@ -1414,11 +1682,24 @@ const main = async () => {
|
|
|
1414
1682
|
break;
|
|
1415
1683
|
}
|
|
1416
1684
|
case 5: {
|
|
1417
|
-
|
|
1685
|
+
try {
|
|
1686
|
+
await generateSampleJson();
|
|
1687
|
+
} catch (error) {
|
|
1688
|
+
log(
|
|
1689
|
+
chalk.red(
|
|
1690
|
+
`Error generating sample JSON: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1691
|
+
)
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1418
1694
|
firstStart = firstStart && false;
|
|
1419
1695
|
break;
|
|
1420
1696
|
}
|
|
1421
1697
|
case 6: {
|
|
1698
|
+
await displaySettings();
|
|
1699
|
+
firstStart = firstStart && false;
|
|
1700
|
+
break;
|
|
1701
|
+
}
|
|
1702
|
+
case 7: {
|
|
1422
1703
|
console.log("exit");
|
|
1423
1704
|
process.exit(0);
|
|
1424
1705
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hyouji",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
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
|
"bin": {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"test:error-handling": "node tests/scripts/error-handling/run-all.cjs",
|
|
39
39
|
"test:config": "node tests/scripts/config/run-all.cjs",
|
|
40
40
|
"test:integration": "node tests/scripts/run-integration.cjs",
|
|
41
|
+
"test:auto-detection": "node tests/integration/auto-detection/integration-flow.cjs",
|
|
41
42
|
"test:verification": "node tests/scripts/verification/run-all.cjs",
|
|
42
43
|
"test:all-custom": "npm run test:error-handling && npm run test:config && npm run test:integration && npm run test:verification",
|
|
43
44
|
"check-cli": "run-s test diff-integration-tests check-integration-tests",
|