hyouji 0.0.5 → 0.0.7
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 +5 -1
- package/dist/index.js +353 -104
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -16,7 +16,8 @@ https://levelup.gitconnected.com/create-github-labels-from-terminal-158d4868fab
|
|
|
16
16
|
|
|
17
17
|

|
|
18
18
|
|
|
19
|
-
https://
|
|
19
|
+
https://github.com/user-attachments/assets/739f185a-1bd0-411b-8947-dd4600c452c8
|
|
20
|
+
|
|
20
21
|
|
|
21
22
|
### Labels API
|
|
22
23
|
|
|
@@ -76,6 +77,9 @@ hyouji
|
|
|
76
77
|
On your first run, you'll be prompted to enter:
|
|
77
78
|
|
|
78
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
|
+
|
|
79
83
|
- **GitHub Username** - Your GitHub account name
|
|
80
84
|
|
|
81
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",
|
|
@@ -280,17 +282,34 @@ const createLabels = async (configs2) => {
|
|
|
280
282
|
log$2("Created all labels");
|
|
281
283
|
log$2(chalk.bgBlueBright(extraGuideText));
|
|
282
284
|
};
|
|
283
|
-
const deleteLabel = (configs2, labelNames) => {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
285
|
+
const deleteLabel = async (configs2, labelNames) => {
|
|
286
|
+
for (const labelName of labelNames) {
|
|
287
|
+
try {
|
|
288
|
+
const resp = await configs2.octokit.request(
|
|
289
|
+
"DELETE /repos/{owner}/{repo}/labels/{name}",
|
|
290
|
+
{
|
|
291
|
+
owner: configs2.owner,
|
|
292
|
+
repo: configs2.repo,
|
|
293
|
+
name: labelName
|
|
294
|
+
}
|
|
295
|
+
);
|
|
296
|
+
if (resp.status === 204) {
|
|
297
|
+
log$2(chalk.green(`${resp.status}: Deleted ${labelName}`));
|
|
298
|
+
} else {
|
|
299
|
+
log$2(chalk.yellow(`${resp.status}: Something wrong with ${labelName}`));
|
|
291
300
|
}
|
|
292
|
-
)
|
|
293
|
-
|
|
301
|
+
} catch (error) {
|
|
302
|
+
if (error && typeof error === "object" && "status" in error && error.status === 404) {
|
|
303
|
+
log$2(chalk.red(`404: Label "${labelName}" not found`));
|
|
304
|
+
} else {
|
|
305
|
+
log$2(
|
|
306
|
+
chalk.red(
|
|
307
|
+
`Error deleting label "${labelName}": ${error instanceof Error ? error.message : "Unknown error"}`
|
|
308
|
+
)
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
294
313
|
};
|
|
295
314
|
const getLabels = async (configs2) => {
|
|
296
315
|
const resp = await configs2.octokit.request(
|
|
@@ -310,18 +329,39 @@ const getLabels = async (configs2) => {
|
|
|
310
329
|
};
|
|
311
330
|
const deleteLabels = async (configs2) => {
|
|
312
331
|
const names = await getLabels(configs2);
|
|
313
|
-
names.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
332
|
+
if (names.length === 0) {
|
|
333
|
+
log$2(chalk.yellow("No labels found to delete"));
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
log$2(chalk.blue(`Deleting ${names.length} labels...`));
|
|
337
|
+
for (const name of names) {
|
|
338
|
+
try {
|
|
339
|
+
const resp = await configs2.octokit.request(
|
|
340
|
+
"DELETE /repos/{owner}/{repo}/labels/{name}",
|
|
341
|
+
{
|
|
342
|
+
owner: configs2.owner,
|
|
343
|
+
repo: configs2.repo,
|
|
344
|
+
name
|
|
345
|
+
}
|
|
346
|
+
);
|
|
347
|
+
if (resp.status === 204) {
|
|
348
|
+
log$2(chalk.green(`${resp.status}: Deleted ${name}`));
|
|
349
|
+
} else {
|
|
350
|
+
log$2(chalk.yellow(`${resp.status}: Something wrong with ${name}`));
|
|
320
351
|
}
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
352
|
+
} catch (error) {
|
|
353
|
+
if (error && typeof error === "object" && "status" in error && error.status === 404) {
|
|
354
|
+
log$2(chalk.red(`404: Label "${name}" not found`));
|
|
355
|
+
} else {
|
|
356
|
+
log$2(
|
|
357
|
+
chalk.red(
|
|
358
|
+
`Error deleting label "${name}": ${error instanceof Error ? error.message : "Unknown error"}`
|
|
359
|
+
)
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
log$2(chalk.blue("Finished deleting labels"));
|
|
325
365
|
log$2(chalk.bgBlueBright(extraGuideText));
|
|
326
366
|
};
|
|
327
367
|
const _CryptoUtils = class _CryptoUtils {
|
|
@@ -1071,6 +1111,177 @@ const getTargetLabel = async () => {
|
|
|
1071
1111
|
const response = await prompts(deleteLabel$1);
|
|
1072
1112
|
return [response.name];
|
|
1073
1113
|
};
|
|
1114
|
+
const execAsync = promisify(exec);
|
|
1115
|
+
const GIT_COMMAND_TIMEOUT_MS = 5e3;
|
|
1116
|
+
class GitRepositoryDetector {
|
|
1117
|
+
/**
|
|
1118
|
+
* Detects Git repository information from the current working directory
|
|
1119
|
+
* @param cwd - Current working directory (defaults to process.cwd())
|
|
1120
|
+
* @returns Promise<GitDetectionResult>
|
|
1121
|
+
*/
|
|
1122
|
+
static async detectRepository(cwd) {
|
|
1123
|
+
const workingDir = cwd || process.cwd();
|
|
1124
|
+
try {
|
|
1125
|
+
const gitRoot = await this.findGitRoot(workingDir);
|
|
1126
|
+
if (!gitRoot) {
|
|
1127
|
+
return {
|
|
1128
|
+
isGitRepository: false,
|
|
1129
|
+
error: "Not a Git repository"
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
const remotes = await this.getAllRemotes(gitRoot);
|
|
1133
|
+
if (remotes.length === 0) {
|
|
1134
|
+
return {
|
|
1135
|
+
isGitRepository: true,
|
|
1136
|
+
error: "No remotes configured"
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
let remoteUrl = null;
|
|
1140
|
+
let detectionMethod = "origin";
|
|
1141
|
+
if (remotes.includes("origin")) {
|
|
1142
|
+
remoteUrl = await this.getRemoteUrl(gitRoot, "origin");
|
|
1143
|
+
}
|
|
1144
|
+
if (!remoteUrl && remotes.length > 0) {
|
|
1145
|
+
remoteUrl = await this.getRemoteUrl(gitRoot, remotes[0]);
|
|
1146
|
+
detectionMethod = "first-remote";
|
|
1147
|
+
}
|
|
1148
|
+
if (!remoteUrl) {
|
|
1149
|
+
return {
|
|
1150
|
+
isGitRepository: true,
|
|
1151
|
+
error: "Could not retrieve remote URL"
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
const parsedUrl = this.parseGitUrl(remoteUrl);
|
|
1155
|
+
if (!parsedUrl) {
|
|
1156
|
+
return {
|
|
1157
|
+
isGitRepository: true,
|
|
1158
|
+
error: "Could not parse remote URL"
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
return {
|
|
1162
|
+
isGitRepository: true,
|
|
1163
|
+
repositoryInfo: {
|
|
1164
|
+
owner: parsedUrl.owner,
|
|
1165
|
+
repo: parsedUrl.repo,
|
|
1166
|
+
remoteUrl,
|
|
1167
|
+
detectionMethod
|
|
1168
|
+
}
|
|
1169
|
+
};
|
|
1170
|
+
} catch (err) {
|
|
1171
|
+
return {
|
|
1172
|
+
isGitRepository: false,
|
|
1173
|
+
error: err instanceof Error ? err.message : "Unknown error occurred"
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Finds the Git root directory by traversing up the directory tree
|
|
1179
|
+
* @param startPath - Starting directory path
|
|
1180
|
+
* @returns Promise<string | null> - Git root path or null if not found
|
|
1181
|
+
*/
|
|
1182
|
+
static async findGitRoot(startPath) {
|
|
1183
|
+
let currentPath = startPath;
|
|
1184
|
+
while (currentPath !== dirname(currentPath)) {
|
|
1185
|
+
const gitPath = join(currentPath, ".git");
|
|
1186
|
+
if (existsSync(gitPath)) {
|
|
1187
|
+
return currentPath;
|
|
1188
|
+
}
|
|
1189
|
+
currentPath = dirname(currentPath);
|
|
1190
|
+
}
|
|
1191
|
+
return null;
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Gets the URL for a specific Git remote
|
|
1195
|
+
* @param gitRoot - Git repository root directory
|
|
1196
|
+
* @param remoteName - Name of the remote (e.g., 'origin')
|
|
1197
|
+
* @returns Promise<string | null> - Remote URL or null if not found
|
|
1198
|
+
*/
|
|
1199
|
+
static async getRemoteUrl(gitRoot, remoteName) {
|
|
1200
|
+
try {
|
|
1201
|
+
const { stdout } = await execAsync(`git remote get-url ${remoteName}`, {
|
|
1202
|
+
cwd: gitRoot,
|
|
1203
|
+
timeout: GIT_COMMAND_TIMEOUT_MS
|
|
1204
|
+
});
|
|
1205
|
+
return stdout.trim() || null;
|
|
1206
|
+
} catch {
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Parses a Git URL to extract owner and repository name
|
|
1212
|
+
* @param url - Git remote URL
|
|
1213
|
+
* @returns Object with owner and repo or null if parsing fails
|
|
1214
|
+
*/
|
|
1215
|
+
static parseGitUrl(url) {
|
|
1216
|
+
if (!url || typeof url !== "string" || url.trim().length === 0) {
|
|
1217
|
+
return null;
|
|
1218
|
+
}
|
|
1219
|
+
const trimmedUrl = url.trim();
|
|
1220
|
+
try {
|
|
1221
|
+
const sshMatch = trimmedUrl.match(
|
|
1222
|
+
/^git@github\.com:([^/\s:]+)\/([^/\s:]+?)(?:\.git)?$/
|
|
1223
|
+
);
|
|
1224
|
+
if (sshMatch) {
|
|
1225
|
+
const owner = sshMatch[1];
|
|
1226
|
+
const repo = sshMatch[2];
|
|
1227
|
+
if (this.isValidGitHubIdentifier(owner) && this.isValidGitHubIdentifier(repo)) {
|
|
1228
|
+
return { owner, repo };
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
const httpsMatch = trimmedUrl.match(
|
|
1232
|
+
/^https:\/\/github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?(?:\/)?$/
|
|
1233
|
+
);
|
|
1234
|
+
if (httpsMatch) {
|
|
1235
|
+
const owner = httpsMatch[1];
|
|
1236
|
+
const repo = httpsMatch[2];
|
|
1237
|
+
if (this.isValidGitHubIdentifier(owner) && this.isValidGitHubIdentifier(repo)) {
|
|
1238
|
+
return { owner, repo };
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
const httpMatch = trimmedUrl.match(
|
|
1242
|
+
/^http:\/\/github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?(?:\/)?$/
|
|
1243
|
+
);
|
|
1244
|
+
if (httpMatch) {
|
|
1245
|
+
const owner = httpMatch[1];
|
|
1246
|
+
const repo = httpMatch[2];
|
|
1247
|
+
if (this.isValidGitHubIdentifier(owner) && this.isValidGitHubIdentifier(repo)) {
|
|
1248
|
+
return { owner, repo };
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
} catch {
|
|
1252
|
+
return null;
|
|
1253
|
+
}
|
|
1254
|
+
return null;
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Validates if a string is a valid GitHub identifier (username or repository name)
|
|
1258
|
+
* @param identifier - The identifier to validate
|
|
1259
|
+
* @returns boolean - True if valid, false otherwise
|
|
1260
|
+
*/
|
|
1261
|
+
static isValidGitHubIdentifier(identifier) {
|
|
1262
|
+
if (!identifier || typeof identifier !== "string") {
|
|
1263
|
+
return false;
|
|
1264
|
+
}
|
|
1265
|
+
const GITHUB_IDENTIFIER_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
|
|
1266
|
+
return identifier.length >= 1 && identifier.length <= 39 && GITHUB_IDENTIFIER_REGEX.test(identifier) && !identifier.includes("--");
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Gets all configured Git remotes
|
|
1270
|
+
* @param gitRoot - Git repository root directory
|
|
1271
|
+
* @returns Promise<string[]> - Array of remote names
|
|
1272
|
+
*/
|
|
1273
|
+
static async getAllRemotes(gitRoot) {
|
|
1274
|
+
try {
|
|
1275
|
+
const { stdout } = await execAsync("git remote", {
|
|
1276
|
+
cwd: gitRoot,
|
|
1277
|
+
timeout: GIT_COMMAND_TIMEOUT_MS
|
|
1278
|
+
});
|
|
1279
|
+
return stdout.trim().split("\n").filter((remote) => remote.length > 0);
|
|
1280
|
+
} catch {
|
|
1281
|
+
return [];
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1074
1285
|
const getGitHubConfigs = async () => {
|
|
1075
1286
|
var _a, _b;
|
|
1076
1287
|
const configManager2 = new ConfigManager();
|
|
@@ -1092,6 +1303,50 @@ const getGitHubConfigs = async () => {
|
|
|
1092
1303
|
};
|
|
1093
1304
|
}
|
|
1094
1305
|
if (validationResult.config && !validationResult.shouldPromptForCredentials) {
|
|
1306
|
+
try {
|
|
1307
|
+
const detectionResult = await GitRepositoryDetector.detectRepository();
|
|
1308
|
+
if (detectionResult.isGitRepository && detectionResult.repositoryInfo) {
|
|
1309
|
+
console.log(
|
|
1310
|
+
chalk.green(
|
|
1311
|
+
`✓ Detected repository: ${detectionResult.repositoryInfo.owner}/${detectionResult.repositoryInfo.repo}`
|
|
1312
|
+
)
|
|
1313
|
+
);
|
|
1314
|
+
console.log(
|
|
1315
|
+
chalk.gray(
|
|
1316
|
+
` Detection method: ${detectionResult.repositoryInfo.detectionMethod === "origin" ? "origin remote" : "first available remote"}`
|
|
1317
|
+
)
|
|
1318
|
+
);
|
|
1319
|
+
const octokit3 = new Octokit({
|
|
1320
|
+
auth: validationResult.config.token
|
|
1321
|
+
});
|
|
1322
|
+
return {
|
|
1323
|
+
octokit: octokit3,
|
|
1324
|
+
owner: detectionResult.repositoryInfo.owner,
|
|
1325
|
+
repo: detectionResult.repositoryInfo.repo,
|
|
1326
|
+
fromSavedConfig: true,
|
|
1327
|
+
autoDetected: true,
|
|
1328
|
+
detectionMethod: detectionResult.repositoryInfo.detectionMethod
|
|
1329
|
+
};
|
|
1330
|
+
} else {
|
|
1331
|
+
if (detectionResult.error) {
|
|
1332
|
+
console.log(
|
|
1333
|
+
chalk.yellow(
|
|
1334
|
+
`⚠️ Repository auto-detection failed: ${detectionResult.error}`
|
|
1335
|
+
)
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
console.log(chalk.gray(" Falling back to manual input..."));
|
|
1339
|
+
}
|
|
1340
|
+
} catch (error) {
|
|
1341
|
+
console.log(
|
|
1342
|
+
chalk.yellow(
|
|
1343
|
+
"⚠️ Repository auto-detection failed, falling back to manual input"
|
|
1344
|
+
)
|
|
1345
|
+
);
|
|
1346
|
+
if (error instanceof Error) {
|
|
1347
|
+
console.log(chalk.gray(` Error: ${error.message}`));
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1095
1350
|
const repoResponse = await prompts([
|
|
1096
1351
|
{
|
|
1097
1352
|
type: "text",
|
|
@@ -1106,7 +1361,9 @@ const getGitHubConfigs = async () => {
|
|
|
1106
1361
|
octokit: octokit2,
|
|
1107
1362
|
owner: validationResult.config.owner,
|
|
1108
1363
|
repo: repoResponse.repo,
|
|
1109
|
-
fromSavedConfig: true
|
|
1364
|
+
fromSavedConfig: true,
|
|
1365
|
+
autoDetected: false,
|
|
1366
|
+
detectionMethod: "manual"
|
|
1110
1367
|
};
|
|
1111
1368
|
}
|
|
1112
1369
|
const promptConfig = [...githubConfigs];
|
|
@@ -1158,7 +1415,9 @@ const getGitHubConfigs = async () => {
|
|
|
1158
1415
|
octokit,
|
|
1159
1416
|
owner: response.owner,
|
|
1160
1417
|
repo: response.repo,
|
|
1161
|
-
fromSavedConfig: false
|
|
1418
|
+
fromSavedConfig: false,
|
|
1419
|
+
autoDetected: false,
|
|
1420
|
+
detectionMethod: "manual"
|
|
1162
1421
|
};
|
|
1163
1422
|
};
|
|
1164
1423
|
const getJsonFilePath = async () => {
|
|
@@ -1177,33 +1436,6 @@ const selectAction = async () => {
|
|
|
1177
1436
|
const log = console.log;
|
|
1178
1437
|
let firstStart = true;
|
|
1179
1438
|
const configManager = new ConfigManager();
|
|
1180
|
-
const setupConfigs = async () => {
|
|
1181
|
-
console.log(initialText);
|
|
1182
|
-
if (firstStart) {
|
|
1183
|
-
await configManager.migrateToEncrypted();
|
|
1184
|
-
}
|
|
1185
|
-
const config = await getGitHubConfigs();
|
|
1186
|
-
if (!config.octokit || !config.owner || !config.repo) {
|
|
1187
|
-
throw new Error("Invalid configuration: missing required fields");
|
|
1188
|
-
}
|
|
1189
|
-
try {
|
|
1190
|
-
await config.octokit.request("GET /user");
|
|
1191
|
-
} catch (error) {
|
|
1192
|
-
if (config.fromSavedConfig) {
|
|
1193
|
-
console.log(
|
|
1194
|
-
chalk.yellow(
|
|
1195
|
-
"Saved credentials are invalid. Please provide new credentials."
|
|
1196
|
-
)
|
|
1197
|
-
);
|
|
1198
|
-
await configManager.clearConfig();
|
|
1199
|
-
return setupConfigs();
|
|
1200
|
-
}
|
|
1201
|
-
throw new Error(
|
|
1202
|
-
`GitHub API authentication failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1203
|
-
);
|
|
1204
|
-
}
|
|
1205
|
-
return config;
|
|
1206
|
-
};
|
|
1207
1439
|
const displaySettings = async () => {
|
|
1208
1440
|
log(chalk.cyan("\n=== Current Settings ==="));
|
|
1209
1441
|
const configPath = configManager.getConfigPath();
|
|
@@ -1247,9 +1479,9 @@ const displaySettings = async () => {
|
|
|
1247
1479
|
log(chalk.cyan("========================\n"));
|
|
1248
1480
|
};
|
|
1249
1481
|
let configs;
|
|
1250
|
-
const
|
|
1482
|
+
const initializeConfigs = async () => {
|
|
1251
1483
|
let hasValidConfig = false;
|
|
1252
|
-
if (
|
|
1484
|
+
if (configManager.configExists()) {
|
|
1253
1485
|
try {
|
|
1254
1486
|
const existingConfig = await configManager.loadValidatedConfig();
|
|
1255
1487
|
if (existingConfig && existingConfig.config && !existingConfig.shouldPromptForCredentials) {
|
|
@@ -1268,58 +1500,75 @@ const main = async () => {
|
|
|
1268
1500
|
`Please go to ${linkToPersonalToken} and generate a personal token!`
|
|
1269
1501
|
)
|
|
1270
1502
|
);
|
|
1271
|
-
return;
|
|
1503
|
+
return null;
|
|
1272
1504
|
}
|
|
1273
1505
|
}
|
|
1274
|
-
|
|
1506
|
+
try {
|
|
1507
|
+
const asciiText = await getAsciiText();
|
|
1508
|
+
if (asciiText != null) {
|
|
1509
|
+
log(asciiText);
|
|
1510
|
+
}
|
|
1511
|
+
} catch (error) {
|
|
1512
|
+
console.warn("Failed to display ASCII art, continuing...");
|
|
1513
|
+
console.error("Error:", error);
|
|
1514
|
+
}
|
|
1515
|
+
try {
|
|
1516
|
+
console.log(initialText);
|
|
1517
|
+
if (firstStart) {
|
|
1518
|
+
await configManager.migrateToEncrypted();
|
|
1519
|
+
}
|
|
1520
|
+
const config = await getGitHubConfigs();
|
|
1521
|
+
if (!config.octokit || !config.owner || !config.repo) {
|
|
1522
|
+
throw new Error("Invalid configuration: missing required fields");
|
|
1523
|
+
}
|
|
1275
1524
|
try {
|
|
1276
|
-
|
|
1277
|
-
if (asciiText != null) {
|
|
1278
|
-
log(asciiText);
|
|
1279
|
-
}
|
|
1525
|
+
await config.octokit.request("GET /user");
|
|
1280
1526
|
} catch (error) {
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
try {
|
|
1286
|
-
const existingConfig = await configManager.loadValidatedConfig();
|
|
1287
|
-
if (existingConfig && existingConfig.config) {
|
|
1288
|
-
const repoResponse = await prompts([
|
|
1289
|
-
{
|
|
1290
|
-
type: "text",
|
|
1291
|
-
name: "repo",
|
|
1292
|
-
message: "Please type your target repo name"
|
|
1293
|
-
}
|
|
1294
|
-
]);
|
|
1295
|
-
configs = {
|
|
1296
|
-
octokit: new Octokit({ auth: existingConfig.config.token }),
|
|
1297
|
-
owner: existingConfig.config.owner,
|
|
1298
|
-
repo: repoResponse.repo,
|
|
1299
|
-
fromSavedConfig: true
|
|
1300
|
-
};
|
|
1301
|
-
log(chalk.green(`Using saved configuration for ${configs.owner}`));
|
|
1302
|
-
} else {
|
|
1303
|
-
configs = await setupConfigs();
|
|
1304
|
-
}
|
|
1305
|
-
} catch (error) {
|
|
1306
|
-
console.error("Error:", error);
|
|
1307
|
-
configs = await setupConfigs();
|
|
1308
|
-
}
|
|
1309
|
-
} else {
|
|
1310
|
-
try {
|
|
1311
|
-
configs = await setupConfigs();
|
|
1312
|
-
if (configs.fromSavedConfig) {
|
|
1313
|
-
log(chalk.green(`Using saved configuration for ${configs.owner}`));
|
|
1314
|
-
}
|
|
1315
|
-
} catch (error) {
|
|
1316
|
-
log(
|
|
1317
|
-
chalk.red(
|
|
1318
|
-
`Configuration error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1527
|
+
if (config.fromSavedConfig) {
|
|
1528
|
+
console.log(
|
|
1529
|
+
chalk.yellow(
|
|
1530
|
+
"Saved credentials are invalid. Please provide new credentials."
|
|
1319
1531
|
)
|
|
1320
1532
|
);
|
|
1321
|
-
|
|
1533
|
+
await configManager.clearConfig();
|
|
1534
|
+
return initializeConfigs();
|
|
1322
1535
|
}
|
|
1536
|
+
throw new Error(
|
|
1537
|
+
`GitHub API authentication failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
if (config.fromSavedConfig) {
|
|
1541
|
+
log(chalk.green(`✓ Using saved configuration for ${config.owner}`));
|
|
1542
|
+
}
|
|
1543
|
+
if (config.autoDetected) {
|
|
1544
|
+
log(
|
|
1545
|
+
chalk.green(
|
|
1546
|
+
`✓ Repository auto-detected: ${config.owner}/${config.repo}`
|
|
1547
|
+
)
|
|
1548
|
+
);
|
|
1549
|
+
const detectionMethodText = config.detectionMethod === "origin" ? "origin remote" : config.detectionMethod === "first-remote" ? "first available remote" : "manual input";
|
|
1550
|
+
log(chalk.gray(` Detection method: ${detectionMethodText}`));
|
|
1551
|
+
} else if (config.detectionMethod === "manual") {
|
|
1552
|
+
log(
|
|
1553
|
+
chalk.blue(`✓ Repository configured: ${config.owner}/${config.repo}`)
|
|
1554
|
+
);
|
|
1555
|
+
log(chalk.gray(` Input method: manual`));
|
|
1556
|
+
}
|
|
1557
|
+
return config;
|
|
1558
|
+
} catch (error) {
|
|
1559
|
+
log(
|
|
1560
|
+
chalk.red(
|
|
1561
|
+
`Configuration error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1562
|
+
)
|
|
1563
|
+
);
|
|
1564
|
+
return null;
|
|
1565
|
+
}
|
|
1566
|
+
};
|
|
1567
|
+
const main = async () => {
|
|
1568
|
+
if (firstStart) {
|
|
1569
|
+
configs = await initializeConfigs();
|
|
1570
|
+
if (!configs) {
|
|
1571
|
+
return;
|
|
1323
1572
|
}
|
|
1324
1573
|
}
|
|
1325
1574
|
let selectedIndex = await selectAction();
|
|
@@ -1329,23 +1578,23 @@ const main = async () => {
|
|
|
1329
1578
|
switch (selectedIndex) {
|
|
1330
1579
|
case 0: {
|
|
1331
1580
|
const newLabel2 = await getNewLabel();
|
|
1332
|
-
createLabel(configs, newLabel2);
|
|
1581
|
+
await createLabel(configs, newLabel2);
|
|
1333
1582
|
firstStart = firstStart && false;
|
|
1334
1583
|
break;
|
|
1335
1584
|
}
|
|
1336
1585
|
case 1: {
|
|
1337
|
-
createLabels(configs);
|
|
1586
|
+
await createLabels(configs);
|
|
1338
1587
|
firstStart = firstStart && false;
|
|
1339
1588
|
break;
|
|
1340
1589
|
}
|
|
1341
1590
|
case 2: {
|
|
1342
1591
|
const targetLabel = await getTargetLabel();
|
|
1343
|
-
deleteLabel(configs, targetLabel);
|
|
1592
|
+
await deleteLabel(configs, targetLabel);
|
|
1344
1593
|
firstStart = firstStart && false;
|
|
1345
1594
|
break;
|
|
1346
1595
|
}
|
|
1347
1596
|
case 3: {
|
|
1348
|
-
deleteLabels(configs);
|
|
1597
|
+
await deleteLabels(configs);
|
|
1349
1598
|
firstStart = firstStart && false;
|
|
1350
1599
|
break;
|
|
1351
1600
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hyouji",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
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": {
|
|
@@ -35,6 +35,12 @@
|
|
|
35
35
|
"test:coverage": "vitest run --coverage",
|
|
36
36
|
"test:lint": "eslint src --ext .ts",
|
|
37
37
|
"test:prettier": "prettier \"src/**/*.ts\" --list-different",
|
|
38
|
+
"test:error-handling": "node tests/scripts/error-handling/run-all.cjs",
|
|
39
|
+
"test:config": "node tests/scripts/config/run-all.cjs",
|
|
40
|
+
"test:integration": "node tests/scripts/run-integration.cjs",
|
|
41
|
+
"test:auto-detection": "node tests/integration/auto-detection/integration-flow.cjs",
|
|
42
|
+
"test:verification": "node tests/scripts/verification/run-all.cjs",
|
|
43
|
+
"test:all-custom": "npm run test:error-handling && npm run test:config && npm run test:integration && npm run test:verification",
|
|
38
44
|
"check-cli": "run-s test diff-integration-tests check-integration-tests",
|
|
39
45
|
"check-integration-tests": "run-s check-integration-test:*",
|
|
40
46
|
"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.'",
|