From 0b2b5c0d1ae4dfbca55d0c27bb6ebbbd383d1e42 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Thu, 16 Jul 2026 22:16:36 +0900 Subject: [PATCH 1/4] Improve English feature request template --- .../ISSUE_TEMPLATE/13_feature_request_en.yaml | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/13_feature_request_en.yaml b/.github/ISSUE_TEMPLATE/13_feature_request_en.yaml index b3fe5bf5f..473609b78 100644 --- a/.github/ISSUE_TEMPLATE/13_feature_request_en.yaml +++ b/.github/ISSUE_TEMPLATE/13_feature_request_en.yaml @@ -8,6 +8,10 @@ body: - type: markdown attributes: value: | + Use this form only for a capability that does not exist yet. + + If something does not work, stopped working, or behaves differently from what you expected, please use the Bug Report template instead. Browser and ScriptCat versions are required here because they help maintainers determine whether the request is actually a version-specific bug or an unavailable feature. + Please describe the real problem you want to solve, not just a button name or an API name. If this is a GM/CAT API design request, please provide: @@ -26,6 +30,35 @@ body: - label: I confirm this is not a bug. If this is unexpected behavior, I should use the Bug Report template. required: true + - type: input + id: browser-version + attributes: + label: Browser and Version + description: Specify the browser where you need this feature, including its version. List all affected browsers if applicable. + placeholder: "Example: Chrome 138.0.7204.101; Firefox 140.0.2" + validations: + required: true + + - type: input + id: scriptcat-version + attributes: + label: ScriptCat Version + description: Find this in ScriptCat's extension details or About page. Do not write only "latest". + placeholder: "Example: v0.17.0 or commit/build identifier" + validations: + required: true + + - type: dropdown + id: expected-existing-behavior + attributes: + label: Did You Expect This to Already Work? + description: This helps distinguish a missing feature from a bug or regression. + options: + - No. I am requesting a completely new capability. + - Yes. I expected ScriptCat to support this already; I have explained why this is not a bug below. + validations: + required: true + - type: dropdown id: feature-area attributes: @@ -48,6 +81,18 @@ body: validations: required: true + - type: textarea + id: current-behavior + attributes: + label: Current Behavior and What You Tried + description: Describe what ScriptCat currently does, what you tried, and why the existing workflow cannot achieve your goal. If something is failing or used to work, use the Bug Report template instead. + placeholder: | + Current behavior: + Steps or workarounds tried: + Why the current behavior is insufficient: + validations: + required: true + - type: textarea id: problem attributes: From be3c1bd075e89c350ee1e830d7a68fef914a4816 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Thu, 16 Jul 2026 22:17:07 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E8=AF=B7=E6=B1=82=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ISSUE_TEMPLATE/03_feature_request.yaml | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/03_feature_request.yaml b/.github/ISSUE_TEMPLATE/03_feature_request.yaml index 738e12d6d..7ef413856 100644 --- a/.github/ISSUE_TEMPLATE/03_feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/03_feature_request.yaml @@ -8,6 +8,10 @@ body: - type: markdown attributes: value: | + 本模板仅用于提交尚不存在的新能力。 + + 如果某个功能无法使用、以前可以使用但现在失效,或行为与你的预期不一致,请改用 Bug 模板。即使是功能请求,也必须填写浏览器和 ScriptCat 版本,以便维护者判断这是版本相关的 bug,还是确实缺少功能。 + 请描述你真正想解决的问题,而不只是描述一个按钮或 API 名称。 如果是 GM/CAT API 设计,建议给出 API 草案、兼容对象和安全影响。 @@ -21,6 +25,35 @@ body: - label: 我已经确认这不是 bug;如果是异常行为,应使用 Bug 模板 required: true + - type: input + id: browser-version + attributes: + label: 浏览器及版本 + description: 请填写需要此功能的浏览器及其完整版本;如涉及多个浏览器,请全部列出。 + placeholder: "例如:Chrome 138.0.7204.101;Firefox 140.0.2" + validations: + required: true + + - type: input + id: scriptcat-version + attributes: + label: ScriptCat 版本 + description: 可在扩展详情或关于页面查看,请不要只填写“最新版”。 + placeholder: "例如:v0.17.0 或 commit/build 标识" + validations: + required: true + + - type: dropdown + id: expected-existing-behavior + attributes: + label: 你是否原本认为这个功能已经存在? + description: 此项用于区分缺少新功能、现有功能异常和回归问题。 + options: + - 否,我请求的是一个全新的能力 + - 是,我原本认为 ScriptCat 已支持;我会在下方说明为什么这不是 bug + validations: + required: true + - type: dropdown id: feature-area attributes: @@ -43,6 +76,18 @@ body: validations: required: true + - type: textarea + id: current-behavior + attributes: + label: 当前行为和已尝试的方法 + description: 请说明 ScriptCat 当前的行为、你尝试过的方法,以及现有流程为什么无法满足需求。如果某项操作失败或以前可以使用,请改用 Bug 模板。 + placeholder: | + 当前行为: + 已尝试的步骤或替代方案: + 现有行为无法满足需求的原因: + validations: + required: true + - type: textarea id: problem attributes: From 0d35f50af8dc22c406df21c1d7f169e487ad9f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Mon, 20 Jul 2026 22:13:35 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=93=9D=20=E7=B2=BE=E7=AE=80=E5=85=A8?= =?UTF-8?q?=E9=83=A8=20Issue=20=E6=A8=A1=E6=9D=BF=EF=BC=8C=E9=99=8D?= =?UTF-8?q?=E4=BD=8E=E5=8F=8D=E9=A6=88=E9=97=A8=E6=A7=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 功能请求模板回退浏览器/ScriptCat 版本等必填环境字段:新功能与版本、 浏览器无关,误报的 bug 应引导去 Bug 模板,而不是让所有人多填几栏。 顶部只保留一句「功能失效/以前能用现在不行请改用 Bug 模板」的提示。 同时精简其余模板: - 删除全部分类下拉框(问题类型/功能领域/文档领域/翻译类型/涉及模块等), 这些由维护者打 label 更准,对提交者只是负担 - Bug 模板的渠道/Manifest/系统/设备类型/浏览器状态合并为一个 browser 字段 - 各模板的可选长尾字段合并为一个「补充信息」 保留 issues/new 预填链接的契约:模板文件名不变,popup「报告问题」用到的 scriptcat-version、browser 两个 id 保留,已安装的旧版本预填同样不受影响。 必填项:Bug 11→5,功能请求 4→3,兼容性 9→7,文档 7→4,翻译 7→5, 技术方案 4→2;总行数 1583→772。中英文模板结构保持一一对应。 --- .github/ISSUE_TEMPLATE/01_bug_report.yaml | 158 ++-------------- .../02_userscript_compatibility.yaml | 81 ++------- .../ISSUE_TEMPLATE/03_feature_request.yaml | 110 +---------- .../ISSUE_TEMPLATE/04_technical_proposal.yaml | 43 +---- .github/ISSUE_TEMPLATE/05_documentation.yaml | 78 +------- .github/ISSUE_TEMPLATE/06_translation.yaml | 75 +------- .github/ISSUE_TEMPLATE/11_bug_report_en.yaml | 172 ++---------------- .../12_userscript_compatibility_en.yaml | 86 ++------- .../ISSUE_TEMPLATE/13_feature_request_en.yaml | 115 +----------- .../14_technical_proposal_en.yaml | 42 +---- .../ISSUE_TEMPLATE/15_documentation_en.yaml | 77 -------- .github/ISSUE_TEMPLATE/16_translation_en.yaml | 76 +------- 12 files changed, 107 insertions(+), 1006 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/01_bug_report.yaml b/.github/ISSUE_TEMPLATE/01_bug_report.yaml index f5fadcab1..9f9020f2d 100644 --- a/.github/ISSUE_TEMPLATE/01_bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/01_bug_report.yaml @@ -8,42 +8,17 @@ body: - type: markdown attributes: value: | - 感谢反馈。请尽量填写完整,尤其是 **ScriptCat 版本、浏览器版本、复现步骤、日志、相关脚本链接/导出文件**。 - 如果是某个用户脚本在 ScriptCat 不兼容,但在 Tampermonkey/Violentmonkey 正常,请改用 “Userscript Compatibility” 模板。 + 感谢反馈。请尽量写清复现步骤和版本信息。 + + 如果是某个用户脚本在 ScriptCat 不兼容、但在 Tampermonkey/Violentmonkey 正常,请改用 “Userscript 兼容性问题” 模板。 - type: checkboxes id: precheck attributes: label: 提交前检查 options: - - label: 我已经搜索过现有 issues,确认不是重复问题 - required: true - - label: 我已经尽量使用最新 stable 或 beta 版本测试 + - label: 我已经搜索过现有 issues,并尽量用最新 stable 或 beta 版本确认问题仍然存在 required: true - - label: 这不是安全漏洞;安全漏洞会通过 Security Advisory 私密提交 - required: true - - - type: dropdown - id: bug-area - attributes: - label: 问题类型 - multiple: true - options: - - 扩展安装 / 更新 / 卸载 - - 脚本安装 / 更新 / 导入 / 导出 - - 脚本执行 / 注入 / 匹配规则 - - GM API / CAT API - - 后台脚本 / 定时脚本 / offscreen - - Cloud Sync / 备份 / 恢复 / WebDAV / Google Drive / OneDrive - - Popup / Options 管理页 / UI - - 内置编辑器 / Monaco / ESLint - - 日志 / 调试 / 错误提示 - - 移动端 / 小屏适配 - - 隐身模式 / 多窗口 / 多标签页 - - 性能 / 内存 / 存储 - - 其他 - validations: - required: true - type: textarea id: summary @@ -52,14 +27,17 @@ body: description: 实际发生了什么?你原本期望发生什么? placeholder: | 例:在 Edge Android 上打开 ScriptCat popup 后点击“导入文件”,文件选择器出现但选择后没有任何反应。 + + 期望: + 实际: validations: required: true - type: textarea id: steps attributes: - label: 最小复现步骤 - description: 请写成别人可以照着操作的步骤。不要只写“1”“见图”“如题”。 + label: 复现步骤 + description: 请写成别人可以照着操作的步骤。 placeholder: | 1. 安装 ScriptCat v... 2. 打开 ... @@ -68,16 +46,6 @@ body: validations: required: true - - type: textarea - id: expected-actual - attributes: - label: 期望行为 vs 实际行为 - placeholder: | - 期望: - 实际: - validations: - required: true - - type: input id: scriptcat-version attributes: @@ -86,114 +54,22 @@ body: validations: required: true - - type: dropdown - id: release-channel - attributes: - label: ScriptCat 渠道 - options: - - Chrome Stable - - Chrome Beta - - Edge Stable - - Edge Beta - - Firefox Stable / MV2 - - Firefox Beta / MV2 - - GitHub Release 手动安装 - - 自行构建 / dev - - VSCode 插件 - - 不确定 - validations: - required: true - - - type: dropdown - id: manifest-mode - attributes: - label: Manifest / 扩展模式 - options: - - MV3 - - MV2 / Firefox - - 不确定 - validations: - required: true - - - type: input - id: os - attributes: - label: 操作系统 - placeholder: "例如:Windows 11 24H2 / macOS 15 / Android 16 / HarmonyOS 4.2 / iOS" - validations: - required: true - - type: input id: browser attributes: - label: 浏览器及版本 - placeholder: "例如:Chrome 143.0.7499.41 / Edge 146.0.3802.0 / Firefox 151.0b7 / Brave 1.92.120 / Cromite 142..." - validations: - required: true - - - type: dropdown - id: device-type - attributes: - label: 设备类型 - options: - - 桌面端 - - Android 手机/平板 - - iOS/iPadOS - - 其他 + label: 系统 / 浏览器及版本 + placeholder: "例如:Windows 11 + Chrome 143 / Android 16 + Edge 146 / macOS + Firefox 151" validations: required: true - - type: checkboxes - id: browser-flags - attributes: - label: 浏览器/扩展状态 - options: - - label: 开启了隐身模式 / InPrivate / Private Window - - label: 使用移动端浏览器扩展支持 - - label: 使用第三方 Chromium 浏览器 - - label: 使用第三方 Firefox/Gecko 浏览器 - - label: 已授予 ScriptCat 对目标网站的站点访问权限 - - label: 开启了开发者模式 - - label: 安装了其他可能影响页面/请求/脚本的扩展 - - - type: textarea - id: related-script - attributes: - label: 相关用户脚本 / 配置 - description: 如果问题和某个脚本有关,请提供安装链接、脚本头部 metadata、或 ScriptCat 导出的 zip。敏感信息请先脱敏。 - placeholder: | - 脚本链接: - 目标网站: - 关键 metadata: - ```js - // ==UserScript== - // @name ... - // @match ... - // @grant ... - // @connect ... - // ==/UserScript== - ``` - validations: - required: false - - - type: textarea - id: logs - attributes: - label: 日志 / 错误信息 / 截图 - description: | - 请说明日志来自哪里:页面 DevTools、扩展 Service Worker、offscreen.html、popup、options 页面、定时脚本日志等。 - placeholder: | - 日志来源: - 错误信息: - 截图/视频: - validations: - required: false - - type: textarea - id: workaround + id: details attributes: - label: 是否有临时解决办法 + label: 相关脚本 / 日志 / 截图 + description: 相关脚本的安装链接或 metadata、控制台或 Service Worker 日志、截图。敏感信息请先脱敏。 placeholder: | - 例:刷新页面后恢复;换成 Tampermonkey 正常;关闭隐身模式后正常;重新安装脚本后正常。 + 脚本链接 / 目标网站: + 日志(来源:页面 DevTools / Service Worker / offscreen / popup): + 截图: validations: required: false diff --git a/.github/ISSUE_TEMPLATE/02_userscript_compatibility.yaml b/.github/ISSUE_TEMPLATE/02_userscript_compatibility.yaml index 2d5c15db5..45bce4e08 100644 --- a/.github/ISSUE_TEMPLATE/02_userscript_compatibility.yaml +++ b/.github/ISSUE_TEMPLATE/02_userscript_compatibility.yaml @@ -9,19 +9,15 @@ body: attributes: value: | 这个模板专门用于 **用户脚本兼容性**。 - 请尽量提供脚本链接、目标网站、metadata、对比结果和最小复现脚本。 - 如果只是 ScriptCat 界面或同步功能异常,请使用 Bug 反馈模板。 + + 如果只是 ScriptCat 界面、同步、安装等本体功能异常,请改用 Bug 反馈模板。 - type: checkboxes id: precheck attributes: label: 提交前检查 options: - - label: 我已经搜索过现有 issues - required: true - - label: 我确认该问题与某个用户脚本或 userscript API 兼容性有关 - required: true - - label: 我已经尽量提供脚本链接或最小复现脚本 + - label: 我已经搜索过现有 issues,并确认问题与某个用户脚本或 userscript API 有关 required: true - type: input @@ -36,29 +32,17 @@ body: id: target-url attributes: label: 触发问题的目标网站 - placeholder: "例如: " validations: required: true - type: textarea id: problem attributes: - label: 在 ScriptCat 中的异常表现 + label: 问题表现与对比 + description: 在 ScriptCat 中是什么表现?在其他脚本管理器中又是什么表现?请注明对比用的管理器和版本。 placeholder: | - 例:按钮不出现 / 脚本不执行 / GM_xmlhttpRequest 报错 / 菜单项无效 / 页面无限刷新 - validations: - required: true - - - type: textarea - id: comparison - attributes: - label: 与其他用户脚本管理器的对比 - description: 请写清楚 Tampermonkey / Violentmonkey / FireMonkey / Greasemonkey 的结果。 - placeholder: | - Tampermonkey 版本: - Violentmonkey 版本: - FireMonkey/Greasemonkey 版本: - 在这些管理器中的表现: + 在 ScriptCat 中:按钮不出现 / 脚本不执行 / GM_xmlhttpRequest 报错 / 页面无限刷新 + 在其他管理器中(如 Tampermonkey 5.x):正常 validations: required: true @@ -78,41 +62,6 @@ body: validations: required: true - - type: textarea - id: minimal-reproduction - attributes: - label: 最小复现脚本 - description: 如果原脚本太大,请尽量删减成最小可复现版本。 - render: javascript - placeholder: | - // 尽量提供能单独复现问题的最小脚本 - validations: - required: false - - - type: dropdown - id: api-area - attributes: - label: 可能相关的 API / 机制 - multiple: true - options: - - GM_xmlhttpRequest / GM.xmlHttpRequest - - GM_getValue / GM.setValue / GM storage - - GM_registerMenuCommand - - GM_download - - GM_cookie / Cookie API - - "@match / @include / @exclude" - - "@connect" - - "@require / @resource" - - "@run-at" - - "@inject-into" - - sandbox / unsafeWindow / page context - - iframe / allFrames / noframes - - CSP / Trusted Types - - service worker / offscreen / background - - 不确定 - validations: - required: true - - type: input id: scriptcat-version attributes: @@ -124,19 +73,19 @@ body: - type: input id: browser attributes: - label: 操作系统和浏览器 - placeholder: "例如:Windows 11 Chrome 143 / macOS Firefox 151 / Android WebLibre 0.20" + label: 系统 / 浏览器及版本 + placeholder: "例如:Windows 11 + Chrome 143 / macOS + Firefox 151 / Android + WebLibre 0.20" validations: required: true - type: textarea - id: logs + id: details attributes: - label: 控制台日志 / 网络请求 / 截图 - description: 请说明日志来源:页面 console、content script、service worker、offscreen、网络面板等。 + label: 最小复现脚本 / 日志 / 截图 + description: 如果原脚本太大,请尽量删减成最小可复现版本。日志请注明来源:页面 console、content script、Service Worker、offscreen、网络面板等。 placeholder: | - 日志来源: - 错误: - 截图/视频: + 最小复现脚本: + 日志来源与错误信息: + 截图 / 视频: validations: required: false diff --git a/.github/ISSUE_TEMPLATE/03_feature_request.yaml b/.github/ISSUE_TEMPLATE/03_feature_request.yaml index 7ef413856..62f7d4893 100644 --- a/.github/ISSUE_TEMPLATE/03_feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/03_feature_request.yaml @@ -8,91 +8,23 @@ body: - type: markdown attributes: value: | - 本模板仅用于提交尚不存在的新能力。 - - 如果某个功能无法使用、以前可以使用但现在失效,或行为与你的预期不一致,请改用 Bug 模板。即使是功能请求,也必须填写浏览器和 ScriptCat 版本,以便维护者判断这是版本相关的 bug,还是确实缺少功能。 - 请描述你真正想解决的问题,而不只是描述一个按钮或 API 名称。 - 如果是 GM/CAT API 设计,建议给出 API 草案、兼容对象和安全影响。 + + 如果某个功能失效、或者以前能用现在不行,请改用 Bug 反馈模板。 - type: checkboxes id: precheck attributes: label: 提交前检查 options: - - label: 我已经搜索过现有 issues,确认不是重复功能请求 + - label: 我已经搜索过现有 issues,确认不是重复的功能请求 required: true - - label: 我已经确认这不是 bug;如果是异常行为,应使用 Bug 模板 - required: true - - - type: input - id: browser-version - attributes: - label: 浏览器及版本 - description: 请填写需要此功能的浏览器及其完整版本;如涉及多个浏览器,请全部列出。 - placeholder: "例如:Chrome 138.0.7204.101;Firefox 140.0.2" - validations: - required: true - - - type: input - id: scriptcat-version - attributes: - label: ScriptCat 版本 - description: 可在扩展详情或关于页面查看,请不要只填写“最新版”。 - placeholder: "例如:v0.17.0 或 commit/build 标识" - validations: - required: true - - - type: dropdown - id: expected-existing-behavior - attributes: - label: 你是否原本认为这个功能已经存在? - description: 此项用于区分缺少新功能、现有功能异常和回归问题。 - options: - - 否,我请求的是一个全新的能力 - - 是,我原本认为 ScriptCat 已支持;我会在下方说明为什么这不是 bug - validations: - required: true - - - type: dropdown - id: feature-area - attributes: - label: 功能领域 - multiple: true - options: - - 用户脚本管理 / 列表 / 分组 / 搜索 - - 脚本安装 / 更新 / 订阅 - - 脚本匹配 / 排除 / 权限管理 - - GM API / CAT API - - 后台脚本 / 定时脚本 - - Cloud Sync / 备份 / 恢复 / 迁移 - - Popup / Options / UI - - 内置编辑器 / Monaco / ESLint / VSCode 插件 - - 日志 / 调试 / 开发者体验 - - 移动端适配 - - 国际化 / 翻译 - - 性能 / 存储 / 安全 - - 其他 - validations: - required: true - - - type: textarea - id: current-behavior - attributes: - label: 当前行为和已尝试的方法 - description: 请说明 ScriptCat 当前的行为、你尝试过的方法,以及现有流程为什么无法满足需求。如果某项操作失败或以前可以使用,请改用 Bug 模板。 - placeholder: | - 当前行为: - 已尝试的步骤或替代方案: - 现有行为无法满足需求的原因: - validations: - required: true - type: textarea id: problem attributes: - label: 你遇到的实际问题 - description: 没有这个功能时,用户或脚本作者会遇到什么痛点? + label: 你想解决的问题 + description: 没有这个功能时,你或脚本作者会遇到什么麻烦? placeholder: | 例:我有很多定时签到脚本,目前必须逐个打开日志才能确认运行结果。 validations: @@ -108,35 +40,11 @@ body: required: true - type: textarea - id: api-design - attributes: - label: API / UI 草案(如适用) - description: 如果涉及 GM/CAT API、配置结构、同步格式、UI 流程,请给出草案。 - placeholder: | - ```ts - // API 草案 - ``` - validations: - required: false - - - type: textarea - id: alternatives - attributes: - label: 备选方案 - description: 是否已有 workaround?其他扩展是如何处理的? - placeholder: | - Tampermonkey / Violentmonkey / FireMonkey / Soulsign / Automa 等参考: - validations: - required: false - - - type: textarea - id: compatibility + id: additional-info attributes: - label: 兼容性和安全影响 - description: 是否影响 MV2/MV3、Firefox、移动端、隐身模式、站点权限、用户脚本权限? + label: 补充信息 + description: 可以补充 API 草案、其他脚本管理器的做法、截图或参考链接。 placeholder: | - 可能影响: - 需要新增权限: - 是否可能被恶意脚本滥用: + API 草案 / 参考实现 / 截图: validations: required: false diff --git a/.github/ISSUE_TEMPLATE/04_technical_proposal.yaml b/.github/ISSUE_TEMPLATE/04_technical_proposal.yaml index 0e128e53a..d0ab30bb8 100644 --- a/.github/ISSUE_TEMPLATE/04_technical_proposal.yaml +++ b/.github/ISSUE_TEMPLATE/04_technical_proposal.yaml @@ -9,29 +9,8 @@ body: attributes: value: | 这个模板用于技术设计、重构、性能优化、存储结构、运行时架构等提案。 - 普通用户 bug 或功能请求请使用其他模板。 - - type: dropdown - id: proposal-area - attributes: - label: 涉及模块 - multiple: true - options: - - Service Worker - - offscreen - - sandbox / inject / content script - - script matching / registry - - GM/CAT API runtime - - storage / Dexie / IndexedDB / OPFS - - sync / backup / import/export - - resource loader / @require / @resource - - editor / Monaco / ESLint - - popup / options UI - - build / packaging / manifest - - tests / e2e / CI - - docs / i18n - validations: - required: true + 普通用户 bug 或功能请求请使用其他模板。 - type: textarea id: background @@ -50,25 +29,13 @@ body: required: true - type: textarea - id: compatibility + id: impact attributes: - label: 兼容性 / 迁移影响 + label: 兼容性 / 迁移 / 测试 placeholder: | 对旧版本数据的影响: - 对 MV2/MV3 的影响: - 对 Firefox/Chrome/Edge/移动端的影响: + 对 MV2/MV3、Firefox/Chrome/Edge/移动端的影响: 是否需要 migration: - validations: - required: true - - - type: textarea - id: test-plan - attributes: - label: 测试计划 - placeholder: | - 单元测试: - e2e: - 手动测试浏览器: - 回归风险: + 测试与回归风险: validations: required: false diff --git a/.github/ISSUE_TEMPLATE/05_documentation.yaml b/.github/ISSUE_TEMPLATE/05_documentation.yaml index b5b712395..60a660e78 100644 --- a/.github/ISSUE_TEMPLATE/05_documentation.yaml +++ b/.github/ISSUE_TEMPLATE/05_documentation.yaml @@ -9,9 +9,8 @@ body: attributes: value: | 感谢帮助改进 ScriptCat 文档。 - 请尽量提供具体页面、章节、当前问题和建议修改内容。 - 如果是界面翻译、语言包、本地化措辞问题,请使用 “Translation / i18n” 模板。 + 如果是界面翻译、语言包、本地化措辞问题,请改用 “翻译 / 本地化” 模板。 - type: checkboxes id: precheck @@ -20,73 +19,12 @@ body: options: - label: 我已经搜索过现有 issues,确认不是重复问题 required: true - - label: 我确认这是文档问题,而不是 ScriptCat 本体 bug - required: true - type: input id: doc-url attributes: label: 相关文档页面 description: 请提供 docs.scriptcat.org、learn.scriptcat.org、README、CONTRIBUTING 或其他相关页面。 - placeholder: "例如:" - validations: - required: true - - - type: dropdown - id: doc-language - attributes: - label: 文档语言 - options: - - 简体中文 / zh-CN - - 繁體中文 / zh-TW - - English / en - - 日本語 / ja - - Русский / ru - - Deutsch / de - - Tiếng Việt / vi - - 其他 / Other - - 不确定 - validations: - required: true - - - type: dropdown - id: doc-area - attributes: - label: 文档领域 - multiple: true - options: - - 安装 / 更新 / 浏览器支持 - - 用户脚本安装 / 更新 / 订阅 - - 用户脚本开发指南 - - GM API / CAT API - - 后台脚本 / 定时脚本 - - Cloud Sync / 备份 / 恢复 / 迁移 - - 权限 / 安全 / 隐身模式 - - Popup / Options / UI - - 内置编辑器 / VSCode 插件 - - 调试 / 日志 / 开发者工具 - - 构建 / 贡献指南 - - README / Release Notes - - 其他 - validations: - required: true - - - type: dropdown - id: doc-issue-type - attributes: - label: 文档问题类型 - multiple: true - options: - - 内容错误 - - 内容过期 - - 缺少说明 - - 表达不清楚 - - 示例代码无法运行 - - 链接失效 - - 图片 / 截图过期 - - 页面结构或导航问题 - - 与实际 UI 或行为不一致 - - 其他 validations: required: true @@ -107,27 +45,13 @@ body: description: 请说明你希望如何修改文档。可以直接贴出建议文案。 placeholder: | 建议改为: - ... validations: required: true - - type: textarea - id: affected-version - attributes: - label: 相关版本 / 环境(如适用) - description: 如果文档问题和特定 ScriptCat 版本、浏览器或平台有关,请填写。 - placeholder: | - ScriptCat 版本: - 浏览器 / 平台: - validations: - required: false - - type: textarea id: additional-info attributes: label: 补充信息 description: 可以补充截图、相关 issue、PR、代码位置或参考资料。 - placeholder: | - 截图 / 参考资料 / 相关链接: validations: required: false diff --git a/.github/ISSUE_TEMPLATE/06_translation.yaml b/.github/ISSUE_TEMPLATE/06_translation.yaml index d879f0388..c13ceb51d 100644 --- a/.github/ISSUE_TEMPLATE/06_translation.yaml +++ b/.github/ISSUE_TEMPLATE/06_translation.yaml @@ -9,9 +9,8 @@ body: attributes: value: | 感谢帮助改进 ScriptCat 的本地化。 - 请尽量提供原文、当前译文、建议译文和出现位置。 - 如果是文档内容本身错误、过期或缺失,请使用 “Documentation” 模板。 + 如果是文档内容本身错误、过期或缺失,请改用 “文档问题 / 改进” 模板。 - type: checkboxes id: precheck @@ -20,27 +19,6 @@ body: options: - label: 我已经搜索过现有 issues,确认不是重复问题 required: true - - label: 我已经确认这是翻译 / 本地化问题,而不是功能 bug - required: true - - - type: dropdown - id: i18n-area - attributes: - label: 问题范围 - multiple: true - options: - - 扩展界面 / Popup - - Options 管理页 - - 安装 / 更新页面 - - 内置编辑器 / Monaco / ESLint 提示 - - 日志 / 错误提示 / 通知 - - 文档 - - README / CONTRIBUTING / SECURITY - - VSCode 插件 - - ScriptCat 网站 / 脚本市场 - - 其他 - validations: - required: true - type: dropdown id: language @@ -59,28 +37,11 @@ body: validations: required: true - - type: dropdown - id: issue-type - attributes: - label: 翻译问题类型 - multiple: true - options: - - 翻译错误 - - 术语不一致 - - 机器翻译不自然 - - 缺少翻译 / fallback 到其他语言 - - 文案太长导致 UI 溢出 - - 标点 / 大小写 / 格式问题 - - 新增语言支持 - - 其他 - validations: - required: true - - type: input id: location attributes: label: 出现位置 - description: 请提供页面、菜单路径、截图位置、文件路径或文档页面。 + description: 请提供页面、菜单路径、文件路径或文档页面。 placeholder: "例如:Options > Settings > Script Sync / src/locales//translation.json" validations: required: true @@ -88,10 +49,8 @@ body: - type: textarea id: source-text attributes: - label: 原文 / 当前文案 - description: 请贴出看到的原文或当前翻译。 - placeholder: | - 当前文案: + label: 当前文案 + description: 请贴出你看到的原文或当前翻译。 validations: required: true @@ -100,35 +59,15 @@ body: attributes: label: 建议译文 description: 请提供你建议使用的翻译;如果是新增语言请求,请说明目标语言和是否愿意贡献。 - placeholder: | - 建议改为: validations: required: true - type: textarea - id: reason + id: additional-info attributes: - label: 修改理由 - description: 为什么当前翻译不合适?是否有术语、上下文或地区用语考虑? + label: 修改理由 / 截图 + description: 为什么当前翻译不合适?是否涉及术语、上下文或地区用语?如果涉及 UI 溢出或截断,请上传截图。 placeholder: | 例:这里的 “sync” 在产品中统一译为 “同步”,不应译为 “备份”。 validations: required: false - - - type: input - id: scriptcat-version - attributes: - label: ScriptCat 版本(如适用) - placeholder: "例如:v1.4.0 / v1.4.0-beta / Firefox MV2 v0.16.15" - validations: - required: false - - - type: textarea - id: screenshots - attributes: - label: 截图 / 补充信息 - description: 如果涉及 UI 溢出、截断、换行或上下文,请上传截图。 - placeholder: | - 截图 / 相关链接: - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/11_bug_report_en.yaml b/.github/ISSUE_TEMPLATE/11_bug_report_en.yaml index 909e9e504..de9a9274a 100644 --- a/.github/ISSUE_TEMPLATE/11_bug_report_en.yaml +++ b/.github/ISSUE_TEMPLATE/11_bug_report_en.yaml @@ -8,66 +8,36 @@ body: - type: markdown attributes: value: | - Thanks for reporting a bug. + Thanks for reporting a bug. Please describe the reproduction steps and version information as clearly as you can. - Please provide as much detail as possible, especially: - - ScriptCat version - - Browser and OS version - - Reproduction steps - - Logs or screenshots - - Related userscript link or exported script package, if applicable - - If the issue is that a specific userscript works in Tampermonkey / Violentmonkey but not in ScriptCat, please use the **Userscript Compatibility** template instead. + If a specific userscript works in Tampermonkey / Violentmonkey but not in ScriptCat, please use the **Userscript Compatibility** template instead. - type: checkboxes id: precheck attributes: label: Pre-check options: - - label: I have searched existing issues and confirmed this is not a duplicate. - required: true - - label: I have tested with the latest stable or beta version where possible. + - label: I have searched existing issues, and confirmed the problem still happens on the latest stable or beta version where possible. required: true - - label: This is not a security vulnerability. Security issues should be reported privately through Security Advisory. - required: true - - - type: dropdown - id: bug-area - attributes: - label: Issue Area - multiple: true - options: - - Extension installation / update / uninstall - - Userscript installation / update / import / export - - Script execution / injection / matching rules - - GM API / CAT API - - Background scripts / scheduled scripts / offscreen - - Cloud Sync / backup / restore / WebDAV / Google Drive / OneDrive - - Popup / Options page / UI - - Built-in editor / Monaco / ESLint - - Logs / debugging / error messages - - Mobile / small-screen support - - Incognito mode / multiple windows / multiple tabs - - Performance / memory / storage - - Other - validations: - required: true - type: textarea id: summary attributes: - label: Problem Description - description: What happened? What did you expect to happen? + label: Description + description: What actually happened, and what did you expect to happen? placeholder: | - Example: On Edge for Android, after opening the ScriptCat popup and selecting "Import File", the file picker opens, but nothing happens after choosing a file. + Example: On Edge for Android, opening the ScriptCat popup and tapping "Import file" shows the file picker, but nothing happens after selecting a file. + + Expected: + Actual: validations: required: true - type: textarea id: steps attributes: - label: Minimal Reproduction Steps - description: Please provide steps that others can follow. Do not write only "1", "see screenshot", or "as title". + label: Steps to Reproduce + description: Please write steps someone else can follow. placeholder: | 1. Install ScriptCat v... 2. Open ... @@ -76,17 +46,6 @@ body: validations: required: true - - type: textarea - id: expected-actual - attributes: - label: Expected Behavior vs Actual Behavior - placeholder: | - Expected: - - Actual: - validations: - required: true - - type: input id: scriptcat-version attributes: @@ -95,115 +54,22 @@ body: validations: required: true - - type: dropdown - id: release-channel - attributes: - label: ScriptCat Channel - options: - - Chrome Stable - - Chrome Beta - - Edge Stable - - Edge Beta - - Firefox Stable / MV2 - - Firefox Beta / MV2 - - GitHub Release manual installation - - Self-built / dev - - VSCode extension - - Not sure - validations: - required: true - - - type: dropdown - id: manifest-mode - attributes: - label: Manifest / Extension Mode - options: - - MV3 - - MV2 / Firefox - - Not sure - validations: - required: true - - - type: input - id: os - attributes: - label: Operating System - placeholder: "Example: Windows 11 24H2 / macOS 15 / Android 16 / HarmonyOS 4.2 / iOS" - validations: - required: true - - type: input id: browser attributes: - label: Browser and Version - placeholder: "Example: Chrome 143.0.7499.41 / Edge 146.0.3802.0 / Firefox 151.0b7 / Brave 1.92.120 / Cromite 142..." + label: OS / Browser and Version + placeholder: "Example: Windows 11 + Chrome 143 / Android 16 + Edge 146 / macOS + Firefox 151" validations: required: true - - type: dropdown - id: device-type - attributes: - label: Device Type - options: - - Desktop - - Android phone / tablet - - iOS / iPadOS - - Other - validations: - required: true - - - type: checkboxes - id: browser-flags - attributes: - label: Browser / Extension State - options: - - label: Incognito / InPrivate / Private Window is enabled - - label: Mobile browser extension support is being used - - label: Third-party Chromium-based browser is being used - - label: Third-party Firefox / Gecko-based browser is being used - - label: ScriptCat has been granted site access permission for the target website - - label: Developer mode is enabled - - label: Other extensions may affect pages, requests, or userscripts - - - type: textarea - id: related-script - attributes: - label: Related Userscript / Configuration - description: If the issue is related to a specific userscript, please provide its install URL, metadata block, or an exported ScriptCat zip package. Please remove sensitive information first. - placeholder: | - Userscript URL: - Target website: - Relevant metadata: - ```js - // ==UserScript== - // @name ... - // @match ... - // @grant ... - // @connect ... - // ==/UserScript== - ``` - validations: - required: false - - - type: textarea - id: logs - attributes: - label: Logs / Error Messages / Screenshots - description: | - Please also mention where the logs come from: - page DevTools, extension Service Worker, offscreen.html, popup, options page, scheduled script logs, etc. - placeholder: | - Log source: - Error message: - Screenshots / video: - validations: - required: false - - type: textarea - id: workaround + id: details attributes: - label: Temporary Workaround + label: Related Script / Logs / Screenshots + description: Installation URL or metadata of the related script, console or Service Worker logs, and screenshots. Please redact sensitive information first. placeholder: | - Example: Refreshing the page fixes it; it works in Tampermonkey; it works after disabling incognito mode; reinstalling the userscript fixes it. + Script URL / target website: + Logs (source: page DevTools / Service Worker / offscreen / popup): + Screenshots: validations: required: false diff --git a/.github/ISSUE_TEMPLATE/12_userscript_compatibility_en.yaml b/.github/ISSUE_TEMPLATE/12_userscript_compatibility_en.yaml index 37ed9be35..fecfc2373 100644 --- a/.github/ISSUE_TEMPLATE/12_userscript_compatibility_en.yaml +++ b/.github/ISSUE_TEMPLATE/12_userscript_compatibility_en.yaml @@ -10,13 +10,6 @@ body: value: | This template is specifically for **userscript compatibility issues**. - Please provide: - - Userscript URL - - Target website - - Metadata block - - Comparison with Tampermonkey / Violentmonkey / other managers - - Minimal reproduction script if possible - If the issue is about ScriptCat UI, sync, backup, installation, or general extension behavior, please use the Bug Report template instead. - type: checkboxes @@ -24,11 +17,7 @@ body: attributes: label: Pre-check options: - - label: I have searched existing issues. - required: true - - label: I confirm this issue is related to a userscript or userscript API compatibility. - required: true - - label: I have provided a userscript link or a minimal reproduction script where possible. + - label: I have searched existing issues, and confirmed this is related to a userscript or userscript API. required: true - type: input @@ -42,30 +31,18 @@ body: - type: input id: target-url attributes: - label: Target Website - placeholder: "Example: " + label: Target Website Where the Issue Occurs validations: required: true - type: textarea id: problem attributes: - label: What Fails in ScriptCat? + label: Behavior and Comparison + description: What happens in ScriptCat, and what happens in other userscript managers? Please name the manager and version you compared against. placeholder: | - Example: Button does not appear / script does not run / GM_xmlhttpRequest fails / menu command does not work / page refreshes infinitely. - validations: - required: true - - - type: textarea - id: comparison - attributes: - label: Comparison with Other Userscript Managers - description: Please describe the behavior in Tampermonkey, Violentmonkey, FireMonkey, Greasemonkey, etc. - placeholder: | - Tampermonkey version: - Violentmonkey version: - FireMonkey / Greasemonkey version: - Behavior in those managers: + In ScriptCat: button never appears / script does not run / GM_xmlhttpRequest throws / page reloads endlessly + In another manager (e.g. Tampermonkey 5.x): works fine validations: required: true @@ -73,7 +50,7 @@ body: id: metadata attributes: label: Userscript Metadata - description: Please include relevant fields such as `@match`, `@grant`, `@connect`, `@require`, `@resource`, `@run-at`, `@inject-into`, etc. + description: Please include at least the relevant `@match`, `@grant`, `@connect`, `@require`, `@resource`, `@run-at`, and `@inject-into` entries. render: javascript placeholder: | // ==UserScript== @@ -85,41 +62,6 @@ body: validations: required: true - - type: textarea - id: minimal-reproduction - attributes: - label: Minimal Reproduction Script - description: If the original userscript is large, please try to reduce it to the smallest script that can reproduce the problem. - render: javascript - placeholder: | - // Please provide the smallest standalone script that reproduces the issue. - validations: - required: false - - - type: dropdown - id: api-area - attributes: - label: Related API / Mechanism - multiple: true - options: - - GM_xmlhttpRequest / GM.xmlHttpRequest - - GM_getValue / GM.setValue / GM storage - - GM_registerMenuCommand - - GM_download - - GM_cookie / Cookie API - - "@match / @include / @exclude" - - "@connect" - - "@require / @resource" - - "@run-at" - - "@inject-into" - - sandbox / unsafeWindow / page context - - iframe / allFrames / noframes - - CSP / Trusted Types - - service worker / offscreen / background - - Not sure - validations: - required: true - - type: input id: scriptcat-version attributes: @@ -131,19 +73,19 @@ body: - type: input id: browser attributes: - label: Operating System and Browser - placeholder: "Example: Windows 11 Chrome 143 / macOS Firefox 151 / Android WebLibre 0.20" + label: OS / Browser and Version + placeholder: "Example: Windows 11 + Chrome 143 / macOS + Firefox 151 / Android + WebLibre 0.20" validations: required: true - type: textarea - id: logs + id: details attributes: - label: Console Logs / Network Requests / Screenshots - description: Please mention the log source, such as page console, content script, service worker, offscreen, network panel, etc. + label: Minimal Reproduction Script / Logs / Screenshots + description: If the original script is large, please reduce it to a minimal reproduction. For logs, note where they came from — page console, content script, Service Worker, offscreen, network panel, etc. placeholder: | - Log source: - Error: + Minimal reproduction script: + Log source and error message: Screenshots / video: validations: required: false diff --git a/.github/ISSUE_TEMPLATE/13_feature_request_en.yaml b/.github/ISSUE_TEMPLATE/13_feature_request_en.yaml index 473609b78..7393ad3b2 100644 --- a/.github/ISSUE_TEMPLATE/13_feature_request_en.yaml +++ b/.github/ISSUE_TEMPLATE/13_feature_request_en.yaml @@ -8,17 +8,9 @@ body: - type: markdown attributes: value: | - Use this form only for a capability that does not exist yet. - - If something does not work, stopped working, or behaves differently from what you expected, please use the Bug Report template instead. Browser and ScriptCat versions are required here because they help maintainers determine whether the request is actually a version-specific bug or an unavailable feature. - Please describe the real problem you want to solve, not just a button name or an API name. - If this is a GM/CAT API design request, please provide: - - Proposed API shape - - Compatibility target - - Security impact - - Possible abuse cases + If something is broken, or used to work and no longer does, please use the Bug Report template instead. - type: checkboxes id: precheck @@ -27,79 +19,14 @@ body: options: - label: I have searched existing issues and confirmed this is not a duplicate feature request. required: true - - label: I confirm this is not a bug. If this is unexpected behavior, I should use the Bug Report template. - required: true - - - type: input - id: browser-version - attributes: - label: Browser and Version - description: Specify the browser where you need this feature, including its version. List all affected browsers if applicable. - placeholder: "Example: Chrome 138.0.7204.101; Firefox 140.0.2" - validations: - required: true - - - type: input - id: scriptcat-version - attributes: - label: ScriptCat Version - description: Find this in ScriptCat's extension details or About page. Do not write only "latest". - placeholder: "Example: v0.17.0 or commit/build identifier" - validations: - required: true - - - type: dropdown - id: expected-existing-behavior - attributes: - label: Did You Expect This to Already Work? - description: This helps distinguish a missing feature from a bug or regression. - options: - - No. I am requesting a completely new capability. - - Yes. I expected ScriptCat to support this already; I have explained why this is not a bug below. - validations: - required: true - - - type: dropdown - id: feature-area - attributes: - label: Feature Area - multiple: true - options: - - Userscript management / list / group / search - - Script installation / update / subscription - - Script matching / exclusion / permission management - - GM API / CAT API - - Background scripts / scheduled scripts - - Cloud Sync / backup / restore / migration - - Popup / Options / UI - - Built-in editor / Monaco / ESLint / VSCode extension - - Logs / debugging / developer experience - - Mobile support - - Internationalization / translation - - Performance / storage / security - - Other - validations: - required: true - - - type: textarea - id: current-behavior - attributes: - label: Current Behavior and What You Tried - description: Describe what ScriptCat currently does, what you tried, and why the existing workflow cannot achieve your goal. If something is failing or used to work, use the Bug Report template instead. - placeholder: | - Current behavior: - Steps or workarounds tried: - Why the current behavior is insufficient: - validations: - required: true - type: textarea id: problem attributes: - label: Problem to Solve - description: What pain point do users or userscript authors experience without this feature? + label: The Problem You Want to Solve + description: What is painful for you or for script authors without this feature? placeholder: | - Example: I have many scheduled check-in scripts. Currently I must open logs one by one to confirm whether they ran successfully. + Example: I have many scheduled check-in scripts, and I currently have to open each script's log to find out whether it ran successfully. validations: required: true @@ -108,40 +35,16 @@ body: attributes: label: Proposed Solution placeholder: | - Example: Add "Last Run Time" and "Last Result" columns to the script list. If a scheduled script fails, show the failure count on the extension icon. + Example: Add "last run time" and "last run result" columns to the script list, and show a failure count badge on the extension icon. validations: required: true - type: textarea - id: api-design - attributes: - label: API / UI Draft, if Applicable - description: If this involves GM/CAT APIs, configuration structure, sync format, or UI flow, please provide a draft. - placeholder: | - ```ts - // Proposed API - ``` - validations: - required: false - - - type: textarea - id: alternatives - attributes: - label: Alternatives - description: Is there any workaround? How do other extensions handle this? - placeholder: | - References from Tampermonkey / Violentmonkey / FireMonkey / Soulsign / Automa / others: - validations: - required: false - - - type: textarea - id: compatibility + id: additional-info attributes: - label: Compatibility and Security Impact - description: Does this affect MV2/MV3, Firefox, mobile browsers, incognito mode, site permissions, or userscript permissions? + label: Additional Information + description: Optionally add a proposed API shape, how other userscript managers handle this, screenshots, or reference links. placeholder: | - Possible impact: - New permissions required: - Possible abuse cases: + API draft / reference implementation / screenshots: validations: required: false diff --git a/.github/ISSUE_TEMPLATE/14_technical_proposal_en.yaml b/.github/ISSUE_TEMPLATE/14_technical_proposal_en.yaml index 0c97b3954..e3bbced37 100644 --- a/.github/ISSUE_TEMPLATE/14_technical_proposal_en.yaml +++ b/.github/ISSUE_TEMPLATE/14_technical_proposal_en.yaml @@ -12,28 +12,6 @@ body: For regular user bugs or feature requests, please use the other templates. - - type: dropdown - id: proposal-area - attributes: - label: Affected Module - multiple: true - options: - - Service Worker - - offscreen - - sandbox / inject / content script - - script matching / registry - - GM/CAT API runtime - - storage / Dexie / IndexedDB / OPFS - - sync / backup / import / export - - resource loader / @require / @resource - - editor / Monaco / ESLint - - popup / options UI - - build / packaging / manifest - - tests / e2e / CI - - docs / i18n - validations: - required: true - - type: textarea id: background attributes: @@ -51,25 +29,13 @@ body: required: true - type: textarea - id: compatibility + id: impact attributes: - label: Compatibility / Migration Impact + label: Compatibility / Migration / Testing placeholder: | Impact on existing data: - Impact on MV2/MV3: - Impact on Firefox / Chrome / Edge / mobile: + Impact on MV2/MV3 and Firefox / Chrome / Edge / mobile: Migration required: - validations: - required: true - - - type: textarea - id: test-plan - attributes: - label: Test Plan - placeholder: | - Unit tests: - E2E tests: - Manual browsers to test: - Regression risks: + Test plan and regression risks: validations: required: false diff --git a/.github/ISSUE_TEMPLATE/15_documentation_en.yaml b/.github/ISSUE_TEMPLATE/15_documentation_en.yaml index 0d9124465..f719aa0a6 100644 --- a/.github/ISSUE_TEMPLATE/15_documentation_en.yaml +++ b/.github/ISSUE_TEMPLATE/15_documentation_en.yaml @@ -10,8 +10,6 @@ body: value: | Thanks for helping improve ScriptCat documentation. - Please provide the exact page, section, current problem, and suggested change where possible. - If this is about UI translation, language packs, or localization wording, please use the **Translation / i18n** template instead. - type: checkboxes @@ -21,73 +19,12 @@ body: options: - label: I have searched existing issues and confirmed this is not a duplicate. required: true - - label: I confirm this is a documentation issue, not a ScriptCat product bug. - required: true - type: input id: doc-url attributes: label: Related Documentation Page description: Please provide the related docs.scriptcat.org, learn.scriptcat.org, README, CONTRIBUTING, or other page. - placeholder: "Example: " - validations: - required: true - - - type: dropdown - id: doc-language - attributes: - label: Documentation Language - options: - - Simplified Chinese / zh-CN - - Traditional Chinese / zh-TW - - English / en - - Japanese / ja - - Russian / ru - - German / de - - Vietnamese / vi - - Other - - Not sure - validations: - required: true - - - type: dropdown - id: doc-area - attributes: - label: Documentation Area - multiple: true - options: - - Installation / update / browser support - - Userscript installation / update / subscription - - Userscript development guide - - GM API / CAT API - - Background scripts / scheduled scripts - - Cloud Sync / backup / restore / migration - - Permissions / security / incognito mode - - Popup / Options / UI - - Built-in editor / VSCode extension - - Debugging / logs / developer tools - - Build / contributing guide - - README / Release Notes - - Other - validations: - required: true - - - type: dropdown - id: doc-issue-type - attributes: - label: Documentation Issue Type - multiple: true - options: - - Incorrect content - - Outdated content - - Missing explanation - - Unclear wording - - Code example does not work - - Broken link - - Outdated image / screenshot - - Page structure or navigation issue - - Inconsistent with actual UI or behavior - - Other validations: required: true @@ -108,27 +45,13 @@ body: description: Please describe how the documentation should be changed. You may paste suggested wording directly. placeholder: | Suggested wording: - ... validations: required: true - - type: textarea - id: affected-version - attributes: - label: Related Version / Environment, if Applicable - description: If the documentation issue is related to a specific ScriptCat version, browser, or platform, please provide it. - placeholder: | - ScriptCat version: - Browser / platform: - validations: - required: false - - type: textarea id: additional-info attributes: label: Additional Information description: Screenshots, related issues, PRs, code locations, or references can be added here. - placeholder: | - Screenshots / references / related links: validations: required: false diff --git a/.github/ISSUE_TEMPLATE/16_translation_en.yaml b/.github/ISSUE_TEMPLATE/16_translation_en.yaml index 510b93324..385ad18ba 100644 --- a/.github/ISSUE_TEMPLATE/16_translation_en.yaml +++ b/.github/ISSUE_TEMPLATE/16_translation_en.yaml @@ -10,8 +10,6 @@ body: value: | Thanks for helping improve ScriptCat localization. - Please provide the source text, current translation, suggested translation, and where it appears. - If the documentation content itself is incorrect, outdated, or missing, please use the **Documentation** template instead. - type: checkboxes @@ -21,27 +19,6 @@ body: options: - label: I have searched existing issues and confirmed this is not a duplicate. required: true - - label: I confirm this is a translation / localization issue, not a feature bug. - required: true - - - type: dropdown - id: i18n-area - attributes: - label: Affected Area - multiple: true - options: - - Extension UI / Popup - - Options page - - Installation / update page - - Built-in editor / Monaco / ESLint messages - - Logs / error messages / notifications - - Documentation - - README / CONTRIBUTING / SECURITY - - VSCode extension - - ScriptCat website / script marketplace - - Other - validations: - required: true - type: dropdown id: language @@ -60,28 +37,11 @@ body: validations: required: true - - type: dropdown - id: issue-type - attributes: - label: Translation Issue Type - multiple: true - options: - - Incorrect translation - - Inconsistent terminology - - Unnatural machine translation - - Missing translation / fallback to another language - - Text too long and breaks UI - - Punctuation / capitalization / formatting issue - - New language support - - Other - validations: - required: true - - type: input id: location attributes: label: Location - description: Please provide the page, menu path, screenshot location, file path, or documentation page. + description: Please provide the page, menu path, file path, or documentation page. placeholder: "Example: Options > Settings > Script Sync / src/locales//translation.json" validations: required: true @@ -89,10 +49,8 @@ body: - type: textarea id: source-text attributes: - label: Source Text / Current Text - description: Please paste the current source text or translation you see. - placeholder: | - Current text: + label: Current Text + description: Please paste the source text or current translation you see. validations: required: true @@ -101,35 +59,15 @@ body: attributes: label: Suggested Translation description: Please provide the wording you suggest. For new language requests, mention the target language and whether you are willing to contribute. - placeholder: | - Suggested wording: validations: required: true - type: textarea - id: reason - attributes: - label: Reason - description: Why is the current translation unsuitable? Are there terminology, context, or regional-language considerations? - placeholder: | - Example: In this product, "sync" is consistently translated as "...", so this string should not use "...". - validations: - required: false - - - type: input - id: scriptcat-version - attributes: - label: ScriptCat Version, if Applicable - placeholder: "Example: v1.4.0 / v1.4.0-beta / Firefox MV2 v0.16.15" - validations: - required: false - - - type: textarea - id: screenshots + id: additional-info attributes: - label: Screenshots / Additional Information - description: If this involves UI overflow, truncation, line wrapping, or context, please upload screenshots. + label: Reason / Screenshots + description: Why is the current translation unsuitable? Does it involve terminology, context, or regional wording? If it involves UI overflow or truncation, please attach screenshots. placeholder: | - Screenshots / related links: + Example: In this product, "sync" is consistently translated one way, so this string should not use a different term. validations: required: false From 991c3084ad6f3973b63ae1671662f3ad7360f927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Mon, 20 Jul 2026 22:31:05 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9C=85=20=E6=96=B0=E5=A2=9E=20issue=20?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E6=9C=BA=E6=A2=B0=E6=A3=80=E6=9F=A5=20(check?= =?UTF-8?q?:issue-templates)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 模板改动在 review 里几乎看不出问题:YAML 里一个未加引号的 `: ` 会让 GitHub 直接拒绝渲染表单,而中文模板用全角「:」不受影响,diff 上两边看起来一模一样; 字段 id 改名则会让 popup「报告问题」链接的预填静默失效——GitHub 丢弃未知参数, 且已安装的旧版本会一直沿用旧参数名。 scripts/check-issue-templates.mjs 检查三件事: 1. YAML 合法性 + issue-form schema(顶层键、元素类型、id 唯一、markdown 不带 id/validations、dropdown/checkboxes 有选项、checkboxes 不用 validations) 2. 中英文模板(0N ↔ 1N)字段序列、必填性、title/type/labels、选项数量一一对应 3. src/ 中构造的 issues/new 预填链接,其每个参数都能对应到目标模板的真实字段 id —— 用 TypeScript AST 解析而非字符串匹配,解析不出目标模板时报错而非放行 接入 lint / lint:ci(CI 的 Lint job 自动覆盖)与 .husky/pre-commit(校验暂存 快照而非工作区,与 check:i18n 一致)。 --- .husky/pre-commit | 18 ++ docs/develop.md | 14 +- package.json | 7 +- scripts/check-issue-templates.mjs | 350 +++++++++++++++++++++++++ scripts/check-issue-templates.test.mjs | 226 ++++++++++++++++ 5 files changed, 609 insertions(+), 6 deletions(-) create mode 100644 scripts/check-issue-templates.mjs create mode 100644 scripts/check-issue-templates.test.mjs diff --git a/.husky/pre-commit b/.husky/pre-commit index ba726b54f..6a1d34393 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -49,6 +49,24 @@ else rm -f "$i18n_files" fi +# issue 模板或构造 issues/new 预填链接的源码有改动时,跑模板机械检查(YAML 合法性、 +# issue-form schema、中英文结构对齐、预填参数 id 是否仍然存在)。同样检查暂存快照而非工作区。 +issue_template_files=$(mktemp) +git diff --cached --name-only -z --diff-filter=ACMRD -- \ + ".github/ISSUE_TEMPLATE/*" "src/*.ts" "src/*.tsx" > "$issue_template_files" + +if [ -s "$issue_template_files" ]; then + rm -f "$issue_template_files" + snapshot_dir=$(mktemp -d) + node scripts/git-staged-snapshot.mjs "$(pwd)" "$snapshot_dir" \ + && node scripts/check-issue-templates.mjs "--root=$snapshot_dir" + status=$? + rm -rf "$snapshot_dir" + [ $status -eq 0 ] || exit 1 +else + rm -f "$issue_template_files" +fi + # 在 main 或 release/* 分支上提交时额外跑测试 branch=$(git rev-parse --abbrev-ref HEAD) if [ "$branch" = "main" ] || echo "$branch" | grep -q "^release/"; then diff --git a/docs/develop.md b/docs/develop.md index f4eb30c2d..691760815 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -22,13 +22,21 @@ pnpm run typecheck # tsc --noEmit pnpm run test:e2e:install # install Playwright Chromium (first run only) pnpm run test:e2e # Playwright (e2e/*.spec.ts, 1 worker) -pnpm run lint # tsc --noEmit + eslint +pnpm run lint # prettier --check + tsc --noEmit + check:i18n + check:issue-templates, then eslint pnpm run lint-fix # prettier --write + tsc --noEmit + eslint --fix + +pnpm run check:i18n # translation key parity (see docs/translation.md) +pnpm run check:issue-templates # .github/ISSUE_TEMPLATE schema, zh/en parity, issues/new prefill ids ``` No standalone `format` script — formatting is part of `lint-fix` and runs through `prettier --write`. Husky -pre-commit runs `prettier --check` and `pnpm run typecheck` plus ESLint for staged JS/TS files, and also runs -`pnpm run test:ci` when committing on `main` or `release/*`. +pre-commit runs `prettier --check` and `pnpm run typecheck` plus ESLint for staged JS/TS files, runs +`check:i18n` when locale files are staged and `check:issue-templates` when issue templates or `src/` TypeScript +are staged, and also runs `pnpm run test:ci` when committing on `main` or `release/*`. + +`check:issue-templates` guards a contract that is invisible in review: GitHub prefills an issue form from +`issues/new?...` query params keyed by **field id**, so renaming or deleting an id silently breaks every link +using it — including links already shipped in installed builds, which keep sending the old param name. After `pnpm run dev`, load `dist/ext` as an unpacked extension. The browser hot-reloads page changes, but edits to `manifest.json`, `service_worker`, `offscreen`, or `sandbox` require reloading the extension. diff --git a/package.json b/package.json index 16f331519..371daac99 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,8 @@ "build": "cross-env NODE_ENV=production rspack build", "pack": "node ./scripts/pack.js", "typecheck": "tsc --noEmit", - "lint": "concurrently -g \"prettier --check --cache \\\"**/*.{ts,tsx,js,jsx,mjs}\\\"\" \"tsc --noEmit\" \"pnpm run check:i18n\" && eslint . --cache --cache-location .eslintcache", - "lint:ci": "concurrently -g \"prettier --check --cache \\\"**/*.{ts,tsx,js,jsx,mjs}\\\"\" \"tsc --noEmit\" \"pnpm run check:i18n\" && eslint . --cache --cache-location .eslintcache", + "lint": "concurrently -g \"prettier --check --cache \\\"**/*.{ts,tsx,js,jsx,mjs}\\\"\" \"tsc --noEmit\" \"pnpm run check:i18n\" \"pnpm run check:issue-templates\" && eslint . --cache --cache-location .eslintcache", + "lint:ci": "concurrently -g \"prettier --check --cache \\\"**/*.{ts,tsx,js,jsx,mjs}\\\"\" \"tsc --noEmit\" \"pnpm run check:i18n\" \"pnpm run check:issue-templates\" && eslint . --cache --cache-location .eslintcache", "lint-fix": "concurrently -g \"prettier --write --cache \\\"**/*.{ts,tsx,js,jsx,mjs}\\\"\" \"tsc --noEmit\" && eslint --fix . --cache --cache-location .eslintcache", "test": "vitest --no-coverage --reporter=verbose", "test:ci": "vitest run --no-coverage --reporter=default --reporter.summary=false", @@ -26,7 +26,8 @@ "test:e2e:ui": "pnpm exec playwright test --ui", "validate:yaml": "node ./scripts/validate-yaml.mjs", "validate:yaml:all": "node ./scripts/validate-yaml.mjs --all", - "check:i18n": "node ./scripts/check-i18n.mjs" + "check:i18n": "node ./scripts/check-i18n.mjs", + "check:issue-templates": "node ./scripts/check-issue-templates.mjs" }, "dependencies": { "@dnd-kit/core": "^6.3.1", diff --git a/scripts/check-issue-templates.mjs b/scripts/check-issue-templates.mjs new file mode 100644 index 000000000..94833ad7b --- /dev/null +++ b/scripts/check-issue-templates.mjs @@ -0,0 +1,350 @@ +#!/usr/bin/env node + +// Mechanical check for .github/ISSUE_TEMPLATE/*.yaml. +// +// Fail-closed by design: anything the checker cannot statically resolve is reported as an error +// asking a human to extend the check, never passed through. A silently-broken issue form is +// invisible in review — GitHub simply refuses to render it, or drops a prefilled value, and +// nobody notices until a user hits it. +// +// Checks: +// 1. Every template parses as YAML and satisfies GitHub's issue-form schema: known top-level +// keys, `name`/`description`/`body` present, known element types, unique non-empty ids, +// `markdown` blocks carry neither id nor validations, dropdown/checkboxes have options, +// and checkboxes use per-option `required` rather than `validations.required`. +// 2. zh/en parity — templates pair by numeric prefix (0N ↔ 1N). The pair must expose the same +// element sequence (type, id, required, render, multiple) and the same title/type/labels, +// so a field added to one language cannot silently go missing in the other. +// 3. The `issues/new?...` prefill contract. GitHub prefills an issue form from query params +// keyed by *field id*, so renaming or dropping an id silently breaks every link that used +// it — including links baked into already-installed extension builds, which keep sending +// the old param forever. Every such URL built in src/ is parsed from the TypeScript AST +// (not string-matched) and each of its params must resolve to a real id in every template +// the URL can select. +// +// Run with `--root=` to check a tree other than this file's repo checkout. + +import process from "node:process"; +import { readdirSync, readFileSync, existsSync, statSync, realpathSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import YAML from "yaml"; +import ts from "typescript"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const TEMPLATE_DIR = ".github/ISSUE_TEMPLATE"; +const TOP_LEVEL_KEYS = new Set(["name", "description", "title", "labels", "assignees", "body", "type", "projects"]); +const ELEMENT_TYPES = new Set(["markdown", "textarea", "input", "dropdown", "checkboxes"]); + +function walkFiles(dir, out = []) { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + if (entry.name === "node_modules" || entry.name === "dist") continue; + walkFiles(full, out); + } else if (/\.(ts|tsx)$/.test(entry.name)) { + out.push(full); + } + } + return out; +} + +// Collapse a `a + b + c` chain of string/template literals into one static skeleton, replacing +// each `${...}` with "\0" so the result stays parseable as a URL shape while marking the spots +// whose value is only known at runtime. +function staticText(node) { + if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text; + if (ts.isTemplateExpression(node)) { + return node.head.text + node.templateSpans.map((span) => "\0" + span.literal.text).join(""); + } + if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken) { + const left = staticText(node.left); + const right = staticText(node.right); + return left === null || right === null ? null : left + right; + } + if (ts.isParenthesizedExpression(node)) return staticText(node.expression); + return null; +} + +// ts.forEachChild aborts as soon as its callback returns a truthy value, so the visitor must +// return undefined — returning the accumulator here silently stops after the first child. +function collectStringLiterals(node, out = []) { + if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) out.push(node.text); + node.forEachChild((child) => { + collectStringLiterals(child, out); + }); + return out; +} + +// The whole expression a URL literal belongs to, so `"...?" + \`template=${a ? "x" : "y"}\`` is +// reconstructed as one unit rather than three unrelated fragments. +function outermostConcat(node) { + let current = node; + while ( + current.parent && + ((ts.isBinaryExpression(current.parent) && current.parent.operatorToken.kind === ts.SyntaxKind.PlusToken) || + ts.isParenthesizedExpression(current.parent)) + ) { + current = current.parent; + } + return current; +} + +function readTemplates(root, problems) { + const dir = path.join(root, TEMPLATE_DIR); + if (!existsSync(dir) || !statSync(dir).isDirectory()) { + problems.push(`${TEMPLATE_DIR}/ not found — issue templates must live there.`); + return {}; + } + + const templates = {}; + for (const file of readdirSync(dir) + .filter((f) => /\.ya?ml$/.test(f) && f !== "config.yml") + .sort()) { + const source = readFileSync(path.join(dir, file), "utf8"); + const doc = YAML.parseDocument(source, { prettyErrors: true, strict: true }); + if (doc.errors.length > 0) { + for (const error of doc.errors) problems.push(`${TEMPLATE_DIR}/${file}: invalid YAML — ${error.message}`); + continue; + } + templates[file] = doc.toJS(); + } + return templates; +} + +function checkSchema(file, doc, problems) { + const where = `${TEMPLATE_DIR}/${file}`; + + if (doc === null || typeof doc !== "object" || Array.isArray(doc)) { + problems.push(`${where}: top level must be a mapping.`); + return; + } + for (const key of Object.keys(doc)) { + if (!TOP_LEVEL_KEYS.has(key)) problems.push(`${where}: unknown top-level key "${key}".`); + } + for (const key of ["name", "description", "body"]) { + if (!doc[key]) problems.push(`${where}: missing required top-level "${key}".`); + } + if (!Array.isArray(doc.body)) { + if (doc.body) problems.push(`${where}: "body" must be a list.`); + return; + } + + const seen = new Set(); + doc.body.forEach((element, index) => { + const at = `${where} body[${index}]`; + if (element === null || typeof element !== "object") { + problems.push(`${at}: element must be a mapping.`); + return; + } + const attributes = element.attributes ?? {}; + + if (!ELEMENT_TYPES.has(element.type)) { + problems.push(`${at}: unknown element type "${element.type}".`); + return; + } + + if (element.type === "markdown") { + if (element.id) problems.push(`${at}: markdown blocks must not declare an id.`); + if (element.validations) problems.push(`${at}: markdown blocks must not declare validations.`); + if (!attributes.value) problems.push(`${at}: markdown block missing attributes.value.`); + return; + } + + if (!element.id) { + problems.push(`${at}: missing id — prefill links address fields by id.`); + } else if (seen.has(element.id)) { + problems.push(`${at}: duplicate id "${element.id}".`); + } + seen.add(element.id); + + if (!attributes.label) problems.push(`${at}: missing attributes.label.`); + + if (element.type === "dropdown" || element.type === "checkboxes") { + if (!Array.isArray(attributes.options) || attributes.options.length === 0) { + problems.push(`${at}: ${element.type} must declare a non-empty options list.`); + } + } + if (element.type === "checkboxes" && element.validations) { + problems.push(`${at}: checkboxes mark required per option, not via validations.`); + } + }); +} + +function elementShape(doc) { + return (doc.body ?? []) + .filter((element) => element && typeof element === "object") + .map((element) => { + const attributes = element.attributes ?? {}; + return [ + element.type, + element.id ?? "-", + element.validations?.required ? "required" : "optional", + attributes.render ? `render=${attributes.render}` : "", + attributes.multiple ? "multiple" : "", + ] + .filter(Boolean) + .join(":"); + }); +} + +// zh templates are 0N_*, their en mirrors 1N_*_en. +function checkLanguageParity(templates, problems) { + for (const file of Object.keys(templates)) { + const match = /^0(\d)_(.+)\.yaml$/.exec(file); + if (!match) continue; + const [, digit, slug] = match; + const mirror = `1${digit}_${slug}_en.yaml`; + + if (!templates[mirror]) { + problems.push(`${TEMPLATE_DIR}/${file}: missing English mirror ${mirror}.`); + continue; + } + + const zh = templates[file]; + const en = templates[mirror]; + + const zhShape = elementShape(zh); + const enShape = elementShape(en); + if (JSON.stringify(zhShape) !== JSON.stringify(enShape)) { + problems.push( + `${file} / ${mirror}: element structure differs — the two languages must expose the same fields.\n` + + ` zh: ${JSON.stringify(zhShape)}\n` + + ` en: ${JSON.stringify(enShape)}` + ); + } + for (const key of ["title", "type"]) { + if (zh[key] !== en[key]) problems.push(`${file} / ${mirror}: top-level "${key}" differs.`); + } + if (JSON.stringify(zh.labels) !== JSON.stringify(en.labels)) { + problems.push(`${file} / ${mirror}: "labels" differ.`); + } + + for (const type of ["dropdown", "checkboxes"]) { + const zhElements = (zh.body ?? []).filter((element) => element?.type === type); + const enElements = (en.body ?? []).filter((element) => element?.type === type); + zhElements.forEach((element, index) => { + const zhCount = element.attributes?.options?.length ?? 0; + const enCount = enElements[index]?.attributes?.options?.length ?? 0; + if (zhCount !== enCount) { + problems.push( + `${file} / ${mirror}: ${type} "${element.id}" has ${zhCount} options in zh but ${enCount} in en.` + ); + } + }); + } + } +} + +function checkPrefillContract(root, templates, problems) { + const srcDir = path.join(root, "src"); + if (!existsSync(srcDir)) return; + + const ids = Object.fromEntries( + Object.entries(templates).map(([file, doc]) => [ + file, + new Set((doc.body ?? []).map((element) => element?.id).filter(Boolean)), + ]) + ); + + for (const file of walkFiles(srcDir)) { + const source = readFileSync(file, "utf8"); + if (!source.includes("issues/new")) continue; + + const relative = path.relative(root, file); + const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true); + const expressions = new Set(); + + const visit = (node) => { + if ( + (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isTemplateExpression(node)) && + (staticText(node) ?? "").includes("issues/new") + ) { + expressions.add(outermostConcat(node)); + } + node.forEachChild(visit); + }; + visit(sourceFile); + + for (const expression of expressions) { + const url = staticText(expression); + if (url === null) { + problems.push( + `${relative}: found an issues/new URL this check cannot statically resolve. ` + + `Extend scripts/check-issue-templates.mjs rather than leaving the prefill contract unverified.` + ); + continue; + } + + const params = [...url.matchAll(/[?&\0]([a-zA-Z][\w-]*)=/g)].map((m) => m[1]).filter((p) => p !== "template"); + if (params.length === 0) continue; + + // `template=` is either spelled out inline (`template=01_bug_report.yaml&...`, so the name + // sits in the surrounding literal text) or chosen at runtime between locale variants + // (`template=${cond ? "01_bug_report" : "11_bug_report_en"}.yaml`, so the candidates are + // string literals in the expression). Collect both, and treat every candidate as reachable. + const targets = new Set(); + const addCandidate = (value) => { + const name = /^(\d\d_\w+?)(\.yaml)?$/.exec(value)?.[1]; + if (name && ids[`${name}.yaml`]) targets.add(`${name}.yaml`); + }; + addCandidate(/[?&\0]template=([^&\0]+)/.exec(url)?.[1] ?? ""); + for (const literal of collectStringLiterals(expression)) addCandidate(literal); + + if (targets.size === 0) { + problems.push( + `${relative}: issues/new URL prefills ${params.map((p) => `"${p}"`).join(", ")} ` + + `but no known template file could be resolved from it — the prefill target is unverifiable.` + ); + continue; + } + + for (const target of targets) { + for (const param of params) { + if (!ids[target].has(param)) { + problems.push( + `${relative}: prefills "${param}=", but ${TEMPLATE_DIR}/${target} has no field with that id. ` + + `GitHub silently drops unknown prefill params, and installed builds keep sending the old name.` + ); + } + } + } + } + } +} + +function runCheck(root) { + const problems = []; + const templates = readTemplates(root, problems); + + for (const [file, doc] of Object.entries(templates)) checkSchema(file, doc, problems); + checkLanguageParity(templates, problems); + checkPrefillContract(root, templates, problems); + + return { problems, templateCount: Object.keys(templates).length }; +} + +function main() { + const rootArg = process.argv.find((arg) => arg.startsWith("--root=")); + const root = rootArg ? path.resolve(rootArg.slice("--root=".length)) : path.resolve(__dirname, ".."); + + const { problems, templateCount } = runCheck(root); + + if (problems.length === 0) { + console.log(`✅ issue template check passed: ${templateCount} templates valid, mirrored, prefill ids intact.`); + process.exit(0); + } + + for (const problem of problems) console.error(`\n❌ ${problem}`); + console.error("\nIssue template check failed. Fix the problems above before submitting."); + process.exit(1); +} + +// Same argv/import.meta.url normalization rationale as scripts/check-i18n.mjs: compare real paths +// on both sides, or the check silently no-ops under symlinked or non-ASCII repo paths. +if (process.argv[1] && fileURLToPath(import.meta.url) === realpathSync(process.argv[1])) { + main(); +} + +export { runCheck }; diff --git a/scripts/check-issue-templates.test.mjs b/scripts/check-issue-templates.test.mjs new file mode 100644 index 000000000..b2b1f5145 --- /dev/null +++ b/scripts/check-issue-templates.test.mjs @@ -0,0 +1,226 @@ +import { describe, it, expect, afterEach } from "vitest"; +import { mkdirSync, writeFileSync, rmSync } from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import { fileURLToPath } from "node:url"; +import { runCheck } from "./check-issue-templates.mjs"; + +const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); + +// 用最小 fixture 仓库树(而非真实仓库)锁定两类真实回归: +// - 英文模板里未加引号的 `: ` 让 YAML 解析失败,中文模板因为用全角「:」而侥幸通过, +// 单看 diff 完全看不出来,GitHub 则直接拒绝渲染该表单; +// - 字段 id 被改名后,popup「报告问题」链接的 &browser= 预填静默失效, +// 且已安装的旧版本会一直沿用旧参数名。 + +const tmpDirs = []; + +const TEMPLATE_DIR = ".github/ISSUE_TEMPLATE"; + +function template({ name, title = "[BUG] ", labels = ["bug"], fields }) { + const body = fields + .map((field) => { + if (field.type === "markdown") { + return ` - type: markdown\n attributes:\n value: |\n ${field.value ?? "hello"}\n`; + } + const lines = [` - type: ${field.type}`]; + if (field.id !== undefined) lines.push(` id: ${field.id}`); + lines.push(` attributes:`); + lines.push(` label: ${field.label ?? field.id}`); + if (field.options) { + lines.push(` options:`); + for (const option of field.options) { + lines.push(typeof option === "string" ? ` - ${option}` : ` - label: ${option.label}`); + } + } + if (field.validations !== false) lines.push(` validations:\n required: ${field.required ?? true}`); + return lines.join("\n") + "\n"; + }) + .join("\n"); + return `name: ${name}\ndescription: ${name} description\ntitle: "${title}"\nlabels: ${JSON.stringify(labels)}\n\nbody:\n${body}`; +} + +const DEFAULT_FIELDS = [ + { type: "markdown" }, + { type: "textarea", id: "summary" }, + { type: "input", id: "scriptcat-version" }, + { type: "input", id: "browser" }, +]; + +function makeFixtureRoot({ zh, en, source } = {}) { + const dir = path.join(os.tmpdir(), `check-issue-templates-${Math.random().toString(36).slice(2)}`); + tmpDirs.push(dir); + mkdirSync(path.join(dir, TEMPLATE_DIR), { recursive: true }); + + writeFileSync( + path.join(dir, TEMPLATE_DIR, "01_bug_report.yaml"), + zh ?? template({ name: "Bug 反馈", fields: DEFAULT_FIELDS }) + ); + writeFileSync( + path.join(dir, TEMPLATE_DIR, "11_bug_report_en.yaml"), + en ?? template({ name: "Bug Report", fields: DEFAULT_FIELDS }) + ); + + if (source !== undefined) { + mkdirSync(path.join(dir, "src/pages/popup"), { recursive: true }); + writeFileSync(path.join(dir, "src/pages/popup/App.tsx"), source); + } + return dir; +} + +function problemsOf(root) { + return runCheck(root).problems; +} + +afterEach(() => { + while (tmpDirs.length) rmSync(tmpDirs.pop(), { recursive: true, force: true }); +}); + +describe("issue 模板机械检查", () => { + it("结构合法且中英文对齐的模板集合应通过", () => { + expect(problemsOf(makeFixtureRoot())).toEqual([]); + }); + + it("仓库现有的 issue 模板应全部通过检查", () => { + expect(problemsOf(REPO_ROOT)).toEqual([]); + }); + + describe("YAML 与 issue-form schema", () => { + it("未加引号导致的 YAML 解析失败应报错,而不是被跳过", () => { + // description 里裸露的 `: ` 会让 YAML 认为这里又开了一个映射键。 + const broken = + `name: Bug Report\ndescription: d\ntitle: "[BUG] "\nlabels: ["bug"]\n\nbody:\n` + + ` - type: input\n id: summary\n attributes:\n label: L\n` + + ` description: For logs, note the source: page console, service worker\n`; + const problems = problemsOf(makeFixtureRoot({ en: broken })); + expect(problems.join("\n")).toMatch(/invalid YAML/); + }); + + it("markdown 块声明 id 应报错", () => { + const zh = template({ + name: "Bug 反馈", + fields: [{ type: "markdown" }, { type: "textarea", id: "summary" }], + }).replace(" - type: markdown\n", " - type: markdown\n id: intro\n"); + expect(problemsOf(makeFixtureRoot({ zh })).join("\n")).toMatch(/markdown blocks must not declare an id/); + }); + + it("重复的字段 id 应报错", () => { + const fields = [ + { type: "textarea", id: "summary" }, + { type: "input", id: "summary" }, + ]; + const zh = template({ name: "Bug 反馈", fields }); + const en = template({ name: "Bug Report", fields }); + expect(problemsOf(makeFixtureRoot({ zh, en })).join("\n")).toMatch(/duplicate id "summary"/); + }); + + it("非 markdown 字段缺少 id 应报错", () => { + const fields = [{ type: "textarea", label: "无 id" }]; + const zh = template({ name: "Bug 反馈", fields }); + const en = template({ name: "Bug Report", fields }); + expect(problemsOf(makeFixtureRoot({ zh, en })).join("\n")).toMatch(/missing id/); + }); + + it("dropdown 没有 options 应报错", () => { + const fields = [{ type: "dropdown", id: "area" }]; + const zh = template({ name: "Bug 反馈", fields }); + const en = template({ name: "Bug Report", fields }); + expect(problemsOf(makeFixtureRoot({ zh, en })).join("\n")).toMatch(/must declare a non-empty options list/); + }); + + it("checkboxes 用 validations 而非逐项 required 应报错", () => { + const fields = [{ type: "checkboxes", id: "precheck", options: [{ label: "已搜索" }] }]; + const zh = template({ name: "Bug 反馈", fields }); + const en = template({ name: "Bug Report", fields }); + expect(problemsOf(makeFixtureRoot({ zh, en })).join("\n")).toMatch(/mark required per option/); + }); + + it("未知的顶层键应报错", () => { + const zh = template({ name: "Bug 反馈", fields: DEFAULT_FIELDS }) + "\nunknown_key: x\n"; + expect(problemsOf(makeFixtureRoot({ zh })).join("\n")).toMatch(/unknown top-level key "unknown_key"/); + }); + }); + + describe("中英文模板对齐", () => { + it("英文镜像缺少某个字段时应报错", () => { + const en = template({ + name: "Bug Report", + fields: DEFAULT_FIELDS.filter((field) => field.id !== "browser"), + }); + expect(problemsOf(makeFixtureRoot({ en })).join("\n")).toMatch(/element structure differs/); + }); + + it("同一字段中英文必填性不一致时应报错", () => { + const en = template({ + name: "Bug Report", + fields: DEFAULT_FIELDS.map((field) => (field.id === "browser" ? { ...field, required: false } : field)), + }); + expect(problemsOf(makeFixtureRoot({ en })).join("\n")).toMatch(/element structure differs/); + }); + + it("labels 不一致时应报错", () => { + const en = template({ name: "Bug Report", labels: ["bug", "extra"], fields: DEFAULT_FIELDS }); + expect(problemsOf(makeFixtureRoot({ en })).join("\n")).toMatch(/"labels" differ/); + }); + + it("缺少英文镜像文件应报错", () => { + const root = makeFixtureRoot(); + rmSync(path.join(root, TEMPLATE_DIR, "11_bug_report_en.yaml")); + expect(problemsOf(root).join("\n")).toMatch(/missing English mirror/); + }); + }); + + describe("issues/new 预填契约", () => { + const linkSource = (params) => ` + export function App() { + const onClick = () => { + const issueUrl = + \`https://github.com/scriptscat/scriptcat/issues/new?\` + + \`template=\${isChineseUser() ? "01_bug_report" : "11_bug_report_en"}.yaml&${params}\`; + window.open(issueUrl, "_blank"); + }; + return onClick; + } + `; + + it("预填参数都能对应到字段 id 时应通过", () => { + const source = linkSource("scriptcat-version=${ExtVersion}&browser=${encodeURIComponent(ua)}"); + expect(problemsOf(makeFixtureRoot({ source }))).toEqual([]); + }); + + it("字段 id 被改名导致预填参数失效时应报错", () => { + // browser -> environment:模板本身完全合法,只有链接会静默失效。 + const renamed = DEFAULT_FIELDS.map((field) => (field.id === "browser" ? { ...field, id: "environment" } : field)); + const zh = template({ name: "Bug 反馈", fields: renamed }); + const en = template({ name: "Bug Report", fields: renamed }); + const source = linkSource("scriptcat-version=${ExtVersion}&browser=${encodeURIComponent(ua)}"); + const problems = problemsOf(makeFixtureRoot({ zh, en, source })); + expect(problems.join("\n")).toMatch(/prefills "browser="/); + expect(problems.join("\n")).toMatch(/01_bug_report\.yaml has no field with that id/); + expect(problems.join("\n")).toMatch(/11_bug_report_en\.yaml has no field with that id/); + }); + + it("跨越模板字面量拼接的第二段参数同样要被检查", () => { + // browser= 出现在新的一段模板字面量开头,而不是紧跟 ? 或 &。 + const source = ` + const issueUrl = + \`https://github.com/scriptscat/scriptcat/issues/new?\` + + \`template=01_bug_report.yaml&scriptcat-version=\${ExtVersion}&\` + + \`nonexistent-field=\${x}\`; + `; + expect(problemsOf(makeFixtureRoot({ source })).join("\n")).toMatch(/prefills "nonexistent-field="/); + }); + + it("无法静态解析出模板名时应报错而不是放行", () => { + const source = ` + const issueUrl = \`https://github.com/scriptscat/scriptcat/issues/new?template=\${pickTemplate()}&browser=\${ua}\`; + \`;`; + expect(problemsOf(makeFixtureRoot({ source })).join("\n")).toMatch(/prefill target is unverifiable/); + }); + + it("不带预填参数的 issues/new 链接不应报错", () => { + const source = `const url = "https://github.com/scriptscat/scriptcat/issues/new";`; + expect(problemsOf(makeFixtureRoot({ source }))).toEqual([]); + }); + }); +});