dsh-preview-market 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.3
4
+
5
+ - Catalog entries now live in `catalog/plugins/<owner>__<repo>.json` — one file per plugin, so third parties can submit a PR without editing market code.
6
+ - The Host loads, validates and merges every file at startup; duplicate names and filename/repository mismatches fail fast.
7
+ - `catalog.json` remains the generated machine-readable snapshot; a test keeps it in sync.
8
+
3
9
  ## 0.1.2
4
10
 
5
11
  - Add a top-level **Settings → Preview market** section, next to dsh-market, with Discover / Installed tabs.
package/README.md CHANGED
@@ -23,7 +23,9 @@ Upgrading from `dsh-media-preview` / `dsh-blend-preview` / `dsh-stl-preview`? Se
23
23
  - **Install / update** — delegates to `dsh-market` when it is present. Without it, the card shows the exact `dsh plugin --profile <profile> add <package>@<version>` command to copy.
24
24
  - **Independent plugins** — media, Blender and STL remain separate packages; this page has no renderer and does not merge their code.
25
25
 
26
- ## Catalog (0.1.2)
26
+ ## Catalog (0.1.3)
27
+
28
+ Every plugin is **one file** under [`catalog/plugins/`](catalog/plugins/), named `<owner>__<repo>.json`. A third-party submission adds exactly one file plus the regenerated `catalog.json` snapshot — no market code change. The Host loads, validates and merges all files at startup; the format and PR steps are in [`catalog/README.md`](catalog/README.md).
27
29
 
28
30
  | Package | Formats | Host requirement |
29
31
  |---|---|---|
@@ -37,7 +39,7 @@ Settings → Plugins → Preview market (namespace `preview-market`):
37
39
 
38
40
  | Key | Default | Meaning |
39
41
  |---|---|---|
40
- | `enabled` | `true` | Register the routes and the settings tab. |
42
+ | `enabled` | `true` | Register the routes, the top-level section and the Settings → Plugins tab. |
41
43
  | `routeRoot` | `/api/preview-market` | Authenticated route root; must stay under `/api`. |
42
44
  | `profileDir` | `""` | Profile to inspect. Empty uses the Host `profileContext`, then `$DSH_HOME/profiles/desktop`. |
43
45
  | `debug` | `false` | Reserved for request diagnostics. |
@@ -83,4 +85,4 @@ The client bundle is `lib/client.js` (classic `__ModuleLoader__.load` factory, n
83
85
 
84
86
  ## Status
85
87
 
86
- `0.1.2` — top-level Settings → Preview market section with Discover / Installed tabs, plus the Settings → Plugins tab. Enable/disable goes through dsh-market. One-click install requires the package in the connected dsh-market registry; otherwise use the copyable command. Configuration stays in each child plugin's own Settings → Plugins page.
88
+ `0.1.3` — top-level Settings → Preview market section with Discover / Installed tabs, plus the Settings → Plugins tab. The catalog is one file per plugin under `catalog/plugins/`, so third-party entries are a one-file PR. Enable/disable goes through dsh-market. One-click install requires the package in the connected dsh-market registry; otherwise use the copyable command. Configuration stays in each child plugin's own Settings → Plugins page.
package/README.zh.md CHANGED
@@ -23,7 +23,9 @@ dsh plugin --profile <profile> add dsh-preview-market
23
23
  - **安装/更新** — 检测到 dsh-market 时委托给它;没有时显示可复制的 `dsh plugin --profile <profile> add <包>@<版本>` 命令。
24
24
  - **独立插件** — media、Blender、STL 仍是独立包;本页不注册渲染器,也不合并它们的代码。
25
25
 
26
- ## 目录(0.1.2)
26
+ ## 目录(0.1.3)
27
+
28
+ 每个插件是 [`catalog/plugins/`](catalog/plugins/) 下的**一个文件**,命名为 `<owner>__<repo>.json`。三方收录只需新增一个文件 + 重新生成 `catalog.json`,不用改市场代码;Host 启动时读取、校验并合并全部文件。字段与 PR 步骤见 [`catalog/README.md`](catalog/README.md)。
27
29
 
28
30
  | 包 | 格式 | 宿主依赖 |
29
31
  |---|---|---|
@@ -33,7 +35,7 @@ dsh plugin --profile <profile> add dsh-preview-market
33
35
 
34
36
  ## 配置
35
37
 
36
- 设置 → 插件 → 预览市场(命名空间 `preview-market`):
38
+ 设置 → 预览市场(命名空间 `preview-market`):
37
39
 
38
40
  | 键 | 默认 | 含义 |
39
41
  |---|---|---|
@@ -57,4 +59,4 @@ node --test test/*.test.js
57
59
 
58
60
  ## 状态
59
61
 
60
- `0.1.2` — 顶层「设置 → 预览市场」+ 发现/已安装两个 tab,同时保留「设置 → 插件」里的入口。启用/停用走 dsh-market;一键安装要求对应包已进入所连接 dsh-market 的 registry,否则用可复制命令。配置仍在各子插件自己的 设置 → 插件 页面。
62
+ `0.1.3` — 顶层「设置 → 预览市场」+ 发现/已安装两个 tab,同时保留「设置 → 插件」里的入口。目录改为 `catalog/plugins/` 下一插件一文件,三方收录只需一个文件 PR。启用/停用走 dsh-market;一键安装要求对应包已进入所连接 dsh-market 的 registry,否则用可复制命令。配置仍在各子插件自己的 设置 → 插件 页面。
package/VERIFY.md CHANGED
@@ -1,15 +1,16 @@
1
- # Verification record — dsh-preview-market 0.1.2
1
+ # Verification record — dsh-preview-market 0.1.3
2
2
 
3
3
  ## Unit tests (2026-10-23)
4
4
 
5
- `node --test test/*.test.js` → **10 pass, 0 fail**.
5
+ `node --test test/*.test.js` → **12 pass, 0 fail**.
6
6
 
7
7
  | File | Covers |
8
8
  |---|---|
9
9
  | `test/config.test.js` | defaults, `/api` route fence, route normalization, boolean normalization |
10
- | `test/catalog.test.js` | three catalog entries, required fields, payload shape |
10
+ | `test/catalog.test.js` | one file per entry, required fields, filename/repository match, duplicates, payload shape |
11
+ | `test/catalog-file.test.js` | `catalog.json` snapshot matches the files |
11
12
  | `test/installed.test.js` | profile `package.json` + `node_modules` inventory, missing profile, `profileContext` resolution |
12
- | `test/client.test.js` | bundle registers exactly one `settings.plugins.tab` entry with id `dsh-preview-market` |
13
+ | `test/client.test.js` | bundle registers the `settings.section` and `settings.plugins.tab` entries |
13
14
 
14
15
  ## Host / bundle smoke (2026-10-23, preview-test profile)
15
16
 
@@ -18,6 +19,13 @@
18
19
  - The Web boot graph (`__DSH_BOOT__`) contains `dsh-preview-market` and injects `__DSH_PREVIEW_MARKET_CONFIG__`.
19
20
  - The combo bundle URL returned **200** (16,213 B) and contains `settings.plugins.tab` + `dsh-preview-market`.
20
21
 
22
+ ## One-file catalog (2026-10-23)
23
+
24
+ - Moved the three entries from `lib/catalog.js` into `catalog/plugins/<owner>__<repo>.json`; the Host loads and validates every file at startup.
25
+ - Added a temporary `catalog/plugins/example__dsh-preview-example.json`; `CATALOG` went from 3 to 4 entries without any code change, then was removed.
26
+ - Unit tests (12 pass) validate every file, the filename/repository match, duplicate names, required fields, and the `catalog.json` snapshot.
27
+ - `catalog/README.md` + `.github/PULL_REQUEST_TEMPLATE.md` document the one-file PR.
28
+
21
29
  ## Top-level section e2e (2026-10-23, preview-test)
22
30
 
23
31
  ```sh
@@ -0,0 +1,49 @@
1
+ # Preview catalog
2
+
3
+ One JSON file per plugin. A third-party submission adds **exactly one file** here and opens a PR; nobody edits the market's code.
4
+
5
+ ```
6
+ catalog/plugins/<owner>__<repo>.json
7
+ ```
8
+
9
+ The filename must match the repository URL: `https://github.com/<owner>/<repo>` → `<owner>__<repo>.json`. The Host validates the filename, the required fields, duplicate names and the repository host at startup.
10
+
11
+ ## Entry shape
12
+
13
+ ```json
14
+ {
15
+ "name": "dsh-preview-example",
16
+ "npm": "dsh-preview-example",
17
+ "capability": "example",
18
+ "kind": "three",
19
+ "repo": "https://github.com/example/dsh-preview-example",
20
+ "version": "0.1.0",
21
+ "minDsh": "0.2.0-rc.2",
22
+ "requires": [],
23
+ "extensions": ["example"],
24
+ "formats": { "model": ["example"] },
25
+ "description": {
26
+ "zh": "一句话说明这个预览插件做什么。",
27
+ "en": "One sentence describing what this preview plugin does."
28
+ }
29
+ }
30
+ ```
31
+
32
+ Required: `name`, `npm`, `capability`, `kind`, `repo`, `version`, `extensions`, `formats`, `description.zh`, `description.en`.
33
+
34
+ Optional: `minDsh`, `requires` (defaults to `[]`).
35
+
36
+ - `kind` is currently `media` or `three`; `three` entries get the shared WebGL viewport.
37
+ - `extensions` drives the document-preview registration in the child plugin; the catalog only displays it.
38
+ - `formats` groups extensions for the card, e.g. `{ "video": [...], "audio": [...] }`.
39
+
40
+ ## Submit a plugin
41
+
42
+ 1. Add `catalog/plugins/<owner>__<repo>.json` with the shape above.
43
+ 2. Run `npm test` (it validates every file and the `catalog.json` snapshot).
44
+ 3. Run `npm run catalog:write` to regenerate the root `catalog.json` snapshot.
45
+ 4. Open a PR that adds **one plugin file plus the regenerated snapshot**.
46
+
47
+ The plugin itself stays an independent DSH package with its own `dsh.bundle` and repository. Being in this catalog does **not** make one-click install work by itself: the package also has to be present in the connected dsh-market registry; otherwise the market shows the copyable `dsh plugin add` command.
48
+
49
+ Do not add a plugin to this catalog if it is only a copy of an existing package under another name. The catalog is for distinct preview capabilities.
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "dsh-preview-blender",
3
+ "npm": "dsh-preview-blender",
4
+ "capability": "blender",
5
+ "kind": "three",
6
+ "repo": "https://github.com/439436269-ctrl/dsh-preview-blender",
7
+ "version": "0.1.0",
8
+ "minDsh": "0.2.0-rc.2",
9
+ "requires": [
10
+ "blender"
11
+ ],
12
+ "extensions": [
13
+ "blend"
14
+ ],
15
+ "formats": {
16
+ "model": [
17
+ "blend"
18
+ ]
19
+ },
20
+ "description": {
21
+ "zh": "Blender 场景:headless 导出缓存 GLB,右侧栏 WebGL 视口 + 对象/三角面/mm 包围盒/材质统计。",
22
+ "en": "Blender scenes: a cached headless GLB export rendered in a WebGL viewport, with object, triangle, mm-bounds and material statistics."
23
+ }
24
+ }
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "dsh-preview-media",
3
+ "npm": "dsh-preview-media",
4
+ "capability": "media",
5
+ "kind": "media",
6
+ "repo": "https://github.com/439436269-ctrl/dsh-preview-media",
7
+ "version": "0.1.4",
8
+ "minDsh": "0.2.0-rc.2",
9
+ "requires": [],
10
+ "extensions": [
11
+ "mp4",
12
+ "m4v",
13
+ "mov",
14
+ "webm",
15
+ "ogv",
16
+ "ogg",
17
+ "mkv",
18
+ "avi",
19
+ "wmv",
20
+ "flv",
21
+ "3gp",
22
+ "ts",
23
+ "m2ts",
24
+ "mts",
25
+ "mp3",
26
+ "wav",
27
+ "flac",
28
+ "m4a",
29
+ "aac",
30
+ "oga",
31
+ "opus",
32
+ "weba",
33
+ "wma",
34
+ "aiff",
35
+ "aif"
36
+ ],
37
+ "formats": {
38
+ "video": [
39
+ "mp4",
40
+ "m4v",
41
+ "mov",
42
+ "webm",
43
+ "ogv",
44
+ "ogg",
45
+ "mkv",
46
+ "avi",
47
+ "wmv",
48
+ "flv",
49
+ "3gp",
50
+ "ts",
51
+ "m2ts",
52
+ "mts"
53
+ ],
54
+ "audio": [
55
+ "mp3",
56
+ "wav",
57
+ "flac",
58
+ "m4a",
59
+ "aac",
60
+ "oga",
61
+ "opus",
62
+ "weba",
63
+ "wma",
64
+ "aiff",
65
+ "aif"
66
+ ]
67
+ },
68
+ "description": {
69
+ "zh": "视频与音频:右侧栏原生播放器,HTTP Range 流式读取,桌面端可拖动进度。",
70
+ "en": "Video and audio in the right-Sidebar document preview, streamed with HTTP Range and seekable in the desktop app."
71
+ }
72
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "dsh-preview-stl",
3
+ "npm": "dsh-preview-stl",
4
+ "capability": "stl",
5
+ "kind": "three",
6
+ "repo": "https://github.com/439436269-ctrl/dsh-preview-stl",
7
+ "version": "0.1.0",
8
+ "minDsh": "0.2.0-rc.2",
9
+ "requires": [],
10
+ "extensions": [
11
+ "stl"
12
+ ],
13
+ "formats": {
14
+ "model": [
15
+ "stl"
16
+ ]
17
+ },
18
+ "description": {
19
+ "zh": "STL 网格:浏览器解析二进制/ASCII,右侧栏 WebGL 视口 + 尺寸/体积/表面积/水密统计。",
20
+ "en": "STL meshes: binary/ASCII parsed in the browser, with a WebGL viewport and size, volume, surface-area and watertightness statistics."
21
+ }
22
+ }
package/catalog.json CHANGED
@@ -1,6 +1,30 @@
1
1
  {
2
2
  "schema": 1,
3
3
  "plugins": [
4
+ {
5
+ "name": "dsh-preview-blender",
6
+ "npm": "dsh-preview-blender",
7
+ "capability": "blender",
8
+ "kind": "three",
9
+ "repo": "https://github.com/439436269-ctrl/dsh-preview-blender",
10
+ "version": "0.1.0",
11
+ "minDsh": "0.2.0-rc.2",
12
+ "requires": [
13
+ "blender"
14
+ ],
15
+ "extensions": [
16
+ "blend"
17
+ ],
18
+ "formats": {
19
+ "model": [
20
+ "blend"
21
+ ]
22
+ },
23
+ "description": {
24
+ "zh": "Blender 场景:headless 导出缓存 GLB,右侧栏 WebGL 视口 + 对象/三角面/mm 包围盒/材质统计。",
25
+ "en": "Blender scenes: a cached headless GLB export rendered in a WebGL viewport, with object, triangle, mm-bounds and material statistics."
26
+ }
27
+ },
4
28
  {
5
29
  "name": "dsh-preview-media",
6
30
  "npm": "dsh-preview-media",
@@ -73,30 +97,6 @@
73
97
  "en": "Video and audio in the right-Sidebar document preview, streamed with HTTP Range and seekable in the desktop app."
74
98
  }
75
99
  },
76
- {
77
- "name": "dsh-preview-blender",
78
- "npm": "dsh-preview-blender",
79
- "capability": "blender",
80
- "kind": "three",
81
- "repo": "https://github.com/439436269-ctrl/dsh-preview-blender",
82
- "version": "0.1.0",
83
- "minDsh": "0.2.0-rc.2",
84
- "requires": [
85
- "blender"
86
- ],
87
- "extensions": [
88
- "blend"
89
- ],
90
- "formats": {
91
- "model": [
92
- "blend"
93
- ]
94
- },
95
- "description": {
96
- "zh": "Blender 场景:headless 导出缓存 GLB,右侧栏 WebGL 视口 + 对象/三角面/mm 包围盒/材质统计。",
97
- "en": "Blender scenes: a cached headless GLB export rendered in a WebGL viewport, with object, triangle, mm-bounds and material statistics."
98
- }
99
- },
100
100
  {
101
101
  "name": "dsh-preview-stl",
102
102
  "npm": "dsh-preview-stl",
package/lib/catalog.js CHANGED
@@ -1,75 +1,91 @@
1
1
  /**
2
2
  * The vertical preview catalog shipped with this release.
3
3
  *
4
- * The catalog is intentionally data-only: adding a new preview plugin should
5
- * be a catalog entry plus that plugin's own repository, not a change to the
6
- * market's renderer or routes. Third-party plugins can be added later with the
7
- * same fields; `dsh.preview` in the installed package is used to enrich the
8
- * installed view when present.
4
+ * One JSON file per plugin lives in `catalog/plugins/`; a third-party
5
+ * submission adds exactly one file and opens a PR. The Host reads and validates
6
+ * every file at startup, so adding an entry never requires touching this module.
9
7
  *
10
8
  * @module dsh-preview-market/catalog
11
9
  */
10
+ import { readdirSync, readFileSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { fileURLToPath } from "node:url";
12
13
 
13
14
  /** Catalog schema version. */
14
15
  export const CATALOG_SCHEMA = 1;
15
16
 
16
- /** One preview plugin per entry. */
17
- export const CATALOG = Object.freeze([
18
- {
19
- name: "dsh-preview-media",
20
- npm: "dsh-preview-media",
21
- capability: "media",
22
- kind: "media",
23
- repo: "https://github.com/439436269-ctrl/dsh-preview-media",
24
- version: "0.1.4",
25
- minDsh: "0.2.0-rc.2",
26
- requires: [],
27
- extensions: [
28
- "mp4", "m4v", "mov", "webm", "ogv", "ogg", "mkv", "avi", "wmv", "flv", "3gp", "ts", "m2ts", "mts",
29
- "mp3", "wav", "flac", "m4a", "aac", "oga", "opus", "weba", "wma", "aiff", "aif"
30
- ],
31
- formats: {
32
- video: ["mp4", "m4v", "mov", "webm", "ogv", "ogg", "mkv", "avi", "wmv", "flv", "3gp", "ts", "m2ts", "mts"],
33
- audio: ["mp3", "wav", "flac", "m4a", "aac", "oga", "opus", "weba", "wma", "aiff", "aif"]
34
- },
35
- description: {
36
- zh: "视频与音频:右侧栏原生播放器,HTTP Range 流式读取,桌面端可拖动进度。",
37
- en: "Video and audio in the right-Sidebar document preview, streamed with HTTP Range and seekable in the desktop app."
38
- }
39
- },
40
- {
41
- name: "dsh-preview-blender",
42
- npm: "dsh-preview-blender",
43
- capability: "blender",
44
- kind: "three",
45
- repo: "https://github.com/439436269-ctrl/dsh-preview-blender",
46
- version: "0.1.0",
47
- minDsh: "0.2.0-rc.2",
48
- requires: ["blender"],
49
- extensions: ["blend"],
50
- formats: { model: ["blend"] },
51
- description: {
52
- zh: "Blender 场景:headless 导出缓存 GLB,右侧栏 WebGL 视口 + 对象/三角面/mm 包围盒/材质统计。",
53
- en: "Blender scenes: a cached headless GLB export rendered in a WebGL viewport, with object, triangle, mm-bounds and material statistics."
54
- }
55
- },
56
- {
57
- name: "dsh-preview-stl",
58
- npm: "dsh-preview-stl",
59
- capability: "stl",
60
- kind: "three",
61
- repo: "https://github.com/439436269-ctrl/dsh-preview-stl",
62
- version: "0.1.0",
63
- minDsh: "0.2.0-rc.2",
64
- requires: [],
65
- extensions: ["stl"],
66
- formats: { model: ["stl"] },
67
- description: {
68
- zh: "STL 网格:浏览器解析二进制/ASCII,右侧栏 WebGL 视口 + 尺寸/体积/表面积/水密统计。",
69
- en: "STL meshes: binary/ASCII parsed in the browser, with a WebGL viewport and size, volume, surface-area and watertightness statistics."
70
- }
17
+ const PLUGIN_DIR = fileURLToPath(new URL("../catalog/plugins/", import.meta.url));
18
+ const REQUIRED = [
19
+ "name",
20
+ "npm",
21
+ "capability",
22
+ "kind",
23
+ "repo",
24
+ "version",
25
+ "extensions",
26
+ "formats",
27
+ "description"
28
+ ];
29
+
30
+ /** Derive `<owner>__<repo>` from a GitHub repository URL. */
31
+ function repoTuple(url) {
32
+ let parsed;
33
+ try {
34
+ parsed = new URL(url);
35
+ } catch {
36
+ throw new Error(`invalid repo URL: ${url}`);
71
37
  }
72
- ]);
38
+ if (parsed.hostname !== "github.com") throw new Error(`repo must be a github.com URL: ${url}`);
39
+ const parts = parsed.pathname.split("/").filter(Boolean);
40
+ if (parts.length < 2) throw new Error(`repo must point at a repository: ${url}`);
41
+ const owner = parts[0];
42
+ const repo = parts[1].replace(/\.git$/, "");
43
+ return { owner, repo };
44
+ }
45
+
46
+ function loadCatalog() {
47
+ const files = readdirSync(PLUGIN_DIR)
48
+ .filter((file) => file.endsWith(".json"))
49
+ .sort();
50
+ if (files.length === 0) throw new Error("catalog/plugins has no entries");
51
+
52
+ const seen = new Set();
53
+ return Object.freeze(
54
+ files.map((file) => {
55
+ let entry;
56
+ try {
57
+ entry = JSON.parse(readFileSync(join(PLUGIN_DIR, file), "utf8"));
58
+ } catch (error) {
59
+ throw new Error(`catalog/plugins/${file}: invalid JSON (${error.message})`);
60
+ }
61
+ for (const key of REQUIRED) {
62
+ if (entry[key] === undefined) throw new Error(`catalog/plugins/${file}: missing ${key}`);
63
+ }
64
+ if (!Array.isArray(entry.extensions) || entry.extensions.length === 0) {
65
+ throw new Error(`catalog/plugins/${file}: extensions must be a non-empty array`);
66
+ }
67
+ if (typeof entry.formats !== "object" || entry.formats === null) {
68
+ throw new Error(`catalog/plugins/${file}: formats must be an object`);
69
+ }
70
+ if (typeof entry.description?.zh !== "string" || typeof entry.description?.en !== "string") {
71
+ throw new Error(`catalog/plugins/${file}: description.zh/en must be strings`);
72
+ }
73
+ if (seen.has(entry.name)) throw new Error(`duplicate catalog name: ${entry.name}`);
74
+ seen.add(entry.name);
75
+ entry.requires = Array.isArray(entry.requires) ? entry.requires : [];
76
+
77
+ const { owner, repo } = repoTuple(entry.repo);
78
+ const stem = file.slice(0, -".json".length);
79
+ if (stem !== `${owner}__${repo}`) {
80
+ throw new Error(`catalog/plugins/${file}: filename must be ${owner}__${repo}.json`);
81
+ }
82
+ return Object.freeze(entry);
83
+ })
84
+ );
85
+ }
86
+
87
+ /** The catalog, loaded from one file per plugin. */
88
+ export const CATALOG = loadCatalog();
73
89
 
74
90
  /** The catalog payload sent to the browser. */
75
91
  export function catalogPayload() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-preview-market",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Vertical preview market for DeepSeek Harness: discover, install and configure preview plugins (media, Blender, STL, ...) from one panel.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -22,6 +22,8 @@
22
22
  "files": [
23
23
  "lib/**/*.js",
24
24
  "catalog.json",
25
+ "catalog/**/*.json",
26
+ "catalog/README.md",
25
27
  "cordis.patch.yml",
26
28
  "README.md",
27
29
  "README.zh.md",