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.
Files changed (3) hide show
  1. package/README.md +28 -15
  2. package/dist/index.js +226 -51
  3. 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
- In terms of multiple labels, this script is using `label.js` to define name, color and description. The format is very simple.
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
- ```js
139
- module.exports = Object.freeze([
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
- const resp = await configs2.octokit.request(
274
- "POST /repos/{owner}/{repo}/labels",
275
- {
276
- owner: configs2.owner,
277
- repo: configs2.repo,
278
- name: label.name,
279
- color: label.color,
280
- description: label.description
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
- const status = resp.status;
284
- switch (status) {
285
- case 201:
286
- log$4(chalk.green(`${resp.status}: Created ${label.name}`));
287
- break;
288
- case 404:
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
- labels.forEach(async (label) => {
301
- createLabel(configs2, label);
302
- });
303
- log$4("Created all labels");
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
- const _CryptoUtils = class _CryptoUtils {
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
- return;
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
- return;
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
- return;
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
- return;
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
- return;
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
- const _GitRepositoryDetector = class _GitRepositoryDetector {
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 ((_a = validationResult.preservedData) == null ? void 0 : _a.owner) {
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 (((_b = validationResult.preservedData) == null ? void 0 : _b.owner) && validationResult.preservedData.owner !== response.owner) {
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
- await createLabel(configs, newLabel2);
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
- await createLabels(configs);
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
- await deleteLabel(configs, targetLabel);
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
- await deleteLabels(configs);
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.15",
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": ">=10"
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.4",
65
+ "@biomejs/biome": "2.3.11",
67
66
  "@types/node": "^24.10.0",
68
- "@vitest/coverage-v8": "^4.0.8",
69
- "@vitest/ui": "^4.0.8",
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.2.2",
73
- "vitest": "^4.0.8"
71
+ "vite": "^7.3.1",
72
+ "vitest": "^4.0.17"
74
73
  },
75
74
  "files": [
76
75
  "dist/**/*",