From 7650147b99177f25e5c03d010daef894e78b7680 Mon Sep 17 00:00:00 2001 From: rezon-aki Date: Sun, 11 Oct 2026 19:00:18 +0800 Subject: [PATCH] fix(manifest): declare DSH 0.2.x compatibility and drop the removed dsh-client-runtime peer DSH's startup gate matches peerDependencies with includePrerelease, so the old '<0.2.0' ranges let 0.2.0-rc.x through but reject 0.2.0 and later, which skips the whole bundle at load time. --- CHANGELOG.md | 11 ++++++ CHANGELOG_ZH.md | 11 ++++++ build.mjs | 9 ++--- package.json | 11 ++---- tests/dsh-compat.test.mjs | 77 +++++++++++++++++++++++++++++++++++++++ tests/manifest.test.mjs | 7 +++- 6 files changed, 110 insertions(+), 16 deletions(-) create mode 100644 tests/dsh-compat.test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index ddfce2f..6253f06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses [Semantic Versioning](https://semver.org/). 中文版见 [CHANGELOG_ZH.md](./CHANGELOG_ZH.md). +## [Unreleased] + +### Fixed + +- **DSH 0.2.0 compatibility** — the `@deepseek-ai/dsh-client-*` peer ranges ended at `<0.2.0`. DSH's own startup gate (`evaluatePluginCompatibility` in `dsh-app-boot`, which matches with `includePrerelease`) therefore accepted `0.2.0-rc.x` but **rejects `0.2.0` and every later version**. An incompatible bundle is skipped whole: the indicator, the panel and all five routes disappear, leaving only one line on `dsh web`'s stderr. The ranges now cover both lines — `>=0.1.0-rc.6 <0.2.0 || >=0.2.0-rc.1 <0.3.0`. +- Removed `@deepseek-ai/dsh-client-runtime` from `peerDependencies` and from `dsh.client.inject`: the package no longer exists in DSH 0.2.0, and this plugin only ever imported it as a type (erased at build time). `@deepseek-ai/dsh-client-web-react` and `@deepseek-ai/dsh-client-schema-form` were dropped from the client externals for the same reason. + +### Tests + +- New `tests/dsh-compat.test.mjs`: calls DSH's own `evaluatePluginCompatibility` against this manifest for a version matrix (0.1.x, 0.2.0-rc.x, **and the 0.2.0/0.2.1 releases**), asserts the unsupported 0.3.0 line is still rejected, and checks that every DSH package the manifest names is actually installed in the target DSH. Skips when no DSH is present, since the plugin's CI installs only the plugin. + ## [1.1.7] - 2026-09-24 ### Fixed diff --git a/CHANGELOG_ZH.md b/CHANGELOG_ZH.md index 9d53802..99ea3ff 100644 --- a/CHANGELOG_ZH.md +++ b/CHANGELOG_ZH.md @@ -3,6 +3,17 @@ 本文件记录本项目所有重要变更。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/), 版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。英文版见 [CHANGELOG.md](./CHANGELOG.md)。 +## [未发布] + +### 修复 + +- **适配 DSH 0.2.0**——`@deepseek-ai/dsh-client-*` 的 peer 区间原本止于 `<0.2.0`。DSH 启动期的兼容性门禁(`dsh-app-boot` 的 `evaluatePluginCompatibility`,判定时带 `includePrerelease`)因此放行了 `0.2.0-rc.x`,但**会拒绝 `0.2.0` 及其之后的任何版本**。被拒的 bundle 会被整包跳过:指示器、面板和全部五个路由一起消失,只在 `dsh web` 的 stderr 留一行。现区间覆盖两条线——`>=0.1.0-rc.6 <0.2.0 || >=0.2.0-rc.1 <0.3.0`。 +- 从 `peerDependencies` 与 `dsh.client.inject` 中移除 `@deepseek-ai/dsh-client-runtime`:该包在 DSH 0.2.0 已不存在,本插件对它也只有一个构建期即被擦除的 type-only import。同理从客户端 externals 中移除 `@deepseek-ai/dsh-client-web-react` 与 `@deepseek-ai/dsh-client-schema-form`。 + +### 测试 + +- 新增 `tests/dsh-compat.test.mjs`:直接调用 DSH 自己的 `evaluatePluginCompatibility`,对本插件的 manifest 跑一组版本矩阵(0.1.x、0.2.0-rc.x,**以及 0.2.0/0.2.1 正式版**),断言未声明支持的 0.3.0 线仍被拒绝,并校验 manifest 点名的每个 DSH 包在目标 DSH 里真实存在。环境里没有 DSH 时跳过(插件自身的 CI 只装插件)。 + ## [1.1.7] - 2026-09-24 ### 修复 diff --git a/build.mjs b/build.mjs index 152a608..768994a 100644 --- a/build.mjs +++ b/build.mjs @@ -19,9 +19,9 @@ const tscBin = require.resolve('typescript/bin/tsc') const PLUGIN_ID = process.env.DSH_PLUGIN_ID ?? 'dsh-usage-chart' -// 与 @deepseek-ai/dsh-client-web/src/platform.ts 的 PLATFORM_MODULES 完全一致 -// + RUNTIME_STORE_EXEMPTION(packages/client/tsdown.client.ts 的 CLIENT_EXTERNALS)。 -// 运行时这些 specifier 由浏览器的模块表(loader)回答,绝不内联。 +// 浏览器模块表(loader)能回答的 specifier,绝不内联。清单已按 DSH 0.2.0-rc.2 核对: +// 只保留实际存在的包——0.1.x 时代的 dsh-client-runtime / dsh-client-web-react / +// dsh-client-schema-form 在 0.2.0 已不存在(本包对它们只有 type-only import,esbuild 会擦除)。 const CLIENT_EXTERNALS = [ 'react', 'react/jsx-runtime', @@ -29,11 +29,8 @@ const CLIENT_EXTERNALS = [ 'react-dom/client', '@deepseek-ai/cordis', '@deepseek-ai/dsh-client-ui-slots', - '@deepseek-ai/dsh-client-web-react', '@deepseek-ai/dsh-client-ui-primitives', '@deepseek-ai/dsh-client-ui-attachment', - '@deepseek-ai/dsh-client-schema-form', - '@deepseek-ai/dsh-client-runtime/client', ] // A clean output directory prevents stale chunks or declarations from leaking diff --git a/package.json b/package.json index cbff38b..a9f23fd 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ }, "client": { "inject": [ - "@deepseek-ai/dsh-client-runtime", "@deepseek-ai/dsh-client-locale", "@deepseek-ai/dsh-client-ui-conversation", "@deepseek-ai/dsh-client-ui-slots" @@ -87,19 +86,15 @@ }, "peerDependencies": { "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-client-runtime": ">=0.1.0-rc.6 <0.2.0", - "@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.6 <0.2.0", - "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.0-rc.6 <0.2.0", - "@deepseek-ai/dsh-client-ui-slots": ">=0.1.0-rc.6 <0.2.0", + "@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.6 <0.2.0 || >=0.2.0-rc.1 <0.3.0", + "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.0-rc.6 <0.2.0 || >=0.2.0-rc.1 <0.3.0", + "@deepseek-ai/dsh-client-ui-slots": ">=0.1.0-rc.6 <0.2.0 || >=0.2.0-rc.1 <0.3.0", "react": "^18.2.0" }, "peerDependenciesMeta": { "@deepseek-ai/cordis": { "optional": true }, - "@deepseek-ai/dsh-client-runtime": { - "optional": true - }, "@deepseek-ai/dsh-client-locale": { "optional": true }, diff --git a/tests/dsh-compat.test.mjs b/tests/dsh-compat.test.mjs new file mode 100644 index 0000000..39b6e0f --- /dev/null +++ b/tests/dsh-compat.test.mjs @@ -0,0 +1,77 @@ +/** + * DSH 运行时兼容性门禁——把"假绿"换成真检查。 + * + * DSH 启动时会用 dsh-app-boot 的 evaluatePluginCompatibility 判定每个 profile bundle 的 + * peerDependencies;判为不兼容的 bundle 会被整包跳过,用量面板与全部路由一起消失, + * 只在 dsh web 的 stderr 留一行。本文件直接调用同一个函数,对着插件自己的 manifest 断言 + * 声明仍然成立,并且**覆盖各线正式版**(0.2.0 / 0.2.1),而不只是 rc —— "<0.2.0" 那个区间 + * 正是"rc 通过、正式版被拒",也就是这里要拦住的东西。 + * + * 解析不到 DSH 安装时跳过:插件自身的 CI 只装插件,不装 DSH。 + */ +import assert from 'node:assert/strict' +import test from 'node:test' +import { readFileSync } from 'node:fs' +import { createRequire } from 'node:module' +import { dirname, join } from 'node:path' +import { fileURLToPath, pathToFileURL } from 'node:url' + +const root = dirname(dirname(fileURLToPath(import.meta.url))) +const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')) + +/** 声明支持的 DSH 运行时版本(含各线正式版)。 */ +const SUPPORTED = ['0.1.0-rc.6', '0.1.5', '0.1.9', '0.2.0-rc.1', '0.2.0-rc.2', '0.2.0', '0.2.1'] +/** 明确不支持、必须被拒的版本;要放宽区间就先改这里。 */ +const REJECTED = ['0.3.0'] + +/** 候选 DSH 安装根(DSH_CHECKOUT 优先,其次本机全局安装)。 */ +const DSH_ROOTS = [process.env.DSH_CHECKOUT, '/usr/local/lib/node_modules/@deepseek-ai/dsh'].filter(Boolean) + +/** 取真正的判定函数与提供它的 DSH 根;取不到返回 undefined(CI 上正常)。 */ +async function loadDsh() { + for (const base of DSH_ROOTS) { + try { + const req = createRequire(join(base, 'package.json')) + const entry = req.resolve('@deepseek-ai/dsh-app-boot') + return { root: base, module: await import(pathToFileURL(entry).href) } + } catch { + // 换下一个候选 + } + } + return undefined +} + +const dsh = await loadDsh() +const skip = dsh === undefined ? 'DSH is not installed in this environment' : false + +test('peerDependencies accept every supported DSH runtime', { skip }, () => { + for (const version of SUPPORTED) { + const issue = dsh.module.evaluatePluginCompatibility(manifest, {}, version) + assert.equal(issue, undefined, `dsh ${version} rejects ${JSON.stringify(issue?.peers ?? {})}`) + } +}) + +test('peerDependencies still reject runtimes outside the declared lines', { skip }, () => { + for (const version of REJECTED) { + const issue = dsh.module.evaluatePluginCompatibility(manifest, {}, version) + assert.notEqual(issue, undefined, `dsh ${version} is accepted but the plugin declares no support for it`) + } +}) + +test('every DSH package the manifest names is installed in the target DSH', { skip }, () => { + const named = [...Object.keys(manifest.peerDependencies ?? {}), ...(manifest.dsh?.client?.inject ?? [])] + const req = createRequire(join(dsh.root, 'package.json')) + const missing = [...new Set(named)].filter((name) => { + if (name !== '@deepseek-ai/dsh' && !name.startsWith('@deepseek-ai/dsh-')) return false + for (const specifier of [`${name}/package.json`, name]) { + try { + req.resolve(specifier) + return false + } catch { + // 试下一个 specifier + } + } + return true + }) + assert.deepEqual(missing, [], `declared but absent from DSH ${dsh.module.getDshRuntimeVersion()}`) +}) diff --git a/tests/manifest.test.mjs b/tests/manifest.test.mjs index 40ae4ee..f3e8723 100644 --- a/tests/manifest.test.mjs +++ b/tests/manifest.test.mjs @@ -8,8 +8,11 @@ test('package declares an installable DSH web bundle', () => { assert.equal(pkg.dsh.bundle.patch, './cordis.patch.yml') assert.equal(pkg.dsh.client.platform, 'web') assert.ok(pkg.keywords.includes('dsh-plugin')) - assert.equal(pkg.peerDependencies['@deepseek-ai/dsh-client-locale'], '>=0.1.0-rc.6 <0.2.0') - assert.equal(pkg.peerDependenciesMeta['@deepseek-ai/dsh-client-locale'].optional, true) + // 区间语义由 tests/dsh-compat.test.mjs 对着 DSH 自己的门禁校验;这里只锁声明形状。 + for (const name of ['@deepseek-ai/dsh-client-locale', '@deepseek-ai/dsh-client-ui-conversation', '@deepseek-ai/dsh-client-ui-slots']) { + assert.equal(typeof pkg.peerDependencies[name], 'string', name) + assert.equal(pkg.peerDependenciesMeta[name].optional, true, name) + } }) test('all exported runtime and type files exist after build', async () => {