From ea9c04eff63ef51f4b5c773fd922a8dd4b620a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Wed, 24 Jun 2026 15:25:50 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20=E6=96=B0=E5=A2=9E=E6=89=93?= =?UTF-8?q?=E5=8C=85=E5=8F=98=E9=87=8F=20SC=5FENABLE=5FAGENT=EF=BC=9A?= =?UTF-8?q?=E6=AD=A3=E5=BC=8F=E7=89=88=E5=B1=8F=E8=94=BD=20agent=20?= =?UTF-8?q?=E5=85=A5=E5=8F=A3=E5=B9=B6=E7=A7=BB=E9=99=A4=20debugger=20?= =?UTF-8?q?=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scripts/build-config.js:isAgentEnabled(dev/beta 启用) + applyAgentManifest(正式版剥离 debugger) - rspack.config.ts / pack.js:注入 process.env.SC_ENABLE_AGENT,按版本处理 manifest 权限 - const.ts 暴露 EnableAgent,门控侧边栏 agent 菜单与 /agent/* 路由 - MainLayout/install 安装页屏蔽 Skill ZIP、SkillScript、.cat.md 等安装入口 - service_worker/script.ts 屏蔽 .skill.js/.cat.md 的 DNR/webNavigation 拦截与识别 - agent 运行时(CAT_*/AgentService)保持不变;vitest 默认 SC_ENABLE_AGENT=true --- rspack.config.ts | 9 ++- scripts/build-config.js | 24 +++++++ scripts/build-config.test.js | 49 +++++++++++++ scripts/pack.js | 7 +- src/app/const.ts | 4 ++ src/app/service/service_worker/script.ts | 56 +++++++++------ src/pages/components/layout/MainLayout.tsx | 13 ++-- src/pages/components/layout/Sider.tsx | 80 +++++++++++----------- src/pages/install/hooks.tsx | 15 ++-- src/pages/install/utils.ts | 5 +- vitest.config.ts | 1 + 11 files changed, 186 insertions(+), 77 deletions(-) create mode 100644 scripts/build-config.js create mode 100644 scripts/build-config.test.js diff --git a/rspack.config.ts b/rspack.config.ts index e670fe47e..6d468b26b 100644 --- a/rspack.config.ts +++ b/rspack.config.ts @@ -4,6 +4,7 @@ import { ZipExecutionPlugin } from "./rspack-plugins/ZipExecutionPlugin"; import { readFileSync } from "fs"; import { v4 as uuidv4 } from "uuid"; import { toChromeVersion } from "./scripts/version.js"; +import { isAgentEnabled, applyAgentManifest } from "./scripts/build-config.js"; const pkg = JSON.parse(readFileSync("./package.json", "utf-8")); @@ -12,6 +13,8 @@ const dirname = path.resolve(); const isDev = process.env.NODE_ENV === "development"; const isBeta = version.includes("-"); const isReactTools = process.env.REACT_DEVTOOLS === "true"; +// agent 功能仅在开发版与 beta 版本提供,正式版本屏蔽入口并移除 debugger 权限 +const enableAgent = isAgentEnabled({ isDev, isBeta }); // Target browsers, see: https://github.com/browserslist/browserslist // 依照 https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/userScripts#browser_compatibility @@ -132,6 +135,7 @@ export default { new rspack.DefinePlugin({ "process.env.VI_TESTING": "'false'", "process.env.SC_RANDOM_KEY": `'${uuidv4()}'`, + "process.env.SC_ENABLE_AGENT": `'${enableAgent}'`, }), new rspack.CopyRspackPlugin({ patterns: [ @@ -140,7 +144,10 @@ export default { to: `${dist}/ext`, // 将manifest.json内版本号替换为package.json中版本号 transform(content: Buffer) { - const manifest = JSON.parse(content.toString()) as chrome.runtime.ManifestV3; + const manifest = applyAgentManifest( + JSON.parse(content.toString()) as chrome.runtime.ManifestV3, + enableAgent + ); manifest.version = toChromeVersion(version); if (isDev || isBeta) { manifest.name = "__MSG_scriptcat_beta__"; diff --git a/scripts/build-config.js b/scripts/build-config.js new file mode 100644 index 000000000..cb56fbed3 --- /dev/null +++ b/scripts/build-config.js @@ -0,0 +1,24 @@ +/** + * agent 功能仅在开发版与 beta 预发布版本中提供,正式版本屏蔽。 + * @param {{ isDev: boolean, isBeta: boolean }} env + * @returns {boolean} + */ +export function isAgentEnabled({ isDev, isBeta }) { + return Boolean(isDev || isBeta); +} + +/** + * 正式版本不提供 agent,移除仅 agent 使用的 debugger 权限; + * 其余权限保持不变。启用 agent 时原样返回,禁用时返回新对象,不修改入参。 + * @template {{ permissions?: string[] }} T + * @param {T} manifest + * @param {boolean} agentEnabled + * @returns {T} + */ +export function applyAgentManifest(manifest, agentEnabled) { + if (agentEnabled) return manifest; + return { + ...manifest, + permissions: (manifest.permissions || []).filter((permission) => permission !== "debugger"), + }; +} diff --git a/scripts/build-config.test.js b/scripts/build-config.test.js new file mode 100644 index 000000000..8bd0e0860 --- /dev/null +++ b/scripts/build-config.test.js @@ -0,0 +1,49 @@ +import { describe, it, expect } from "vitest"; +import { isAgentEnabled, applyAgentManifest } from "./build-config.js"; + +describe("构建配置 - agent 开关", () => { + describe("isAgentEnabled", () => { + it("开发版本应启用 agent", () => { + expect(isAgentEnabled({ isDev: true, isBeta: false })).toBe(true); + }); + + it("beta 版本应启用 agent", () => { + expect(isAgentEnabled({ isDev: false, isBeta: true })).toBe(true); + }); + + it("正式版本应禁用 agent", () => { + expect(isAgentEnabled({ isDev: false, isBeta: false })).toBe(false); + }); + }); + + describe("applyAgentManifest", () => { + const makeManifest = () => ({ + permissions: ["tabs", "debugger", "storage"], + optional_permissions: ["background", "userScripts"], + }); + + it("启用 agent 时原样返回 manifest 且保留 debugger 权限", () => { + const manifest = makeManifest(); + const result = applyAgentManifest(manifest, true); + expect(result).toBe(manifest); + expect(result.permissions).toContain("debugger"); + }); + + it("禁用 agent 时移除 debugger 权限", () => { + const result = applyAgentManifest(makeManifest(), false); + expect(result.permissions).not.toContain("debugger"); + expect(result.permissions).toEqual(["tabs", "storage"]); + }); + + it("禁用 agent 时不改动其它权限", () => { + const result = applyAgentManifest(makeManifest(), false); + expect(result.optional_permissions).toEqual(["background", "userScripts"]); + }); + + it("禁用 agent 时不修改入参 manifest", () => { + const manifest = makeManifest(); + applyAgentManifest(manifest, false); + expect(manifest.permissions).toContain("debugger"); + }); + }); +}); diff --git a/scripts/pack.js b/scripts/pack.js index 5afc6cc66..9b4a407d4 100644 --- a/scripts/pack.js +++ b/scripts/pack.js @@ -7,6 +7,7 @@ import manifest from "../src/manifest.json" with { type: "json" }; import packageInfo from "../package.json" with { type: "json" }; import semver from "semver"; import { toChromeVersion } from "./version.js"; +import { isAgentEnabled, applyAgentManifest } from "./build-config.js"; // ============================================================================ @@ -28,6 +29,8 @@ const addZipFile = async (zip, path, content) => { // 判断是否为beta版本 const version = semver.parse(packageInfo.version); +// agent 功能仅在 beta 版本提供,正式版本屏蔽入口并移除 debugger 权限 +const agentEnabled = isAgentEnabled({ isDev: false, isBeta: version.prerelease.length > 0 }); manifest.version = toChromeVersion(packageInfo.version); if (version.prerelease.length) { manifest.name = `__MSG_scriptcat_beta__`; @@ -58,8 +61,8 @@ execSync("pnpm run build", { stdio: "inherit" }); // 处理firefox和chrome的zip压缩包 // 浅拷贝防止后续修改 -const firefoxManifest = { ...manifest, background: { ...manifest.background } }; -const chromeManifest = { ...manifest, background: { ...manifest.background } }; +const firefoxManifest = applyAgentManifest({ ...manifest, background: { ...manifest.background } }, agentEnabled); +const chromeManifest = applyAgentManifest({ ...manifest, background: { ...manifest.background } }, agentEnabled); chromeManifest.optional_permissions = chromeManifest.optional_permissions.filter((val) => val !== "userScripts"); delete chromeManifest.background.scripts; diff --git a/src/app/const.ts b/src/app/const.ts index 364e2c5dc..53f9651d2 100644 --- a/src/app/const.ts +++ b/src/app/const.ts @@ -1,6 +1,10 @@ import { version } from "../../package.json"; export const ExtVersion = version; + +// agent 功能仅在开发版与 beta 版本提供,正式版本屏蔽相关入口。 +// 由打包变量 process.env.SC_ENABLE_AGENT 注入(见 rspack.config.ts / scripts/build-config.js)。 +export const EnableAgent = process.env.SC_ENABLE_AGENT === "true"; export const Discord = "https://discord.gg/JF76nHCCM7"; export const DocumentationSite = "https://docs.scriptcat.org"; diff --git a/src/app/service/service_worker/script.ts b/src/app/service/service_worker/script.ts index 3d116db44..efb9405d1 100644 --- a/src/app/service/service_worker/script.ts +++ b/src/app/service/service_worker/script.ts @@ -47,6 +47,7 @@ import { CompiledResourceDAO } from "@App/app/repo/resource"; import { initRegularUpdateCheck } from "./regular_updatecheck"; import { parseSkillScriptMetadata } from "@App/pkg/utils/skill_script"; import { TempStorageDAO, TempStorageItemType } from "@App/app/repo/tempStorage"; +import { EnableAgent } from "@App/app/const"; export type TCheckScriptUpdateOption = Partial< { checkType: "user"; noUpdateCheck?: number } | ({ checkType: "system" } & Record) @@ -98,8 +99,11 @@ export class ScriptService { } // 处理url, 实现安装脚本 let targetUrl: string; - // 判断是否为 file:///*/*.user.js 或 file:///*/*.skill.js - if (req.url.startsWith("file://") && (req.url.endsWith(".user.js") || req.url.endsWith(".skill.js"))) { + // 判断是否为 file:///*/*.user.js 或 file:///*/*.skill.js(skill 仅 agent 启用时) + if ( + req.url.startsWith("file://") && + (req.url.endsWith(".user.js") || (EnableAgent && req.url.endsWith(".skill.js"))) + ) { targetUrl = req.url; } else { const reqUrl = new URL(req.url); @@ -166,8 +170,13 @@ export class ScriptService { { schemes: ["http", "https"], hostEquals: "docs.scriptcat.org", pathPrefix: "/en/docs/script_installation/" }, { schemes: ["http", "https"], hostEquals: "www.tampermonkey.net", pathPrefix: "/script_installation.php" }, { schemes: ["file"], pathSuffix: ".user.js" }, - { schemes: ["file"], pathSuffix: ".skill.js" }, - { schemes: ["file"], pathSuffix: ".cat.md" }, + // Skill 安装入口仅在 agent 启用时拦截(正式版屏蔽) + ...(EnableAgent + ? [ + { schemes: ["file"], pathSuffix: ".skill.js" }, + { schemes: ["file"], pathSuffix: ".cat.md" }, + ] + : []), ], } ); @@ -252,21 +261,26 @@ export class ScriptService { isUrlFilterCaseSensitive: false, requestDomains: ["bitbucket.org"], // Chrome 101+ }, - // SkillScript (.skill.js) 安装检测 - { - regexFilter: "^([^?#]+?\\.skill\\.js)", - resourceTypes: [chrome.declarativeNetRequest.ResourceType.MAIN_FRAME], - requestMethods: ["get" as chrome.declarativeNetRequest.RequestMethod], - isUrlFilterCaseSensitive: false, - excludedRequestDomains: ["github.com", "gitlab.com", "gitea.com", "bitbucket.org"], - }, - // Skill 包 (.cat.md) 安装检测 - { - regexFilter: "^([^?#]+?\\.cat\\.md)", - resourceTypes: [chrome.declarativeNetRequest.ResourceType.MAIN_FRAME], - requestMethods: ["get" as chrome.declarativeNetRequest.RequestMethod], - isUrlFilterCaseSensitive: false, - }, + // Skill 安装入口仅在 agent 启用时拦截(正式版屏蔽) + ...(EnableAgent + ? [ + // SkillScript (.skill.js) 安装检测 + { + regexFilter: "^([^?#]+?\\.skill\\.js)", + resourceTypes: [chrome.declarativeNetRequest.ResourceType.MAIN_FRAME], + requestMethods: ["get" as chrome.declarativeNetRequest.RequestMethod], + isUrlFilterCaseSensitive: false, + excludedRequestDomains: ["github.com", "gitlab.com", "gitea.com", "bitbucket.org"], + }, + // Skill 包 (.cat.md) 安装检测 + { + regexFilter: "^([^?#]+?\\.cat\\.md)", + resourceTypes: [chrome.declarativeNetRequest.ResourceType.MAIN_FRAME], + requestMethods: ["get" as chrome.declarativeNetRequest.RequestMethod], + isUrlFilterCaseSensitive: false, + }, + ] + : []), ]; const installPageURL = chrome.runtime.getURL("src/install.html"); const rules = conditions.map((condition, idx) => { @@ -948,8 +962,8 @@ export class ScriptService { logger?.error("prepare script failed", Logger.E(e)); } } - // 检测是否为 SkillScript - const skillScriptMeta = parseSkillScriptMetadata(code); + // 检测是否为 SkillScript(仅 agent 启用时,正式版不识别 skill 安装) + const skillScriptMeta = EnableAgent ? parseSkillScriptMetadata(code) : null; if (skillScriptMeta) { const si = await createTempCodeEntry(false, uuid, code, url, upsertBy, {} as SCMetadata, options); si[1].skillScript = true; diff --git a/src/pages/components/layout/MainLayout.tsx b/src/pages/components/layout/MainLayout.tsx index 279ae8500..f53c48abb 100644 --- a/src/pages/components/layout/MainLayout.tsx +++ b/src/pages/components/layout/MainLayout.tsx @@ -35,6 +35,7 @@ import "./index.css"; import { arcoLocale } from "@App/locales/arco"; import { prepareScriptByCode, parseMetadata } from "@App/pkg/utils/script"; import { parseSkillScriptMetadata } from "@App/pkg/utils/skill_script"; +import { EnableAgent } from "@App/app/const"; import { saveHandle } from "@App/pkg/utils/filehandle-db"; import { makeBlobURL, openInCurrentTab } from "@App/pkg/utils/utils"; import ScrollBoundary from "@App/pages/components/layout/ScrollBoundary"; @@ -221,8 +222,8 @@ const MainLayout: React.FC<{ Promise.all( acceptedFiles.map(async (aFile) => { try { - // ZIP 文件走 Skill 安装流程 - if (aFile.name.endsWith(".zip")) { + // ZIP 文件走 Skill 安装流程(仅 agent 启用时) + if (EnableAgent && aFile.name.endsWith(".zip")) { await handleZipSkillInstall(aFile); stat.success++; return; @@ -261,9 +262,11 @@ const MainLayout: React.FC<{ // 先尝试 UserScript 解析 const metadata = parseMetadata(code); if (metadata) return prepareScriptByCode(code, `file:///*resp-check*/${file.name}`); - // 再尝试 SkillScript 解析 - const skillScriptMeta = parseSkillScriptMetadata(code); - if (skillScriptMeta) return { script: {} as any }; + // 再尝试 SkillScript 解析(仅 agent 启用时) + if (EnableAgent) { + const skillScriptMeta = parseSkillScriptMetadata(code); + if (skillScriptMeta) return { script: {} as any }; + } throw new Error("not a valid UserScript or SkillScript"); }), simpleDigestMessage(`f=${file.name}\ns=${file.size},m=${file.lastModified}`), diff --git a/src/pages/components/layout/Sider.tsx b/src/pages/components/layout/Sider.tsx index 6d290ffb9..235e7a4d6 100644 --- a/src/pages/components/layout/Sider.tsx +++ b/src/pages/components/layout/Sider.tsx @@ -26,7 +26,7 @@ import { RiFileCodeLine, RiGuideLine, RiLinkM } from "react-icons/ri"; import SiderGuide from "./SiderGuide"; import CustomLink from "../CustomLink"; import { localePath } from "@App/locales/locales"; -import { DocumentationSite } from "@App/app/const"; +import { DocumentationSite, EnableAgent } from "@App/app/const"; const MenuItem = Menu.Item; let { hash } = window.location; @@ -82,43 +82,45 @@ const Sider: React.FC = () => { - { - e.stopPropagation(); - setMenuSelect("/agent/chat"); - setOpenKeys((prev) => (prev.includes("/agent") ? prev : [...prev, "/agent"])); - window.location.hash = "/agent/chat"; - }} - > - {t("agent")} - - } - > - - {t("agent_chat")} - - - {t("agent_provider")} - - - {t("agent_skills")} - - - {t("agent_mcp")} - - - {t("agent_tasks")} - - - {t("agent_opfs")} - - - {t("agent_settings")} - - + {EnableAgent && ( + { + e.stopPropagation(); + setMenuSelect("/agent/chat"); + setOpenKeys((prev) => (prev.includes("/agent") ? prev : [...prev, "/agent"])); + window.location.hash = "/agent/chat"; + }} + > + {t("agent")} + + } + > + + {t("agent_chat")} + + + {t("agent_provider")} + + + {t("agent_skills")} + + + {t("agent_mcp")} + + + {t("agent_tasks")} + + + {t("agent_opfs")} + + + {t("agent_settings")} + + + )} @@ -279,7 +281,7 @@ const Sider: React.FC = () => { } /> } /> } /> - } /> + {EnableAgent && } />} } /> diff --git a/src/pages/install/hooks.tsx b/src/pages/install/hooks.tsx index 9b9d0a709..bfbaf801b 100644 --- a/src/pages/install/hooks.tsx +++ b/src/pages/install/hooks.tsx @@ -18,6 +18,7 @@ import { formatBytes, isPermissionOk } from "@App/pkg/utils/utils"; import { i18nName } from "@App/locales/locales"; import { parseSkillScriptMetadata } from "@App/pkg/utils/skill_script"; import type { SkillScriptMetadata } from "@App/app/service/agent/core/types"; +import { EnableAgent } from "@App/app/const"; import { TempStorageDAO, TempStorageItemType } from "@App/app/repo/tempStorage"; import { cIdKey, @@ -49,8 +50,8 @@ export function useInstallData() { const [watchFile, setWatchFile] = useState(false); const closingWindowRef = useRef(false); - // Skill 安装相关状态 - const skillInstallUuid = searchParams.get("skill"); + // Skill 安装相关状态(agent 关闭时不识别 skill 入口) + const skillInstallUuid = EnableAgent ? searchParams.get("skill") : null; const [skillPreview, setSkillPreview] = useState<{ metadata: { name: string; description: string; version?: string }; prompt: string; @@ -189,8 +190,8 @@ export function useInstallData() { const code = await file.text(); const metadata = parseMetadata(code); if (!metadata) { - // 非 UserScript,尝试作为 SkillScript 处理 - const skillScriptMeta = parseSkillScriptMetadata(code); + // 非 UserScript,尝试作为 SkillScript 处理(仅 agent 启用时) + const skillScriptMeta = EnableAgent ? parseSkillScriptMetadata(code) : null; if (!skillScriptMeta) { throw new Error("parse script info failed"); } @@ -611,7 +612,7 @@ export function useInstallData() { try { const { result, url } = await fetchValidScript(); const { code, metadata } = result; - const isSkillScript = "skillScript" in result && result.skillScript === true; + const isSkillScript = EnableAgent && "skillScript" in result && result.skillScript === true; const uuid = uuidv4(); const scriptData = await createTempCodeEntry(false, uuid, code, url, "user", metadata, {}); @@ -655,8 +656,8 @@ export function useInstallData() { }; const handleUrlChangeAndFetch = (targetUrlHref: string) => { - // .cat.md URL → Skill 安装流程 - if (targetUrlHref.match(/\.cat\.md(\?|#|$)/i)) { + // .cat.md URL → Skill 安装流程(仅 agent 启用时) + if (EnableAgent && targetUrlHref.match(/\.cat\.md(\?|#|$)/i)) { loadSkillFromUrl(targetUrlHref); return; } diff --git a/src/pages/install/utils.ts b/src/pages/install/utils.ts index b29545124..f799da845 100644 --- a/src/pages/install/utils.ts +++ b/src/pages/install/utils.ts @@ -3,6 +3,7 @@ import { readRawContent } from "@App/pkg/utils/encoding"; import { parseSkillScriptMetadata } from "@App/pkg/utils/skill_script"; import type { SCMetadata } from "@App/app/repo/scripts"; import { TempStorageDAO } from "@App/app/repo/tempStorage"; +import { EnableAgent } from "@App/app/const"; export const cIdKey = `(cid_${Math.random()})`; @@ -79,9 +80,9 @@ export const fetchScriptBody = async (url: string, { onProgress }: { [key: strin const code = await readRawContent(chunksAll, contentType); const metadata = parseMetadata(code); - // 如果不是 UserScript,检测是否为 SkillScript + // 如果不是 UserScript,检测是否为 SkillScript(仅 agent 启用时) if (!metadata) { - const skillScriptMeta = parseSkillScriptMetadata(code); + const skillScriptMeta = EnableAgent ? parseSkillScriptMetadata(code) : null; if (skillScriptMeta) { return { code, metadata: {} as SCMetadata, skillScript: true }; } diff --git a/vitest.config.ts b/vitest.config.ts index 91493ccc8..c3f7d5ed0 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -31,6 +31,7 @@ export default defineConfig({ env: { VI_TESTING: "true", SC_RANDOM_KEY: "005a7deb-3a6e-4337-83ea-b9626c02ea38", + SC_ENABLE_AGENT: "true", }, }, }); From 13dacd5bf2bdd794896f106c1ef39fc0cf3f275a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Wed, 24 Jun 2026 15:26:11 +0800 Subject: [PATCH 2/3] chore: bump version to 1.4.0 --- package.json | 2 +- src/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index bbcae64a2..9c8f4b155 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "scriptcat", - "version": "1.4.0-beta.4", + "version": "1.4.0", "description": "脚本猫,一个可以执行用户脚本的浏览器扩展,万物皆可脚本化,让你的浏览器可以做更多的事情!", "author": "CodFrm", "license": "GPLv3", diff --git a/src/manifest.json b/src/manifest.json index 0e02f4510..7eee525cb 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "__MSG_scriptcat__", - "version": "1.4.0.1500", + "version": "1.4.0", "author": "CodFrm", "description": "__MSG_scriptcat_description__", "options_ui": { From 73e19eb3a3935ab429ed0cc0999cb476bc64fd41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Wed, 24 Jun 2026 15:45:14 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9C=85=20e2e=20=E5=BC=BA=E5=88=B6?= =?UTF-8?q?=E5=BC=80=E5=90=AF=20agent=EF=BC=9A=E6=96=B0=E5=A2=9E=20SC=5FEN?= =?UTF-8?q?ABLE=5FAGENT=20env=20=E8=A6=86=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - build-config 新增 resolveAgentEnabled:env 覆盖优先,否则按版本派生 - rspack.config.ts / pack.js 改用 resolveAgentEnabled - test.yaml e2e 构建设 SC_ENABLE_AGENT=true,使正式版下 agent 用例仍可运行 - 正式版默认仍屏蔽 agent(无 env 时不变) --- .github/workflows/test.yaml | 3 +++ rspack.config.ts | 7 ++++--- scripts/build-config.js | 11 +++++++++++ scripts/build-config.test.js | 20 +++++++++++++++++++- scripts/pack.js | 9 +++++++-- 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b63f4104a..8ba9023cd 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -83,7 +83,10 @@ jobs: run: pnpm test:e2e:install - name: Build extension + # 正式版构建默认屏蔽 agent 入口;e2e 需覆盖 agent 用例,故强制开启 run: pnpm build + env: + SC_ENABLE_AGENT: "true" - name: Run E2E tests run: pnpm test:e2e diff --git a/rspack.config.ts b/rspack.config.ts index 6d468b26b..c35ebe6fb 100644 --- a/rspack.config.ts +++ b/rspack.config.ts @@ -4,7 +4,7 @@ import { ZipExecutionPlugin } from "./rspack-plugins/ZipExecutionPlugin"; import { readFileSync } from "fs"; import { v4 as uuidv4 } from "uuid"; import { toChromeVersion } from "./scripts/version.js"; -import { isAgentEnabled, applyAgentManifest } from "./scripts/build-config.js"; +import { resolveAgentEnabled, applyAgentManifest } from "./scripts/build-config.js"; const pkg = JSON.parse(readFileSync("./package.json", "utf-8")); @@ -13,8 +13,9 @@ const dirname = path.resolve(); const isDev = process.env.NODE_ENV === "development"; const isBeta = version.includes("-"); const isReactTools = process.env.REACT_DEVTOOLS === "true"; -// agent 功能仅在开发版与 beta 版本提供,正式版本屏蔽入口并移除 debugger 权限 -const enableAgent = isAgentEnabled({ isDev, isBeta }); +// agent 功能仅在开发版与 beta 版本提供,正式版本屏蔽入口并移除 debugger 权限。 +// 可用环境变量 SC_ENABLE_AGENT 强制覆盖(如 e2e 在正式版上强制开启)。 +const enableAgent = resolveAgentEnabled({ isDev, isBeta, envValue: process.env.SC_ENABLE_AGENT }); // Target browsers, see: https://github.com/browserslist/browserslist // 依照 https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/userScripts#browser_compatibility diff --git a/scripts/build-config.js b/scripts/build-config.js index cb56fbed3..376234831 100644 --- a/scripts/build-config.js +++ b/scripts/build-config.js @@ -7,6 +7,17 @@ export function isAgentEnabled({ isDev, isBeta }) { return Boolean(isDev || isBeta); } +/** + * 解析 agent 开关:环境变量 SC_ENABLE_AGENT 优先(用于 e2e/CI 或手动强制覆盖), + * 未设置时按版本派生(dev/beta 启用,正式版屏蔽)。 + * @param {{ isDev: boolean, isBeta: boolean, envValue: string | undefined }} param + * @returns {boolean} + */ +export function resolveAgentEnabled({ isDev, isBeta, envValue }) { + if (envValue !== undefined) return envValue === "true"; + return isAgentEnabled({ isDev, isBeta }); +} + /** * 正式版本不提供 agent,移除仅 agent 使用的 debugger 权限; * 其余权限保持不变。启用 agent 时原样返回,禁用时返回新对象,不修改入参。 diff --git a/scripts/build-config.test.js b/scripts/build-config.test.js index 8bd0e0860..d9bdcac70 100644 --- a/scripts/build-config.test.js +++ b/scripts/build-config.test.js @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { isAgentEnabled, applyAgentManifest } from "./build-config.js"; +import { isAgentEnabled, resolveAgentEnabled, applyAgentManifest } from "./build-config.js"; describe("构建配置 - agent 开关", () => { describe("isAgentEnabled", () => { @@ -16,6 +16,24 @@ describe("构建配置 - agent 开关", () => { }); }); + describe("resolveAgentEnabled - 环境变量覆盖", () => { + it("未设置环境变量时按版本派生(正式版禁用)", () => { + expect(resolveAgentEnabled({ isDev: false, isBeta: false, envValue: undefined })).toBe(false); + }); + + it("未设置环境变量时按版本派生(beta 启用)", () => { + expect(resolveAgentEnabled({ isDev: false, isBeta: true, envValue: undefined })).toBe(true); + }); + + it("环境变量 'true' 可在正式版强制启用 agent(用于 e2e)", () => { + expect(resolveAgentEnabled({ isDev: false, isBeta: false, envValue: "true" })).toBe(true); + }); + + it("环境变量 'false' 可在 beta 强制禁用 agent", () => { + expect(resolveAgentEnabled({ isDev: false, isBeta: true, envValue: "false" })).toBe(false); + }); + }); + describe("applyAgentManifest", () => { const makeManifest = () => ({ permissions: ["tabs", "debugger", "storage"], diff --git a/scripts/pack.js b/scripts/pack.js index 9b4a407d4..ebe579131 100644 --- a/scripts/pack.js +++ b/scripts/pack.js @@ -7,7 +7,7 @@ import manifest from "../src/manifest.json" with { type: "json" }; import packageInfo from "../package.json" with { type: "json" }; import semver from "semver"; import { toChromeVersion } from "./version.js"; -import { isAgentEnabled, applyAgentManifest } from "./build-config.js"; +import { resolveAgentEnabled, applyAgentManifest } from "./build-config.js"; // ============================================================================ @@ -30,7 +30,12 @@ const addZipFile = async (zip, path, content) => { // 判断是否为beta版本 const version = semver.parse(packageInfo.version); // agent 功能仅在 beta 版本提供,正式版本屏蔽入口并移除 debugger 权限 -const agentEnabled = isAgentEnabled({ isDev: false, isBeta: version.prerelease.length > 0 }); +// (与 rspack 构建一致,支持环境变量 SC_ENABLE_AGENT 覆盖) +const agentEnabled = resolveAgentEnabled({ + isDev: false, + isBeta: version.prerelease.length > 0, + envValue: process.env.SC_ENABLE_AGENT, +}); manifest.version = toChromeVersion(packageInfo.version); if (version.prerelease.length) { manifest.name = `__MSG_scriptcat_beta__`;