@pubinfo-pr/devtools 0.171.6 → 0.171.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.
@@ -0,0 +1,130 @@
1
+ import { B as withCtx, G as isRef, V as withDirectives, Y as ref, b as defineComponent, c as vModelText, f as computed, g as createElementBlock, h as createCommentVNode, it as toDisplayString, k as openBlock, m as createBlock, nt as normalizeClass, p as createBaseVNode, r as Transition, tt as unref, v as createTextVNode, y as createVNode } from "./index-BgqWVAMJ.js";
2
+ import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-D8E0syuh.js";
3
+ import { t as PanelGrids_default } from "./PanelGrids-F8O_nB5x.js";
4
+ var _hoisted_1 = { class: "flex flex-col gap-4 m-auto h-full max-w-400 w-full p-5 px-5 md:px-20 of-auto" };
5
+ var _hoisted_2 = { class: "flex flex-col gap-4" };
6
+ var _hoisted_3 = { class: "flex flex-col gap-1" };
7
+ var _hoisted_4 = { class: "flex flex-col gap-1" };
8
+ var _hoisted_5 = { class: "flex flex-col gap-1" };
9
+ var _hoisted_6 = { class: "flex flex-col gap-1" };
10
+ var _hoisted_7 = { class: "flex flex-col gap-1" };
11
+ var _hoisted_8 = { class: "flex items-center justify-end gap-3 mt-2 mb-6" };
12
+ var _hoisted_9 = ["disabled"];
13
+ var _hoisted_10 = { class: "flex items-center gap-1.5" };
14
+ var API_ROOT = "/__pubinfo_devtools_api";
15
+ var issue_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
16
+ name: "IssuePage",
17
+ __name: "issue",
18
+ setup(__props) {
19
+ const title = ref("");
20
+ const description = ref("");
21
+ const steps = ref("");
22
+ const expected = ref("");
23
+ const actual = ref("");
24
+ const submitting = ref(false);
25
+ const toastVisible = ref(false);
26
+ const toastMessage = ref("");
27
+ const toastType = ref("success");
28
+ let toastTimer = null;
29
+ function showToast(message, type = "success") {
30
+ if (toastTimer) clearTimeout(toastTimer);
31
+ toastMessage.value = message;
32
+ toastType.value = type;
33
+ toastVisible.value = true;
34
+ toastTimer = setTimeout(() => {
35
+ toastVisible.value = false;
36
+ }, 3e3);
37
+ }
38
+ async function submitIssue() {
39
+ submitting.value = true;
40
+ try {
41
+ const payload = {
42
+ title: title.value,
43
+ description: description.value,
44
+ steps: steps.value,
45
+ expected: expected.value,
46
+ actual: actual.value
47
+ };
48
+ const res = await fetch(`${API_ROOT}/issue`, {
49
+ method: "POST",
50
+ headers: { "Content-Type": "application/json" },
51
+ body: JSON.stringify(payload)
52
+ });
53
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
54
+ if ((await res.json()).success) showToast(`提交成功!`, "success");
55
+ else showToast("提交成功!感谢你的反馈。", "success");
56
+ resetForm();
57
+ } catch {
58
+ showToast("提交失败,请稍后重试。", "error");
59
+ } finally {
60
+ submitting.value = false;
61
+ }
62
+ }
63
+ function resetForm() {
64
+ title.value = "";
65
+ description.value = "";
66
+ steps.value = "";
67
+ expected.value = "";
68
+ actual.value = "";
69
+ }
70
+ const isFormValid = computed(() => title.value.trim().length > 0);
71
+ return (_ctx, _cache) => {
72
+ const _component_PanelGrids = PanelGrids_default;
73
+ return openBlock(), createBlock(_component_PanelGrids, { class: "h-screen w-full" }, {
74
+ default: withCtx(() => [createBaseVNode("div", _hoisted_1, [
75
+ _cache[10] || (_cache[10] = createBaseVNode("div", { class: "flex items-center gap-3 mt-6" }, [createBaseVNode("div", { class: "i-carbon-debug text-3xl text-orange" }), createBaseVNode("h1", { class: "text-2xl font-bold" }, " 提交 Bug Report ")], -1)),
76
+ _cache[11] || (_cache[11] = createBaseVNode("p", { class: "text-sm op50" }, " 在此填写 Bug 信息并提交。 ", -1)),
77
+ createVNode(Transition, { name: "toast" }, {
78
+ default: withCtx(() => [unref(toastVisible) ? (openBlock(), createElementBlock("div", {
79
+ key: 0,
80
+ class: normalizeClass(["fixed right-5 top-5 z-100 flex items-center gap-2 rounded-lg px-4 py-3 text-sm text-white shadow-lg", unref(toastType) === "success" ? "bg-green-6" : "bg-red-6"])
81
+ }, [createBaseVNode("div", { class: normalizeClass(unref(toastType) === "success" ? "i-carbon-checkmark-filled" : "i-carbon-warning-filled") }, null, 2), createTextVNode(" " + toDisplayString(unref(toastMessage)), 1)], 2)) : createCommentVNode("", true)]),
82
+ _: 1
83
+ }),
84
+ createBaseVNode("div", _hoisted_2, [
85
+ createBaseVNode("div", _hoisted_3, [_cache[5] || (_cache[5] = createBaseVNode("label", { class: "text-sm font-medium" }, [createTextVNode(" 标题 "), createBaseVNode("span", { class: "text-red" }, "*")], -1)), withDirectives(createBaseVNode("input", {
86
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => isRef(title) ? title.value = $event : null),
87
+ type: "text",
88
+ placeholder: "简要描述你遇到的问题",
89
+ class: "w-full rounded-lg border n-border-base n-bg-base px-3 py-2 text-sm outline-none transition focus:border-primary"
90
+ }, null, 512), [[vModelText, unref(title)]])]),
91
+ createBaseVNode("div", _hoisted_4, [_cache[6] || (_cache[6] = createBaseVNode("label", { class: "text-sm font-medium" }, "问题描述", -1)), withDirectives(createBaseVNode("textarea", {
92
+ "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => isRef(description) ? description.value = $event : null),
93
+ rows: "6",
94
+ placeholder: "详细描述你遇到的问题...",
95
+ class: "w-full rounded-lg border n-border-base n-bg-base px-3 py-2 text-sm outline-none transition resize-y focus:border-primary"
96
+ }, null, 512), [[vModelText, unref(description)]])]),
97
+ createBaseVNode("div", _hoisted_5, [_cache[7] || (_cache[7] = createBaseVNode("label", { class: "text-sm font-medium" }, "复现步骤", -1)), withDirectives(createBaseVNode("textarea", {
98
+ "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => isRef(steps) ? steps.value = $event : null),
99
+ rows: "6",
100
+ placeholder: "1. 打开页面\n2. 点击按钮\n3. ...",
101
+ class: "w-full rounded-lg border n-border-base n-bg-base px-3 py-2 text-sm outline-none transition resize-y focus:border-primary"
102
+ }, null, 512), [[vModelText, unref(steps)]])]),
103
+ createBaseVNode("div", _hoisted_6, [_cache[8] || (_cache[8] = createBaseVNode("label", { class: "text-sm font-medium" }, "期望行为", -1)), withDirectives(createBaseVNode("textarea", {
104
+ "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => isRef(expected) ? expected.value = $event : null),
105
+ rows: "4",
106
+ placeholder: "你期望发生什么...",
107
+ class: "w-full rounded-lg border n-border-base n-bg-base px-3 py-2 text-sm outline-none transition resize-y focus:border-primary"
108
+ }, null, 512), [[vModelText, unref(expected)]])]),
109
+ createBaseVNode("div", _hoisted_7, [_cache[9] || (_cache[9] = createBaseVNode("label", { class: "text-sm font-medium" }, "实际行为", -1)), withDirectives(createBaseVNode("textarea", {
110
+ "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => isRef(actual) ? actual.value = $event : null),
111
+ rows: "4",
112
+ placeholder: "实际上发生了什么...",
113
+ class: "w-full rounded-lg border n-border-base n-bg-base px-3 py-2 text-sm outline-none transition resize-y focus:border-primary"
114
+ }, null, 512), [[vModelText, unref(actual)]])])
115
+ ]),
116
+ createBaseVNode("div", _hoisted_8, [createBaseVNode("button", {
117
+ disabled: !unref(isFormValid) || unref(submitting),
118
+ class: "rounded-lg bg-orange px-4 py-2 text-sm text-white font-medium transition hover:bg-orange-6 disabled:cursor-not-allowed disabled:op50",
119
+ onClick: submitIssue
120
+ }, [createBaseVNode("span", _hoisted_10, [createBaseVNode("div", { class: normalizeClass(unref(submitting) ? "i-carbon-circle-dash animate-spin" : "i-carbon-send-alt") }, null, 2), createTextVNode(" " + toDisplayString(unref(submitting) ? "提交中..." : "提交"), 1)])], 8, _hoisted_9), createBaseVNode("button", {
121
+ class: "rounded-lg border n-border-base px-4 py-2 text-sm transition hover:bg-active",
122
+ onClick: resetForm
123
+ }, " 重置 ")])
124
+ ])]),
125
+ _: 1
126
+ });
127
+ };
128
+ }
129
+ }), [["__scopeId", "data-v-20b6063c"]]);
130
+ export { issue_default as default };
@@ -1,6 +1,7 @@
1
- import { J as ref, K as reactive, L as watch, O as openBlock, _ as createTextVNode, d as computed, et as unref, f as createBaseVNode, g as createStaticVNode, h as createElementBlock, j as renderSlot, l as Fragment, m as createCommentVNode, p as createBlock, q as readonly, rt as toDisplayString, t as __vitePreload, v as createVNode, y as defineComponent, z as withCtx } from "./index-BxhNhfma.js";
2
- import { a as usePackageVersion$1, i as overviewFetch, m as createEventHook, o as Badge_default, s as _plugin_vue_export_helper_default } from "./fetch-CC6_FGnT.js";
3
- import { t as PanelGrids_default } from "./PanelGrids-D1Mem7DG.js";
1
+ import { B as withCtx, J as readonly, M as renderSlot, R as watch, Y as ref, _ as createStaticVNode, b as defineComponent, f as computed, g as createElementBlock, h as createCommentVNode, it as toDisplayString, k as openBlock, m as createBlock, p as createBaseVNode, q as reactive, t as __vitePreload, tt as unref, u as Fragment, v as createTextVNode, y as createVNode } from "./index-BgqWVAMJ.js";
2
+ import { a as usePackageVersion$1, i as overviewFetch, o as Badge_default, p as createEventHook } from "./fetch-fetJJUYt.js";
3
+ import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-D8E0syuh.js";
4
+ import { t as PanelGrids_default } from "./PanelGrids-F8O_nB5x.js";
4
5
  function usePackageVersion(packageName) {
5
6
  const info = ref(null);
6
7
  const isLoading = ref(false);
@@ -273,15 +274,7 @@ var pages_default = /* @__PURE__ */ defineComponent({
273
274
  createBaseVNode("div", _hoisted_17, toDisplayString(unref(formatDuration$1)(unref(timings).hmr)), 1)
274
275
  ])])
275
276
  ]),
276
- _cache[13] || (_cache[13] = createBaseVNode("div", { class: "flex flex-wrap gap-6 mt-5 items-center justify-center" }, [createBaseVNode("a", {
277
- href: "https://github.com/nuxt/devtools/issues",
278
- target: "_blank",
279
- flex: "~ gap1",
280
- "items-center": "",
281
- op50: "",
282
- hover: "op100 text-rose",
283
- transition: ""
284
- }, [createBaseVNode("div", { "i-carbon-debug": "" }), createTextVNode(" Bug Reports ")])], -1)),
277
+ _cache[13] || (_cache[13] = createBaseVNode("div", { class: "flex flex-wrap gap-6 mt-5 items-center justify-center" }, null, -1)),
285
278
  _cache[14] || (_cache[14] = createBaseVNode("div", { "flex-auto": "" }, null, -1))
286
279
  ])]),
287
280
  _: 1
@@ -1,8 +1,9 @@
1
1
  const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./javascript-DIagQh28.js","./javascript-CZLwsMB1.js","./typescript-CzrZyZmM.js","./typescript-DkhbmgdN.js","./vue-Co3JBzx2.js","./css-DbgpCiOq.js","./html-mHHj5Mh3.js","./json-CzPoZe0r.js","./json-WgRhEAOB.js","./html-C5tHi8hG.js","./css-D-vtiAqw.js"])))=>i.map(i=>d[i]);
2
- import { A as renderList, B as withDirectives, D as onMounted, E as onBeforeUnmount, I as useSlots, J as ref, K as reactive, L as watch, M as resolveComponent, N as resolveDirective, O as openBlock, P as resolveDynamicComponent, Q as toRef, R as watchEffect, S as inject, T as nextTick, W as isRef, X as shallowRef, _ as createTextVNode, b as getCurrentInstance, c as withKeys, d as computed, et as unref, f as createBaseVNode, h as createElementBlock, i as vModelCheckbox, j as renderSlot, k as provide, l as Fragment, m as createCommentVNode, nt as normalizeStyle, p as createBlock, r as Transition, rt as toDisplayString, s as vModelSelect, t as __vitePreload, tt as normalizeClass, u as KeepAlive, v as createVNode, w as mergeProps, x as h, y as defineComponent, z as withCtx } from "./index-BxhNhfma.js";
3
- import { a as Icon_default, i as SectionBlock_default, n as TextInput_default, r as Button_default, t as Navbar_default } from "./Navbar-L7rRcdIR.js";
4
- import { c as createReusableTemplate, d as useElementSize, f as useLocalStorage, h as createSharedComposable, l as onClickOutside, o as Badge_default, p as useVModel, s as _plugin_vue_export_helper_default, t as apiListFetch } from "./fetch-CC6_FGnT.js";
5
- import { t as PanelGrids_default } from "./PanelGrids-D1Mem7DG.js";
2
+ import { $ as toRef, A as provide, B as withCtx, C as inject, D as onBeforeUnmount, E as nextTick, F as resolveDynamicComponent, G as isRef, L as useSlots, M as renderSlot, N as resolveComponent, O as onMounted, P as resolveDirective, R as watch, S as h, T as mergeProps, V as withDirectives, Y as ref, Z as shallowRef, b as defineComponent, d as KeepAlive, f as computed, g as createElementBlock, h as createCommentVNode, i as vModelCheckbox, it as toDisplayString, j as renderList, k as openBlock, l as withKeys, m as createBlock, nt as normalizeClass, p as createBaseVNode, q as reactive, r as Transition, rt as normalizeStyle, s as vModelSelect, t as __vitePreload, tt as unref, u as Fragment, v as createTextVNode, x as getCurrentInstance, y as createVNode, z as watchEffect } from "./index-BgqWVAMJ.js";
3
+ import { a as Icon_default, i as SectionBlock_default, n as TextInput_default, r as Button_default, t as Navbar_default } from "./Navbar-D84j2-0V.js";
4
+ import { c as onClickOutside, d as useLocalStorage, f as useVModel, m as createSharedComposable, o as Badge_default, s as createReusableTemplate, t as apiListFetch, u as useElementSize } from "./fetch-fetJJUYt.js";
5
+ import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-D8E0syuh.js";
6
+ import { t as PanelGrids_default } from "./PanelGrids-F8O_nB5x.js";
6
7
  var _sfc_main = {};
7
8
  var _hoisted_1$10 = { class: "n-card n-card-base" };
8
9
  function _sfc_render(_ctx, _cache) {
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <link rel="icon" href="/favicon.svg" type="image/svg+xml">
7
7
  <title>Vite Plugins</title>
8
- <script type="module" crossorigin src="./assets/index-BxhNhfma.js"></script>
9
- <link rel="stylesheet" crossorigin href="./assets/index-B9Onnz0R.css">
8
+ <script type="module" crossorigin src="./assets/index-BgqWVAMJ.js"></script>
9
+ <link rel="stylesheet" crossorigin href="./assets/index-XXyHuz_l.css">
10
10
  </head>
11
11
  <body data-vite-inspect-mode="DEV" class="n-bg-base text-color-base font-sans">
12
12
  <div id="app"></div>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 14 14"><!-- Icon from Flex color icons by Streamline - https://creativecommons.org/licenses/by/4.0/ --><g fill="none"><path fill="#d7e0ff" d="M.797 6.75c0-3.697 1.55-5.282 2.068-5.81h8.27c.517.528 2.068 2.113 2.068 5.81c0 4.224-4.073 5.782-6.14 6.31C4.994 12.532.796 10.974.796 6.749"/><path stroke="#4147d5" stroke-linecap="round" stroke-linejoin="round" d="M.797 6.75c0-3.697 1.55-5.282 2.068-5.81h8.27c.517.528 2.068 2.113 2.068 5.81c0 4.224-4.073 5.782-6.14 6.31C4.994 12.532.796 10.974.796 6.749"/><path fill="#fff" d="M6.999 9.42c1.521 0 2.377-.856 2.377-2.377c0-1.522-.856-2.377-2.377-2.377s-2.377.855-2.377 2.377c0 1.52.856 2.376 2.377 2.376"/><path stroke="#4147d5" stroke-linecap="round" stroke-linejoin="round" d="M6.999 9.42c1.521 0 2.377-.856 2.377-2.377c0-1.522-.856-2.377-2.377-2.377s-2.377.855-2.377 2.377c0 1.52.856 2.376 2.377 2.376"/><path stroke="#4147d5" stroke-linecap="round" stroke-linejoin="round" d="M9.095 5.74a1.83 1.83 0 0 1 1.39-.634M9.376 6.873h1.108m-6.968 0h1.108m.281-1.133a1.83 1.83 0 0 0-1.39-.634m5.58 3.234a1.83 1.83 0 0 0 1.39.633m-5.58-.633a1.83 1.83 0 0 1-1.39.633m4.27-4.217c.202-.151.37-.37.487-.633s.178-.562.178-.866M6.206 4.756a1.6 1.6 0 0 1-.487-.633a2.15 2.15 0 0 1-.178-.866"/></g></svg>
package/dist/index.mjs CHANGED
@@ -1,9 +1,13 @@
1
1
  import { t as DIR_CLIENT } from "./dirs2.mjs";
2
+ import { Buffer } from "node:buffer";
2
3
  import sirv from "sirv";
3
4
  import { basename, dirname, extname, join, relative } from "node:path";
4
5
  import { pathToFileURL } from "node:url";
5
6
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
6
7
  import { readFile, readdir, stat } from "node:fs/promises";
8
+ import { execSync } from "node:child_process";
9
+ import { arch, platform, release } from "node:os";
10
+ import process from "node:process";
7
11
  import { Lang, parse } from "@ast-grep/napi";
8
12
 
9
13
  //#region src/server/controller/api.ts
@@ -486,6 +490,132 @@ async function getRequestList(root) {
486
490
  };
487
491
  }
488
492
 
493
+ //#endregion
494
+ //#region src/server/controller/issue.ts
495
+ /**
496
+ * 安全执行 git 命令,失败时返回 null
497
+ */
498
+ function execGit(command, cwd) {
499
+ try {
500
+ return execSync(command, {
501
+ cwd,
502
+ encoding: "utf-8",
503
+ timeout: 5e3
504
+ }).trim() || null;
505
+ } catch {
506
+ return null;
507
+ }
508
+ }
509
+ /**
510
+ * 采集 Git 信息
511
+ */
512
+ function collectGitInfo(root) {
513
+ return {
514
+ userName: execGit("git config user.name", root),
515
+ userEmail: execGit("git config user.email", root),
516
+ branch: execGit("git rev-parse --abbrev-ref HEAD", root),
517
+ commitHash: execGit("git rev-parse --short HEAD", root),
518
+ remoteUrl: execGit("git remote get-url origin", root)
519
+ };
520
+ }
521
+ /**
522
+ * 读取项目 package.json
523
+ */
524
+ function collectPackageJson(root) {
525
+ try {
526
+ const pkgPath = join(root, "package.json");
527
+ if (!existsSync(pkgPath)) return {};
528
+ return JSON.parse(readFileSync(pkgPath, "utf-8"));
529
+ } catch {
530
+ return {};
531
+ }
532
+ }
533
+ /**
534
+ * 采集完整的环境信息
535
+ */
536
+ function getEnvironmentInfo(root) {
537
+ return {
538
+ nodeVersion: process.version,
539
+ platform: platform(),
540
+ arch: arch(),
541
+ osVersion: release(),
542
+ packageJson: collectPackageJson(root),
543
+ git: collectGitInfo(root)
544
+ };
545
+ }
546
+ /**
547
+ * 将环境信息格式化为 Markdown 文本,用于 Issue body
548
+ */
549
+ function formatEnvironmentMarkdown(env) {
550
+ const envObj = {
551
+ node: env.nodeVersion,
552
+ os: `${env.platform} ${env.arch} (${env.osVersion})`,
553
+ git: {
554
+ branch: env.git.branch,
555
+ commit: env.git.commitHash,
556
+ user: env.git.userName ? `${env.git.userName}${env.git.userEmail ? ` <${env.git.userEmail}>` : ""}` : null,
557
+ remote: env.git.remoteUrl
558
+ }
559
+ };
560
+ const lines = [];
561
+ lines.push(`### 环境信息`);
562
+ lines.push("");
563
+ lines.push("```json");
564
+ lines.push(JSON.stringify(envObj, null, 2));
565
+ lines.push("```");
566
+ lines.push("");
567
+ lines.push(`### package.json`);
568
+ lines.push("");
569
+ lines.push("```json");
570
+ lines.push(JSON.stringify(env.packageJson, null, 2));
571
+ lines.push("```");
572
+ return lines.join("\n");
573
+ }
574
+ /**
575
+ * 将表单数据格式化为 Issue body(Markdown)
576
+ */
577
+ function formatIssueBody(body, environment) {
578
+ const sections = [];
579
+ sections.push(`## 问题描述`);
580
+ sections.push(body.description || "_未填写_");
581
+ sections.push("");
582
+ if (body.steps) {
583
+ sections.push(`## 复现步骤`);
584
+ sections.push(body.steps);
585
+ sections.push("");
586
+ }
587
+ if (body.expected) {
588
+ sections.push(`## 期望行为`);
589
+ sections.push(body.expected);
590
+ sections.push("");
591
+ }
592
+ if (body.actual) {
593
+ sections.push(`## 实际行为`);
594
+ sections.push(body.actual);
595
+ sections.push("");
596
+ }
597
+ sections.push(formatEnvironmentMarkdown(environment));
598
+ return sections.join("\n");
599
+ }
600
+ /**
601
+ * 处理 Issue 提交,采集环境信息后转发到 issue.elonehoo.cn
602
+ */
603
+ async function submitIssue(root, body) {
604
+ const issueBody = formatIssueBody(body, getEnvironmentInfo(root));
605
+ const response = await fetch("https://issue.elonehoo.cn/issue", {
606
+ method: "POST",
607
+ headers: { "Content-Type": "application/json" },
608
+ body: JSON.stringify({
609
+ title: `[Bug] ${body.title}`,
610
+ body: issueBody
611
+ })
612
+ });
613
+ if (!response.ok) throw new Error(`Issue API responded with ${response.status}: ${response.statusText}`);
614
+ const result = await response.json();
615
+ if (!result.success) throw new Error("Issue API returned success: false");
616
+ return result;
617
+ }
618
+
489
619
  //#endregion
490
620
  //#region src/@data/pubinfo.ts
491
621
  const pubinfoImport = [
@@ -2343,6 +2473,31 @@ function pubinfoDevtools() {
2343
2473
  }
2344
2474
  return;
2345
2475
  }
2476
+ if (req.url === "/environment-info") {
2477
+ try {
2478
+ sendJSON(res, getEnvironmentInfo(server.config.root));
2479
+ } catch (error) {
2480
+ sendError(res, "Failed to get environment info", error);
2481
+ }
2482
+ return;
2483
+ }
2484
+ if (req.url === "/issue" && req.method === "POST") {
2485
+ try {
2486
+ const chunks = [];
2487
+ req.on("data", (chunk) => chunks.push(chunk));
2488
+ req.on("end", async () => {
2489
+ try {
2490
+ const body = JSON.parse(Buffer.concat(chunks).toString());
2491
+ sendJSON(res, await submitIssue(server.config.root, body));
2492
+ } catch (error) {
2493
+ sendError(res, "Failed to submit issue", error);
2494
+ }
2495
+ });
2496
+ } catch (error) {
2497
+ sendError(res, "Failed to submit issue", error);
2498
+ }
2499
+ return;
2500
+ }
2346
2501
  next();
2347
2502
  });
2348
2503
  }
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * - UnoCSS:直接复用 Uno 的内置面板(iframe 方式)。
6
6
  * - Server Router:本项目提供的服务路由调试面板。
7
+ * - Issue:快速提交 Bug Report 到 GitHub。
7
8
  */
8
9
  declare function setupDevtoolsPanel(): void;
9
10
  //#endregion
@@ -3845,6 +3845,27 @@ function setupImportPanel() {
3845
3845
  });
3846
3846
  }
3847
3847
 
3848
+ //#endregion
3849
+ //#region src/panel/issue.ts
3850
+ /**
3851
+ * 注册 Issue 提交面板。
3852
+ *
3853
+ * 在 DevTools 中提供一个快速提交 Bug Report 的入口,
3854
+ * 用户可以填写信息后跳转到 GitHub 创建 Issue。
3855
+ */
3856
+ function setupIssuePanel() {
3857
+ addCustomTab({
3858
+ name: "pubinfo-issue",
3859
+ title: "提交 Bug",
3860
+ icon: "/__pubinfo_devtools/issue.svg",
3861
+ view: {
3862
+ type: "iframe",
3863
+ src: "/__pubinfo_devtools/#/issue"
3864
+ },
3865
+ category: "advanced"
3866
+ });
3867
+ }
3868
+
3848
3869
  //#endregion
3849
3870
  //#region src/panel/overview.ts
3850
3871
  /**
@@ -3911,6 +3932,7 @@ function setupUnoCSSPanel() {
3911
3932
  *
3912
3933
  * - UnoCSS:直接复用 Uno 的内置面板(iframe 方式)。
3913
3934
  * - Server Router:本项目提供的服务路由调试面板。
3935
+ * - Issue:快速提交 Bug Report 到 GitHub。
3914
3936
  */
3915
3937
  function setupDevtoolsPanel() {
3916
3938
  setupOverviewPanel();
@@ -3919,6 +3941,7 @@ function setupDevtoolsPanel() {
3919
3941
  setupComponentPanel();
3920
3942
  setupImportPanel();
3921
3943
  setupIconesPanel();
3944
+ setupIssuePanel();
3922
3945
  }
3923
3946
 
3924
3947
  //#endregion
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pubinfo-pr/devtools",
3
3
  "type": "module",
4
- "version": "0.171.6",
4
+ "version": "0.171.7",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.mts",