fix(preview-download): declare remote service dependency

This commit is contained in:
2026-10-17 03:31:32 +08:00
parent bbdee632f6
commit eb7e809999
9 changed files with 39 additions and 9 deletions
+8 -2
View File
@@ -1,5 +1,11 @@
# DSH Runtime Release # DSH Runtime Release
## runtime-7.8:修复下载按钮服务依赖
下载插件升级为 dsh-preview-download@1.0.1,补齐 remote 服务声明,修复点击时在发送请求前抛出 cannot get property "remote" without inject 的问题。同步更新内置包和新 profile 引用,新增注册回调依赖回归测试。此修复不处理面板 401、Office 504 或 plugins/events HTTP/2 连接错误。
已有实例升级镜像时,必须把 profile 中 dsh-preview-download 依赖的归档路径从 dsh-preview-download-1.0.0.tgz 改为 dsh-preview-download-1.0.1.tgz,保留其他配置和 bundles,重启后刷新页面;已有 profile 不自动改写。尚未完成线上浏览器下载验收。
## runtime-7.7:预览下载插件 ## runtime-7.7:预览下载插件
内置独立插件 dsh-preview-download@1.0.0,在文档预览工具栏提供「下载原文件」。使用正式 sidebar.right.tab.document.actions 插槽,不修改上游 UI、不扫描 DOM、不增加服务端下载接口。通过当前会话 workspaceFiles.readBytes 获取原始字节,沿用服务端权限和完整文件大小上限;支持中文文件名、下载中状态、失败提示及切换文件/卸载时取消读取。下载的是原始文件,不是 Office 转换出的 PDF。文件元数据无法读取时不会显示工具栏按钮。 内置独立插件 dsh-preview-download@1.0.0,在文档预览工具栏提供「下载原文件」。使用正式 sidebar.right.tab.document.actions 插槽,不修改上游 UI、不扫描 DOM、不增加服务端下载接口。通过当前会话 workspaceFiles.readBytes 获取原始字节,沿用服务端权限和完整文件大小上限;支持中文文件名、下载中状态、失败提示及切换文件/卸载时取消读取。下载的是原始文件,不是 Office 转换出的 PDF。文件元数据无法读取时不会显示工具栏按钮。
@@ -7,7 +13,7 @@
新建空 profile 默认启用。已有实例升级镜像后,在 /workspace/profiles/web/package.json 的 dependencies 中添加以下条目,并在 dsh.profile.bundles 中追加一次 dsh-preview-download,保留其他配置,然后重启: 新建空 profile 默认启用。已有实例升级镜像后,在 /workspace/profiles/web/package.json 的 dependencies 中添加以下条目,并在 dsh.profile.bundles 中追加一次 dsh-preview-download,保留其他配置,然后重启:
~~~json ~~~json
"dsh-preview-download": "file:/usr/local/lib/dsh-mgr/dsh-preview-download-1.0.0.tgz" "dsh-preview-download": "file:/usr/local/lib/dsh-mgr/dsh-preview-download-1.0.1.tgz"
~~~ ~~~
插件源码位于 image/preview-download,也可单独打包安装。无第三方运行时依赖,React 由 DSH 浏览器提供。当前仅针对 DSH 0.2.0-rc.2 验证。25 项离线回归测试通过,已在现有 AMD64 镜像中验证插件打包、新 profile 离线安装及服务启动;完整 runtime-7.7 构建在 MiSans 下载阶段超时,尚未完成浏览器实际点击下载、移动端和 ARM64 验收。不应把单元测试中的拒绝响应模拟当作真实权限端到端验收。 插件源码位于 image/preview-download,也可单独打包安装。无第三方运行时依赖,React 由 DSH 浏览器提供。当前仅针对 DSH 0.2.0-rc.2 验证。25 项离线回归测试通过,已在现有 AMD64 镜像中验证插件打包、新 profile 离线安装及服务启动;完整 runtime-7.7 构建在 MiSans 下载阶段超时,尚未完成浏览器实际点击下载、移动端和 ARM64 验收。不应把单元测试中的拒绝响应模拟当作真实权限端到端验收。
@@ -70,7 +76,7 @@ Runtime 镜像包含 Debian Bookworm 的 Python 3、pip、venv(`python` 指向
## 发布清单 ## 发布清单
`release.json` 是发布入口:`schema_version` 为清单格式版本,`release_id` 标识一次发布,`version` 为精确 DSH 版本,`deployment_schema` 为运行布局版本,`platforms` 为支持的平台。当前待发布配置为 `runtime-7.7`、DSH `0.2.0-rc.2`、布局 7、`linux/amd64` 和 `linux/arm64`(aarch64,不含 32 位 ARM)。 `release.json` 是发布入口:`schema_version` 为清单格式版本,`release_id` 标识一次发布,`version` 为精确 DSH 版本,`deployment_schema` 为运行布局版本,`platforms` 为支持的平台。当前待发布配置为 `runtime-7.8`、DSH `0.2.0-rc.2`、布局 7、`linux/amd64` 和 `linux/arm64`(aarch64,不含 32 位 ARM)。
ARM64 验收状态:已确认 Node/Caddy 基础镜像提供 ARM64 版本,但尚未完成 ARM64 runtime 构建及运行验收。清单声明会开放面板的对应平台构建入口,不等于兼容性证明;验收完成前不要推送发布。 ARM64 验收状态:已确认 Node/Caddy 基础镜像提供 ARM64 版本,但尚未完成 ARM64 runtime 构建及运行验收。清单声明会开放面板的对应平台构建入口,不等于兼容性证明;验收完成前不要推送发布。
+1 -1
View File
@@ -8,7 +8,7 @@ if [[ -f "${DSH_HOME}/profiles/web/package.json" ]]; then
# pnpm otherwise probes registry metadata for the overlapping optional peer. # pnpm otherwise probes registry metadata for the overlapping optional peer.
# Restrict offline mode to profiles containing only our fixed archives. # Restrict offline mode to profiles containing only our fixed archives.
install_env=() install_env=()
if node -e 'const m=require(process.env.DSH_HOME+"/profiles/web/package.json"); const pins={"dsh-preview-download":"file:/usr/local/lib/dsh-mgr/dsh-preview-download-1.0.0.tgz","dsh-chat-manager":"file:/usr/local/lib/dsh-mgr/dsh-chat-manager-1.5.12-dsh-mgr.1.tgz","dsh-mgr-workspace":"file:/usr/local/lib/dsh-mgr/dsh-mgr-workspace-1.0.0.tgz","dsh-web-mobile":"file:/usr/local/lib/dsh-mgr/dsh-web-mobile-3.0.5.tgz","dsh-free-search":"file:/usr/local/lib/dsh-mgr/dsh-free-search-0.8.5.tgz"}; process.exit(Object.entries({...m.dependencies,...m.devDependencies,...m.optionalDependencies}).every(([n,v])=>pins[n]===v)?0:1)'; then if node -e 'const m=require(process.env.DSH_HOME+"/profiles/web/package.json"); const pins={"dsh-preview-download":"file:/usr/local/lib/dsh-mgr/dsh-preview-download-1.0.1.tgz","dsh-chat-manager":"file:/usr/local/lib/dsh-mgr/dsh-chat-manager-1.5.12-dsh-mgr.1.tgz","dsh-mgr-workspace":"file:/usr/local/lib/dsh-mgr/dsh-mgr-workspace-1.0.0.tgz","dsh-web-mobile":"file:/usr/local/lib/dsh-mgr/dsh-web-mobile-3.0.5.tgz","dsh-free-search":"file:/usr/local/lib/dsh-mgr/dsh-free-search-0.8.5.tgz"}; process.exit(Object.entries({...m.dependencies,...m.devDependencies,...m.optionalDependencies}).every(([n,v])=>pins[n]===v)?0:1)'; then
install_env=(npm_config_offline=true) install_env=(npm_config_offline=true)
fi fi
if timeout --signal=TERM --kill-after=5s 120s env CI=true "${install_env[@]}" dsh plugin --profile web install --ignore-scripts; then if timeout --signal=TERM --kill-after=5s 120s env CI=true "${install_env[@]}" dsh plugin --profile web install --ignore-scripts; then
+1 -1
View File
@@ -14,7 +14,7 @@ if (!existsSync(profile) || readdirSync(profile).length === 0) {
mkdirSync(profile, { recursive: true }); mkdirSync(profile, { recursive: true });
writeFileSync(join(profile, 'package.json'), JSON.stringify({ writeFileSync(join(profile, 'package.json'), JSON.stringify({
name: 'dsh-profile-web', private: true, name: 'dsh-profile-web', private: true,
dependencies: { 'dsh-preview-download': 'file:/usr/local/lib/dsh-mgr/dsh-preview-download-1.0.0.tgz', 'dsh-chat-manager': 'file:/usr/local/lib/dsh-mgr/dsh-chat-manager-1.5.12-dsh-mgr.1.tgz', 'dsh-mgr-workspace': 'file:/usr/local/lib/dsh-mgr/dsh-mgr-workspace-1.0.0.tgz', 'dsh-web-mobile': 'file:/usr/local/lib/dsh-mgr/dsh-web-mobile-3.0.5.tgz', 'dsh-free-search': 'file:/usr/local/lib/dsh-mgr/dsh-free-search-0.8.5.tgz' }, dependencies: { 'dsh-preview-download': 'file:/usr/local/lib/dsh-mgr/dsh-preview-download-1.0.1.tgz', 'dsh-chat-manager': 'file:/usr/local/lib/dsh-mgr/dsh-chat-manager-1.5.12-dsh-mgr.1.tgz', 'dsh-mgr-workspace': 'file:/usr/local/lib/dsh-mgr/dsh-mgr-workspace-1.0.0.tgz', 'dsh-web-mobile': 'file:/usr/local/lib/dsh-mgr/dsh-web-mobile-3.0.5.tgz', 'dsh-free-search': 'file:/usr/local/lib/dsh-mgr/dsh-free-search-0.8.5.tgz' },
pnpm: { overrides: { pnpm: { overrides: {
'dsh-chat-manager>clsx': 'file:/usr/local/lib/dsh-mgr/clsx-2.1.1.tgz', 'dsh-chat-manager>clsx': 'file:/usr/local/lib/dsh-mgr/clsx-2.1.1.tgz',
'dsh-free-search>@deepseek-ai/schemastery': 'file:/usr/local/lib/dsh-mgr/schemastery-3.18.4.tgz', 'dsh-free-search>@deepseek-ai/schemastery': 'file:/usr/local/lib/dsh-mgr/schemastery-3.18.4.tgz',
+1 -1
View File
@@ -11,7 +11,7 @@ test('new profile enables bundle once and preserves existing configuration', ()
run(); run();
const file=join(home,'profiles/web/package.json'); const file=join(home,'profiles/web/package.json');
const original=readFileSync(file,'utf8'); const original=readFileSync(file,'utf8');
assert.equal(JSON.parse(original).dependencies['dsh-preview-download'], 'file:/usr/local/lib/dsh-mgr/dsh-preview-download-1.0.0.tgz'); assert.equal(JSON.parse(original).dependencies['dsh-preview-download'], 'file:/usr/local/lib/dsh-mgr/dsh-preview-download-1.0.1.tgz');
assert.equal(JSON.parse(original).dsh.profile.bundles.filter(name => name === 'dsh-preview-download').length, 1); assert.equal(JSON.parse(original).dsh.profile.bundles.filter(name => name === 'dsh-preview-download').length, 1);
assert.ok(JSON.parse(original).dsh.profile.bundles.includes('dsh-chat-manager')); assert.ok(JSON.parse(original).dsh.profile.bundles.includes('dsh-chat-manager'));
assert.equal(JSON.parse(original).dependencies['dsh-chat-manager'], 'file:/usr/local/lib/dsh-mgr/dsh-chat-manager-1.5.12-dsh-mgr.1.tgz'); assert.equal(JSON.parse(original).dependencies['dsh-chat-manager'], 'file:/usr/local/lib/dsh-mgr/dsh-chat-manager-1.5.12-dsh-mgr.1.tgz');
+1 -1
View File
@@ -5,4 +5,4 @@ const host = JSON.parse(readFileSync('/usr/local/lib/node_modules/@deepseek-ai/d
if (host.version !== '0.2.0-rc.2') throw new Error('Unverified preview download host'); if (host.version !== '0.2.0-rc.2') throw new Error('Unverified preview download host');
mkdirSync(root + '/preview-download-package', { recursive: true }); mkdirSync(root + '/preview-download-package', { recursive: true });
cpSync(new URL('./preview-download', import.meta.url), root + '/preview-download-package/package', { recursive: true }); cpSync(new URL('./preview-download', import.meta.url), root + '/preview-download-package/package', { recursive: true });
execFileSync('tar', ['-czf', root + '/dsh-preview-download-1.0.0.tgz', '-C', root + '/preview-download-package', 'package']); execFileSync('tar', ['-czf', root + '/dsh-preview-download-1.0.1.tgz', '-C', root + '/preview-download-package', 'package']);
+24
View File
@@ -45,3 +45,27 @@ test('plugin registers only the supported preview action slot', () => {
assert.equal(registration.id, 'dsh-preview-download'); assert.equal(registration.id, 'dsh-preview-download');
assert.equal(typeof registration.inject('session-A').download, 'function'); assert.equal(typeof registration.inject('session-A').download, 'function');
}); });
test('registered download declares every service used by its callback', async () => {
const plugin = load();
assert.ok(plugin.inject.includes('remote'));
assert.ok(plugin.inject.includes('remote.workspaceFiles'));
let registration;
let calls = 0;
const data = new Uint8Array([1, 2, 255]);
const ctx = {
effect: fn => fn(),
locale: { register: () => () => {} },
slots: { inject: (_name, fn) => fn(), register(options) { registration = options; return () => {}; } },
get remote() {
assert.ok(plugin.inject.includes('remote'), 'Cordis requires explicit remote injection');
return { workspaceFiles: { async readBytes(session, path) {
assert.equal(session, 'session-A'); assert.equal(path, '/file.bin'); calls++;
return { ok: true, value: { offset: 0, eof: true, bytes: 3, data } };
} } };
}
};
plugin.apply(ctx);
assert.equal(await registration.inject('session-A').download('/file.bin', new AbortController().signal), data);
assert.equal(calls, 1);
});
+1 -1
View File
@@ -60,7 +60,7 @@ window.__ModuleLoader__.load({ id: 'dsh-preview-download', factory: require => {
} }
return { return {
inject: ['slots', 'locale', 'remote.workspaceFiles'], inject: ['slots', 'locale', 'remote', 'remote.workspaceFiles'],
apply(ctx) { apply(ctx) {
ctx.effect(() => ctx.locale.register(namespace, { ctx.effect(() => ctx.locale.register(namespace, {
zh: { download: '下载原文件', loading: '下载中…', failed: '下载失败,请检查文件权限或重试。', tooLarge: '文件超过下载大小限制。' }, zh: { download: '下载原文件', loading: '下载中…', failed: '下载失败,请检查文件权限或重试。', tooLarge: '文件超过下载大小限制。' },
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "dsh-preview-download", "name": "dsh-preview-download",
"version": "1.0.0", "version": "1.0.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "index.js", "main": "index.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"schema_version": 1, "schema_version": 1,
"release_id": "runtime-7.7", "release_id": "runtime-7.8",
"version": "0.2.0-rc.2", "version": "0.2.0-rc.2",
"deployment_schema": 7, "deployment_schema": 7,
"platforms": ["linux/amd64", "linux/arm64"] "platforms": ["linux/amd64", "linux/arm64"]