@jixoai/gh-runner 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.mjs CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { arch, homedir, platform } from "node:os";
3
3
  import { mkdir, open, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
4
- import { execFile, spawn } from "node:child_process";
5
4
  import { createTray } from "opentray";
6
5
  import { attachDialog } from "@opentray/ext-dialog";
6
+ import { attachOpener } from "@opentray/ext-opener";
7
7
  import { dirname, isAbsolute, join } from "node:path";
8
8
  import { createWriteStream, existsSync } from "node:fs";
9
+ import { execFile, spawn } from "node:child_process";
9
10
  import { promisify } from "node:util";
10
11
  import { Readable } from "node:stream";
11
12
  import { pipeline } from "node:stream/promises";
@@ -223,21 +224,6 @@ function defaultSearchDirs() {
223
224
  if (IS_WIN) return ["C:\\actions-runner", homedir()];
224
225
  return [homedir()];
225
226
  }
226
- /** 在系统文件管理器中揭示目录/文件。 */
227
- function revealInFileManager(path) {
228
- if (PLATFORM === "darwin") return {
229
- file: "open",
230
- args: ["-R", path]
231
- };
232
- if (IS_WIN) return {
233
- file: "explorer",
234
- args: [`/select,${path}`]
235
- };
236
- return {
237
- file: "xdg-open",
238
- args: [path]
239
- };
240
- }
241
227
  /** 平台路径分段(win32 反斜杠)。 */
242
228
  function basenameOf(dir) {
243
229
  const parts = dir.split(/[\\/]+/).filter(Boolean);
@@ -461,14 +447,23 @@ async function stopProcessGroup(pid, graceMs = 8e3) {
461
447
  // src/config.ts
462
448
  const DEFAULT_SEARCH_DIRS = defaultSearchDirs();
463
449
  function configDir() {
450
+ if (process.platform === "win32") {
451
+ const appData = process.env.APPDATA ?? join(homedir(), "AppData", "Roaming");
452
+ return join(appData, "gh-runner");
453
+ }
464
454
  return join(homedir(), "Library", "Application Support", "gh-runner");
465
455
  }
466
456
  function configPath() {
467
457
  return join(configDir(), "config.json");
468
458
  }
469
- /** 形状校验:损坏的实例项直接丢弃(手编配置/撕裂写的容错),不让 boot 崩掉。 */
459
+ /** 形状校验:损坏的实例项直接丢弃(手编配置/撕裂写的容错),不让 boot 崩掉。
460
+ * 路径合法性平台无关(POSIX 绝对 / win32 盘符 / UNC 均可)——config 是本机产物,
461
+ * 但 Windows 盘符路径曾被 startsWith("/") 全量误杀并回写空清单(v0.2.2 实证事故)。 */
462
+ function isAbsoluteish(p) {
463
+ return p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p) || p.startsWith("\\\\");
464
+ }
470
465
  function validInstance(item) {
471
- return typeof item === "object" && item !== null && typeof item.dir === "string" && item.dir.startsWith("/") && typeof item.id === "string";
466
+ return typeof item === "object" && item !== null && typeof item.dir === "string" && isAbsoluteish(item.dir) && typeof item.id === "string";
472
467
  }
473
468
  async function loadConfig() {
474
469
  try {
@@ -1226,6 +1221,7 @@ var UiHost = class {
1226
1221
  webviewTray;
1227
1222
  onOpenDiag;
1228
1223
  pickPackageFile;
1224
+ openExternal;
1229
1225
  #window = null;
1230
1226
  #lastSnapshot = null;
1231
1227
  #ackedSeq = 0;
@@ -1237,11 +1233,12 @@ var UiHost = class {
1237
1233
  /** 单飞守卫:hello 全量重推与去抖推送并发交错会让同一批日志行重复送达页面。 */
1238
1234
  #pushInFlight = false;
1239
1235
  #pushPending = false;
1240
- constructor(fleet, webviewTray, onOpenDiag, pickPackageFile) {
1236
+ constructor(fleet, webviewTray, onOpenDiag, pickPackageFile, openExternal) {
1241
1237
  this.fleet = fleet;
1242
1238
  this.webviewTray = webviewTray;
1243
1239
  this.onOpenDiag = onOpenDiag;
1244
1240
  this.pickPackageFile = pickPackageFile;
1241
+ this.openExternal = openExternal;
1245
1242
  }
1246
1243
  async open() {
1247
1244
  if (this.#window) {
@@ -1431,6 +1428,13 @@ var UiHost = class {
1431
1428
  case "open-diag":
1432
1429
  this.onOpenDiag(cmd.id);
1433
1430
  break;
1431
+ case "open-url":
1432
+ try {
1433
+ await this.openExternal(cmd.url);
1434
+ } catch (err) {
1435
+ console.error("[gh-runner] 打开链接失败:", err);
1436
+ }
1437
+ break;
1434
1438
  case "register":
1435
1439
  await this.fleet.register({
1436
1440
  dir: cmd.dir,
@@ -1459,12 +1463,6 @@ function extendWebview(tray) {
1459
1463
  }
1460
1464
  // src/main.ts
1461
1465
  const APP_ID = "com.jixoai.gh-runner";
1462
- async function revealPath(path) {
1463
- const { file, args } = revealInFileManager(path);
1464
- await new Promise((resolve) => {
1465
- execFile(file, args, () => resolve());
1466
- });
1467
- }
1468
1466
  /** 导入本地分发包:系统文件选择框(ext-dialog;darwin NSOpenPanel / win32 对话框)。 */
1469
1467
  async function pickPackageFile(tray) {
1470
1468
  try {
@@ -1624,10 +1622,12 @@ async function main() {
1624
1622
  },
1625
1623
  quit: () => void shutdown()
1626
1624
  });
1627
- const uiHost = new UiHost(fleet, extendWebview(tray), (id) => {
1625
+ const webviewTray = extendWebview(tray);
1626
+ const opener = attachOpener(tray);
1627
+ const uiHost = new UiHost(fleet, webviewTray, (id) => {
1628
1628
  const view = fleet.view(id);
1629
- if (view) revealPath(`${view.dir}/_diag`);
1630
- }, () => pickPackageFile(tray));
1629
+ if (view) opener.revealInFolder(`${view.dir}/_diag`).catch((err) => console.error("[gh-runner] 揭示目录失败:", err));
1630
+ }, () => pickPackageFile(tray), (url) => opener.open(url));
1631
1631
  fleet.onSnapshot((snap) => {
1632
1632
  projection.update(snap);
1633
1633
  uiHost.push(snap);
@@ -126,6 +126,7 @@
126
126
  .field input:focus { outline: 2px solid color-mix(in srgb, var(--run) 55%, transparent); border-color: transparent; }
127
127
  .field .hint { font-size: 10.5px; color: var(--ink-2); }
128
128
  .field .hint a { color: var(--run); text-decoration: none; }
129
+ a.ext-link { cursor: pointer; }
129
130
  .pkg-row { display: flex; gap: 6px; }
130
131
  .pkg-row select {
131
132
  flex: 1; min-width: 0; font: inherit; padding: 5px 7px; border: 1px solid var(--line);
@@ -196,7 +197,7 @@
196
197
  <div class="field">
197
198
  <label>注册 Token(org 设置页生成,一次性)</label>
198
199
  <input name="token" type="password" placeholder="粘贴一次性注册 Token" required>
199
- <span class="hint">获取:<a href="https://github.com/organizations/jixoai/settings/actions/runners/new" target="_blank">org runners 新建页</a>(自动解包本机已有分发包)</span>
200
+ <span class="hint">获取:<a href="#" id="link-runner-new" data-url="https://github.com/organizations/jixoai/settings/actions/runners/new">org runners 新建页</a>(系统浏览器打开;自动解包本机已有分发包)</span>
200
201
  </div>
201
202
  <div class="field">
202
203
  <label>Labels</label>
@@ -329,6 +330,16 @@ function renderPackages(packages, download) {
329
330
  }
330
331
  }
331
332
 
333
+ document.querySelectorAll("a.ext-link").forEach((a) => {
334
+ a.addEventListener("click", (e) => {
335
+ e.preventDefault();
336
+ const url = a.dataset.url;
337
+ if (!url) return;
338
+ const ipc = navigator.opentray && navigator.opentray.ipc;
339
+ if (ipc) void ipc.postMessage({ type: "open-url", url });
340
+ else window.open(url, "_blank");
341
+ });
342
+ });
332
343
  $("#pkg-download").addEventListener("click", () => send({ type: "download-latest" }));
333
344
  $("#pkg-import").addEventListener("click", () => send({ type: "import-package" }));
334
345
  document.querySelector('#register-form input[name="labels"]').addEventListener("input", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jixoai/gh-runner",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Menu-bar (tray) manager for multiple GitHub Actions self-hosted runners: discover, register from downloaded/imported packages, start/stop, and live job-result analysis. macOS & Windows.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -45,6 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@opentray/ext-dialog": "^0.36.0",
48
+ "@opentray/ext-opener": "^0.36.0",
48
49
  "@opentray/ext-webview": "^0.36.0",
49
50
  "opentray": "^0.36.0"
50
51
  },